1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
9 #include <boost/foreach.hpp>
10 #include <Gosu/Image.hpp>
11 #include <Gosu/Math.hpp>
12 #include <Gosu/Timing.hpp>
19 #include "resourcer.h"
23 #define ASSERT(x) if (!(x)) return false
25 static std::string directions
[][3] = {
26 {"up-left", "up", "up-right"},
27 {"left", "", "right"},
28 {"down-left", "down", "down-right"},
32 Entity::Entity(Area
* area
)
48 bool Entity::init(const std::string
& descriptor
)
50 this->descriptor
= descriptor
;
51 if (!processDescriptor())
54 // Set an initial phase.
55 setPhase(directionStr(setFacing(ivec2(0, 1))));
61 int millis
= GameWindow::getWindow().time();
62 phase
->updateFrame(millis
);
63 phase
->frame()->draw(doff
.x
+ r
.x
, doff
.y
+ r
.y
, r
.z
);
67 bool Entity::needsRedraw() const
69 int millis
= GameWindow::getWindow().time();
70 return redraw
|| phase
->needsRedraw(millis
);
74 static double angleFromXY(double x
, double y
)
79 if (x
!= 0 && y
!= 0) {
83 else if (y
< 0 && x
> 0)
85 else if (y
> 0 && x
< 0)
87 else if (y
> 0 && x
> 0)
106 void Entity::update(unsigned long dt
)
108 switch (conf
.moveMode
) {
121 void Entity::updateTurn(unsigned long)
123 // Entities don't do anything in TILE mode.
126 void Entity::updateTile(unsigned long dt
)
132 double traveled
= speed
* (double)dt
;
133 double destDist
= Gosu::distance(r
.x
, r
.y
, destCoord
.x
, destCoord
.y
);
134 if (destDist
<= traveled
) {
140 double perc
= 1.0 - destDist
/traveled
;
141 unsigned long remt
= (unsigned long)(perc
* (double)dt
);
146 double angle
= angleFromXY(r
.x
- destCoord
.x
,
148 double dx
= cos(angle
);
149 double dy
= -sin(angle
);
151 // Fix inaccurate trig functions. (Why do we have to do this!??)
152 if (-1e-10 < dx
&& dx
< 1e-10)
154 if (-1e-10 < dy
&& dy
< 1e-10)
157 // Save state of partial pixel travel in double.
158 r
.x
+= dx
* traveled
;
159 r
.y
+= dy
* traveled
;
163 void Entity::updateNoTile(unsigned long)
168 const std::string
Entity::getFacing() const
170 return directionStr(facing
);
173 bool Entity::setPhase(const std::string
& name
)
175 AnimationMap::iterator it
;
176 it
= phases
.find(name
);
177 if (it
!= phases
.end()) {
178 Animation
* newPhase
= &it
->second
;
179 if (phase
!= newPhase
) {
180 int now
= GameWindow::getWindow().time();
182 phase
->startOver(now
);
190 rcoord
Entity::getPixelCoord() const
195 icoord
Entity::getTileCoords_i() const
197 return area
->virt2phys(r
);
200 vicoord
Entity::getTileCoords_vi() const
202 return area
->virt2virt(r
);
205 void Entity::setTileCoords(icoord phys
)
207 if (!area
->inBounds(phys
))
210 r
= area
->phys2virt_r(phys
);
213 void Entity::setTileCoords(vicoord virt
)
215 if (!area
->inBounds(virt
))
218 r
= area
->virt2virt(virt
);
221 void Entity::moveByTile(ivec2 delta
)
227 std::vector
<icoord
> tiles
= frontTiles();
228 BOOST_FOREACH(const icoord
& tile
, tiles
) {
234 setPhase(directionStr(facing
));
238 void Entity::setArea(Area
* a
)
242 setSpeed(speedMul
); // Calculate new speed based on tile size.
245 void Entity::gotoRandomTile()
247 const icoord map
= area
->getDimensions();
251 pos
= icoord(rand() % map
.x
, rand() % map
.y
, 0);
252 tile
= &area
->getTile(pos
);
253 } while (tile
->hasFlag(TILE_NOWALK
));
257 double Entity::getSpeed() const
262 void Entity::setSpeed(double multiplier
)
264 speedMul
= multiplier
;
266 double tilesPerSecond
= area
->getTileDimensions().x
/ 1000.0;
267 speed
= baseSpeed
* speedMul
* tilesPerSecond
;
271 Tile
& Entity::getTile() const
273 return area
->getTile(getTileCoords_i());
276 Tile
& Entity::getTile()
278 return area
->getTile(getTileCoords_i());
281 std::vector
<icoord
> Entity::frontTiles() const
283 std::vector
<icoord
> tiles
;
284 icoord normal
= getTileCoords_i();
285 normal
+= icoord(facing
.x
, facing
.y
, 0);
286 tiles
.push_back(normal
);
288 // If we are on a tile with the layermod property, we have access to
289 // tiles on the new layer, too.
290 const boost::optional
<double> layermod
= getTile().layermod
;
292 icoord mod
= area
->virt2phys(
293 vicoord(normal
.x
, normal
.y
, *layermod
));
294 tiles
.push_back(mod
);
299 void Entity::calcDoff()
301 // X-axis is centered on tile.
302 doff
.x
= (area
->getTileDimensions().x
- imgw
) / 2;
303 // Y-axis is aligned with bottom of tile.
304 doff
.y
= area
->getTileDimensions().y
- imgh
- 1;
307 SampleRef
Entity::getSound(const std::string
& name
) const
309 SampleMap::const_iterator it
;
310 it
= sounds
.find(name
);
311 if (it
!= sounds
.end())
317 ivec2
Entity::setFacing(ivec2 facing
)
319 this->facing
= ivec2(
320 Gosu::clamp(facing
.x
, -1, 1),
321 Gosu::clamp(facing
.y
, -1, 1)
326 const std::string
& Entity::directionStr(ivec2 facing
) const
328 return directions
[facing
.y
+1][facing
.x
+1];
331 bool Entity::canMove(icoord dest
)
333 bool can
= false, inBounds
;
335 delta
-= getTileCoords_i();
336 can
= can
|| (inBounds
= area
->inBounds(dest
));
337 can
= can
|| (delta
.z
== 0 && getTile().exitAt(delta
.x
, delta
.y
));
339 // The tile is off the map.
341 destCoord
= area
->phys2virt_r(dest
);
343 destTile
= &area
->getTile(dest
);
344 return !destTile
->hasFlag(TILE_NOWALK
);
352 void Entity::preMove()
355 fromTile
= &getTile();
358 // Set z right away so that we're on-level with the square we're
362 // Start moving animation.
363 switch (conf
.moveMode
) {
368 setPhase("moving " + getFacing());
374 fromTile
->onLeaveScripts(this);
376 if (conf
.moveMode
== TURN
) {
377 // Movement is instantaneous.
383 void Entity::postMove()
387 // Stop moving animation.
388 if (conf
.moveMode
!= TURN
&& !stillMoving
)
389 setPhase(getFacing());
393 destTile
->onEnterScripts(this);
397 // TODO: move teleportation here
401 * moveArea(getExit());
408 void Entity::tileExitScript()
410 Resourcer
* rc
= Resourcer::instance();
411 const std::string
& name
= scripts
["tileexit"];
413 pythonSetGlobal("Entity", this);
414 rc
->runPythonScript(name
);
418 void Entity::tileEntryScript()
420 Resourcer
* rc
= Resourcer::instance();
421 const std::string
& name
= scripts
["tileentry"];
423 pythonSetGlobal("Entity", this);
424 rc
->runPythonScript(name
);
430 * DESCRIPTOR CODE BELOW
433 bool Entity::processDescriptor()
435 Resourcer
* rc
= Resourcer::instance();
436 XMLRef doc
= rc
->getXMLDoc(descriptor
, "entity.dtd");
439 const XMLNode root
= doc
->root(); // <entity>
443 for (XMLNode node
= root
.childrenNode(); node
; node
= node
.next()) {
444 if (node
.is("speed")) {
445 ASSERT(node
.doubleContent(&baseSpeed
));
446 setSpeed(speedMul
); // Calculate speed from tile size.
447 } else if (node
.is("sprite")) {
448 ASSERT(processSprite(node
.childrenNode()));
449 } else if (node
.is("sounds")) {
450 ASSERT(processSounds(node
.childrenNode()));
451 } else if (node
.is("scripts")) {
452 ASSERT(processScripts(node
.childrenNode()));
458 bool Entity::processSprite(XMLNode node
)
460 Resourcer
* rc
= Resourcer::instance();
462 for (; node
; node
= node
.next()) {
463 if (node
.is("sheet")) {
464 std::string imageSheet
= node
.content();
465 ASSERT(node
.intAttr("tilewidth", &imgw
) &&
466 node
.intAttr("tileheight", &imgh
));
467 ASSERT(rc
->getTiledImage(tiles
, imageSheet
,
469 } else if (node
.is("phases")) {
470 ASSERT(processPhases(node
.childrenNode(), tiles
));
476 bool Entity::processPhases(XMLNode node
, const TiledImage
& tiles
)
478 for (; node
; node
= node
.next())
479 if (node
.is("phase"))
480 ASSERT(processPhase(node
, tiles
));
484 bool Entity::processPhase(const XMLNode node
, const TiledImage
& tiles
)
486 /* Each phase requires a 'name'. Additionally,
487 * one of either 'pos' or 'speed' is needed.
488 * If speed is used, we have sub-elements. We
489 * can't have both pos and speed.
491 const std::string name
= node
.attr("name");
493 Log::err(descriptor
, "<phase> name attribute is empty");
497 const std::string posStr
= node
.attr("pos");
498 const std::string speedStr
= node
.attr("speed");
500 if (posStr
.size() && speedStr
.size()) {
501 Log::err(descriptor
, "pos and speed attributes in "
502 "phase element are mutually exclusive");
504 } else if (posStr
.empty() && speedStr
.empty()) {
505 Log::err(descriptor
, "must have pos or speed attribute "
512 ASSERT(node
.intAttr("pos", &pos
));
513 if (pos
< 0 || (int)tiles
.size() < pos
) {
515 "<phase></phase> index out of bounds");
518 phases
[name
].addFrame(tiles
[pos
]);
522 ASSERT(node
.intAttr("speed", &speed
));
524 int len
= (int)(1000.0/speed
);
525 phases
[name
].setFrameLen(len
);
526 ASSERT(processMembers(node
.childrenNode(),
527 phases
[name
], tiles
));
533 bool Entity::processMembers(XMLNode node
, Animation
& anim
,
534 const TiledImage
& tiles
)
536 for (; node
; node
= node
.next())
537 if (node
.is("member"))
538 ASSERT(processMember(node
, anim
, tiles
));
542 bool Entity::processMember(const XMLNode node
, Animation
& anim
,
543 const TiledImage
& tiles
)
546 ASSERT(node
.intAttr("pos", &pos
));
547 if (pos
< 0 || (int)tiles
.size() < pos
) {
548 Log::err(descriptor
, "<member></member> index out of bounds");
551 anim
.addFrame(tiles
[pos
]);
555 bool Entity::processSounds(XMLNode node
)
557 for (; node
; node
= node
.next())
558 if (node
.is("sound"))
559 ASSERT(processSound(node
));
563 bool Entity::processSound(const XMLNode node
)
565 const std::string name
= node
.attr("name");
566 const std::string filename
= node
.content();
568 Log::err(descriptor
, "<sound> name attribute is empty");
570 } else if (filename
.empty()) {
571 Log::err(descriptor
, "<sound></sound> is empty");
575 Resourcer
* rc
= Resourcer::instance();
576 SampleRef s
= rc
->getSample(filename
);
582 bool Entity::processScripts(XMLNode node
)
584 for (; node
; node
= node
.next())
585 if (node
.is("script"))
586 ASSERT(processScript(node
));
590 bool Entity::processScript(const XMLNode node
)
592 const std::string trigger
= node
.attr("trigger");
593 const std::string filename
= node
.content();
594 if (trigger
.empty()) {
595 Log::err(descriptor
, "<script> trigger attribute is empty");
597 } else if (filename
.empty()) {
598 Log::err(descriptor
, "<script></script> is empty");
602 Resourcer
* rc
= Resourcer::instance();
603 if (rc
->resourceExists(filename
)) {
604 scripts
[trigger
] = filename
;
609 std::string("script not found: ") + filename
);
617 boost::python::class_
<Entity
>("Entity", boost::python::no_init
)
618 .add_property("animation",
619 &Entity::getFacing
, &Entity::setPhase
)
620 .add_property("tile",
622 static_cast<Tile
& (Entity::*) ()> (&Entity::getTile
),
623 boost::python::return_value_policy
<
624 boost::python::reference_existing_object
628 .add_property("coords", &Entity::getTileCoords_vi
)
629 .add_property("speed", &Entity::getSpeed
, &Entity::setSpeed
)
630 .def("goto_random_tile", &Entity::gotoRandomTile
)