1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
9 #include <boost/foreach.hpp>
10 #include <Gosu/Image.hpp>
11 #include <Gosu/Math.hpp>
12 #include <Gosu/Timing.hpp>
18 #include "resourcer.h"
22 #define ASSERT(x) if (!(x)) return false
24 static std::string facings
[][3] = {
25 {"up-left", "up", "up-right"},
26 {"left", "", "right"},
27 {"down-left", "down", "down-right"},
31 Entity::Entity(Resourcer
* rc
, Area
* area
, ClientValues
* conf
)
48 bool Entity::init(const std::string
& descriptor
)
50 this->descriptor
= descriptor
;
51 if (!processDescriptor())
54 // Set an initial phase.
55 phase
= &phases
.begin()->second
;
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
, destCoord
.y
- r
.y
);
147 double dx
= cos(angle
);
148 double dy
= -sin(angle
);
150 // Fix inaccurate trig functions. (Why do we have to do this!??)
151 if (-1e-10 < dx
&& dx
< 1e-10)
153 if (-1e-10 < dy
&& dy
< 1e-10)
156 // Save state of partial pixel travel in double.
157 r
.x
+= dx
* traveled
;
158 r
.y
+= dy
* traveled
;
162 void Entity::updateNoTile(unsigned long)
167 bool Entity::setPhase(const std::string
& name
)
169 boost::unordered_map
<std::string
, Animation
>::iterator it
;
170 it
= phases
.find(name
);
171 if (it
!= phases
.end()) {
172 Animation
* newPhase
= &it
->second
;
173 if (phase
!= newPhase
) {
182 rcoord
Entity::getPixelCoord() const
187 icoord
Entity::getTileCoords() const
189 ivec2 tileDim
= area
->getTileDimensions();
191 (int)r
.x
/ tileDim
.x
,
192 (int)r
.y
/ tileDim
.y
,
193 area
->depthIndex(r
.z
)
197 void Entity::setTileCoords(icoord coords
)
199 // FIXME: security: bounds check
201 const ivec2 tileDim
= area
->getTileDimensions();
203 coords
.x
* tileDim
.x
,
204 coords
.y
* tileDim
.y
,
209 void Entity::moveByTile(icoord delta
)
213 calculateFacing(delta
.x
, delta
.y
);
216 std::vector
<icoord
> tiles
= frontTiles();
217 BOOST_FOREACH(const icoord
& tile
, tiles
) {
225 void Entity::setArea(Area
* a
)
229 setSpeed(speedMul
); // Calculate new speed based on tile size.
232 void Entity::gotoRandomTile()
234 const icoord map
= area
->getDimensions();
238 pos
= icoord(rand() % map
.x
, rand() % map
.y
, 0);
239 tile
= &area
->getTile(pos
);
240 } while (tile
->hasFlag(nowalk
));
244 void Entity::setSpeed(double multiplier
)
246 speedMul
= multiplier
;
248 double tilesPerSecond
= area
->getTileDimensions().x
/ 1000.0;
249 speed
= baseSpeed
* speedMul
* tilesPerSecond
;
253 std::vector
<icoord
> Entity::frontTiles() const
255 std::vector
<icoord
> tiles
;
256 icoord normal
= getTileCoords();
257 normal
+= icoord(faceX
, faceY
, 0);
258 tiles
.push_back(normal
);
260 // If we are on a tile with the layermod property, we have access to
261 // tiles on the new layer, too.
262 const boost::optional
<int> layermod
= getTile().layermod
;
265 mod
.z
= area
->depthIndex(*layermod
);
266 tiles
.push_back(mod
);
271 void Entity::calcDoff()
273 // X-axis is centered on tile.
274 doff
.x
= (area
->getTileDimensions().x
- imgw
) / 2;
275 // Y-axis is aligned with bottom of tile.
276 doff
.y
= area
->getTileDimensions().y
- imgh
- 1;
279 Tile
& Entity::getTile() const
281 return area
->getTile(getTileCoords());
284 SampleRef
Entity::getSound(const std::string
& name
)
286 boost::unordered_map
<std::string
, SampleRef
>::iterator it
;
288 it
= sounds
.find(name
);
289 if (it
!= sounds
.end())
295 void Entity::calculateFacing(int x
, int y
)
311 facing
= facings
[faceY
+1][faceX
+1];
314 bool Entity::canMove(icoord dest
)
316 if (!area
->tileExists(dest
))
317 // The tile is off the map.
319 ivec2 tileDim
= area
->getTileDimensions();
323 area
->indexDepth(dest
.z
)
325 destTile
= &area
->getTile(dest
);
326 return !destTile
->hasFlag(nowalk
);
329 void Entity::preMove()
332 fromTile
= &getTile();
335 // Set z right away so that we're on-level with the square we're entering.
338 // Start moving animation.
339 switch (conf
->moveMode
) {
344 setPhase("moving " + facing
);
351 if (conf
->moveMode
== TURN
) {
352 // Movement is instantaneous.
358 void Entity::postMove()
362 // Stop moving animation.
363 if (conf
->moveMode
!= TURN
)
367 fromTile
->onLeaveScripts(rc
, this);
369 destTile
->onEnterScripts(rc
, this);
371 // TODO: move teleportation here
375 * moveArea(getDoor());
382 void Entity::tileExitScript()
384 const std::string
& name
= scripts
["tileexit"];
386 //std::string lines = rc->getText(name);
387 //pyExec(lines.c_str());
391 void Entity::tileEntryScript()
393 const std::string
& name
= scripts
["tileentry"];
395 //std::string lines = rc->getText(name);
396 //pyExec(lines.c_str());
402 * DESCRIPTOR CODE BELOW
405 bool Entity::processDescriptor()
407 XMLRef doc
= rc
->getXMLDoc(descriptor
, "entity.dtd");
410 const XMLNode root
= doc
->root(); // <entity>
414 for (XMLNode node
= root
.childrenNode(); node
; node
= node
.next()) {
415 if (node
.is("speed")) {
416 ASSERT(node
.doubleContent(&baseSpeed
));
417 setSpeed(speedMul
); // Calculate speed from tile size.
418 } else if (node
.is("sprite")) {
419 ASSERT(processSprite(node
.childrenNode()));
420 } else if (node
.is("sounds")) {
421 ASSERT(processSounds(node
.childrenNode()));
422 } else if (node
.is("scripts")) {
423 ASSERT(processScripts(node
.childrenNode()));
429 bool Entity::processSprite(XMLNode node
)
432 for (; node
; node
= node
.next()) {
433 if (node
.is("sheet")) {
434 std::string imageSheet
= node
.content();
435 ASSERT(node
.intAttr("tilewidth", &imgw
) &&
436 node
.intAttr("tileheight", &imgh
));
437 ASSERT(rc
->getTiledImage(tiles
, imageSheet
,
439 } else if (node
.is("phases")) {
440 ASSERT(processPhases(node
.childrenNode(), tiles
));
446 bool Entity::processPhases(XMLNode node
, const TiledImage
& tiles
)
448 for (; node
; node
= node
.next())
449 if (node
.is("phase"))
450 ASSERT(processPhase(node
, tiles
));
454 bool Entity::processPhase(const XMLNode node
, const TiledImage
& tiles
)
456 /* Each phase requires a 'name'. Additionally,
457 * one of either 'pos' or 'speed' is needed.
458 * If speed is used, we have sub-elements. We
459 * can't have both pos and speed.
461 const std::string name
= node
.attr("name");
463 Log::err(descriptor
, "<phase> name attribute is empty");
467 const std::string posStr
= node
.attr("pos");
468 const std::string speedStr
= node
.attr("speed");
470 if (posStr
.size() && speedStr
.size()) {
471 Log::err(descriptor
, "pos and speed attributes in "
472 "phase element are mutually exclusive");
474 } else if (posStr
.empty() && speedStr
.empty()) {
475 Log::err(descriptor
, "must have pos or speed attribute "
482 ASSERT(node
.intAttr("pos", &pos
));
483 if (pos
< 0 || (int)tiles
.size() < pos
) {
485 "<phase></phase> index out of bounds");
488 phases
[name
].addFrame(tiles
[pos
]);
492 ASSERT(node
.intAttr("speed", &speed
));
494 int len
= (int)(1000.0/speed
);
495 phases
[name
].setFrameLen(len
);
496 ASSERT(processMembers(node
.childrenNode(),
497 phases
[name
], tiles
));
503 bool Entity::processMembers(XMLNode node
, Animation
& anim
,
504 const TiledImage
& tiles
)
506 for (; node
; node
= node
.next())
507 if (node
.is("member"))
508 ASSERT(processMember(node
, anim
, tiles
));
512 bool Entity::processMember(const XMLNode node
, Animation
& anim
,
513 const TiledImage
& tiles
)
516 ASSERT(node
.intAttr("pos", &pos
));
517 if (pos
< 0 || (int)tiles
.size() < pos
) {
518 Log::err(descriptor
, "<member></member> index out of bounds");
521 anim
.addFrame(tiles
[pos
]);
525 bool Entity::processSounds(XMLNode node
)
527 for (; node
; node
= node
.next())
528 if (node
.is("sound"))
529 ASSERT(processSound(node
));
533 bool Entity::processSound(const XMLNode node
)
535 const std::string name
= node
.attr("name");
536 const std::string filename
= node
.content();
538 Log::err(descriptor
, "<sound> name attribute is empty");
540 } else if (filename
.empty()) {
541 Log::err(descriptor
, "<sound></sound> is empty");
545 SampleRef s
= rc
->getSample(filename
);
551 bool Entity::processScripts(XMLNode node
)
553 for (; node
; node
= node
.next())
554 if (node
.is("script"))
555 ASSERT(processScript(node
));
559 bool Entity::processScript(const XMLNode node
)
561 const std::string trigger
= node
.attr("trigger");
562 const std::string filename
= node
.content();
563 if (trigger
.empty()) {
564 Log::err(descriptor
, "<script> trigger attribute is empty");
566 } else if (filename
.empty()) {
567 Log::err(descriptor
, "<script></script> is empty");
571 if (rc
->resourceExists(filename
)) {
572 scripts
[trigger
] = filename
;
576 Log::err(descriptor
, std::string("script not found: ") + filename
);