1 /******************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
9 #include <Gosu/Image.hpp>
10 #include <Gosu/Math.hpp>
11 #include <Gosu/Timing.hpp>
12 #include <libxml/parser.h>
13 #include <libxml/tree.h>
18 #include "entity-lua.h"
20 #include "resourcer.h"
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
)
35 speed(240.0 / 1000), // FIXME
46 bool Entity::init(const std::string
& descriptor
)
48 this->descriptor
= descriptor
;
49 if (!processDescriptor())
52 // Set an initial phase
53 phase
= &phases
.begin()->second
;
59 int millis
= GameWindow::getWindow().time();
60 phase
->updateFrame(millis
);
61 phase
->frame()->draw((double)c
.x
, (double)c
.y
, (double)0);
65 bool Entity::needsRedraw() const
67 int millis
= GameWindow::getWindow().time();
68 return redraw
|| phase
->needsRedraw(millis
);
72 static double angleFromXY(long x
, long y
)
77 if (x
!= 0 && y
!= 0) {
78 angle
= atan((double)y
/ (double)x
);
81 else if (y
< 0 && x
> 0)
83 else if (y
> 0 && x
< 0)
85 else if (y
> 0 && x
> 0)
104 void Entity::update(unsigned long dt
)
106 if (conf
->movemode
== TILE
&& moving
) {
109 double destDist
= Gosu::distance((double)c
.x
, (double)c
.y
,
110 (double)dest
.x
, (double)dest
.y
);
111 if (destDist
< speed
* (double)dt
) {
117 double angle
= angleFromXY(c
.x
- dest
.x
, dest
.y
- c
.y
);
118 double dx
= cos(angle
);
119 double dy
= -sin(angle
);
121 // Fix inaccurate trig functions
122 if (-1e-10 < dx
&& dx
< 1e-10)
124 if (-1e-10 < dy
&& dy
< 1e-10)
127 // Save state of partial pixels traveled in double
128 rx
+= dx
* speed
* (double)dt
;
129 ry
+= dy
* speed
* (double)dt
;
137 bool Entity::setPhase(const std::string
& name
)
139 boost::unordered_map
<std::string
, Animation
>::iterator it
;
140 it
= phases
.find(name
);
141 if (it
!= phases
.end()) {
142 Animation
* newPhase
= &it
->second
;
143 if (phase
!= newPhase
) {
152 coord_t
Entity::getCoordsByPixel() const
157 coord_t
Entity::getCoordsByTile() const
159 coord_t tileDim
= area
->getTileDimensions();
160 // XXX: revisit when we have Z-buffers
161 return coord(c
.x
/ tileDim
.x
, c
.y
/ tileDim
.y
, c
.z
);
164 void Entity::setCoordsByPixel(coord_t coords
)
170 void Entity::setCoordsByTile(coord_t coords
)
172 coord_t tileDim
= area
->getTileDimensions();
176 // XXX: set c.z when we have Z-buffers
180 void Entity::moveByPixel(coord_t delta
)
188 void Entity::moveByTile(coord_t delta
)
190 if (conf
->movemode
== TILE
&& moving
)
191 // support queueing moves?
194 coord_t newCoord
= getCoordsByTile();
195 newCoord
.x
+= delta
.x
;
196 newCoord
.y
+= delta
.y
;
197 newCoord
.z
+= delta
.z
;
200 const Area::Tile
& tile
= area
->getTile(newCoord
);
201 if ((tile
.flags
& Area::nowalk
) != 0 ||
202 (tile
.type
->flags
& Area::nowalk
) != 0) {
203 // The tile we're trying to move onto is set as nowalk.
204 // Turn to face the direction, but don't move.
205 calculateFacing(delta
);
211 const coord_t tileDim
= area
->getTileDimensions();
212 dest
.x
= c
.x
+ delta
.x
* tileDim
.x
;
213 dest
.y
= c
.y
+ delta
.y
* tileDim
.y
;
214 dest
.z
= 0; // XXX: set dest.z when we have Z-buffers
219 if (conf
->movemode
== TURN
) {
222 // XXX: set c.z when we have Z-buffers
225 else if (conf
->movemode
== TILE
) {
232 void Entity::setArea(Area
* a
)
237 void Entity::gotoRandomTile()
239 coord_t map
= area
->getDimensions();
243 pos
= coord(rand() % map
.x
, rand() % map
.y
, 0);
244 tile
= &area
->getTile(pos
);
245 } while (((tile
->flags
& Area::nowalk
) |
246 (tile
->type
->flags
& Area::nowalk
)) != 0);
247 setCoordsByTile(pos
);
250 SampleRef
Entity::getSound(const std::string
& name
)
252 boost::unordered_map
<std::string
, SampleRef
>::iterator it
;
254 it
= sounds
.find(name
);
255 if (it
!= sounds
.end())
261 void Entity::calculateFacing(coord_t delta
)
267 else if (delta
.x
== 0)
274 else if (delta
.y
== 0)
279 facing
= facings
[y
][x
];
282 void Entity::preMove(coord_t delta
)
284 calculateFacing(delta
);
285 if (conf
->movemode
== TURN
)
288 setPhase("moving " + facing
);
291 void Entity::postMove()
293 if (conf
->movemode
!= TURN
)
298 void Entity::postMoveHook()
300 coord_t tile
= getCoordsByTile();
302 script
.bindEntity("entity", this);
303 script
.bindObjFn("entity", "gotoRandomTile", lua_Entity_gotoRandomTile
);
304 script
.bindInt("x", tile
.x
);
305 script
.bindInt("y", tile
.y
);
306 script
.run("postMove.lua");
310 * Try to load in descriptor.
312 bool Entity::processDescriptor()
314 XMLDocRef doc
= rc
->getXMLDoc(descriptor
, "entity.dtd");
317 const xmlNode
* root
= xmlDocGetRootElement(doc
.get()); // <entity>
320 xmlNode
* node
= root
->xmlChildrenNode
; // children of <entity>
322 for (; node
!= NULL
; node
= node
->next
) {
323 if (!xmlStrncmp(node
->name
, BAD_CAST("sprite"), 6)) {
324 if (!processSprite(node
))
327 else if (!xmlStrncmp(node
->name
, BAD_CAST("sounds"), 7)) {
328 if (!processSounds(node
))
335 bool Entity::processSprite(const xmlNode
* sprite
)
338 for (xmlNode
* child
= sprite
->xmlChildrenNode
; child
!= NULL
;
339 child
= child
->next
) {
340 if (!xmlStrncmp(child
->name
, BAD_CAST("sheet"), 6)) {
341 str
= xmlNodeGetContent(child
);
342 xml
.sheet
= (char*)str
;
344 str
= xmlGetProp(child
, BAD_CAST("tilewidth"));
345 xml
.tileSize
.x
= atol((char*)str
); // atol
347 str
= xmlGetProp(child
, BAD_CAST("tileheight"));
348 xml
.tileSize
.y
= atol((char*)str
); // atol
350 else if (!xmlStrncmp(child
->name
, BAD_CAST("phases"), 7) &&
351 !processPhases(child
))
357 bool Entity::processPhases(const xmlNode
* phases
)
360 if (!rc
->getTiledImage(tiles
, xml
.sheet
, (unsigned)xml
.tileSize
.x
,
361 (unsigned)xml
.tileSize
.y
, false))
364 for (xmlNode
* phase
= phases
->xmlChildrenNode
; phase
!= NULL
;
366 if (!xmlStrncmp(phase
->name
, BAD_CAST("phase"), 6)) // needed?
367 if (!processPhase(phase
, tiles
))
372 bool Entity::processPhase(xmlNode
* phase
, const TiledImage
& tiles
)
374 /* Each phase requires a 'name'. Additionally,
375 * one of either 'pos' or 'speed' is needed.
376 * If speed is used, we have sub-elements. We
377 * can't have both pos and speed.
379 const std::string name
= (char*)xmlGetProp(phase
, BAD_CAST("name"));
381 const xmlChar
* posStr
= xmlGetProp(phase
, BAD_CAST("pos"));
382 const xmlChar
* speedStr
= xmlGetProp(phase
, BAD_CAST("speed"));
384 // FIXME: check name + pos | speed for 0 length
386 if (posStr
&& speedStr
) {
387 Log::err(descriptor
, "pos and speed attributes in "
388 "element phase are mutually exclusive");
391 if (!posStr
&& !speedStr
) {
392 Log::err(descriptor
, "must have pos or speed attribute "
399 const unsigned pos
= (unsigned)atoi((const char*)posStr
);
400 // FIXME: check for out of bounds
401 phases
[name
] = Animation(tiles
[pos
]);
405 const double speed
= (unsigned)atof((const char*)speedStr
);
406 // FIXME: check for out of bounds
408 phases
[name
] = Animation();
409 int len
= (int)(1000.0/speed
);
410 phases
[name
].setFrameLen(len
);
411 for (xmlNode
* member
= phase
->xmlChildrenNode
; member
!= NULL
;
412 member
= member
->next
)
413 if (!xmlStrncmp(member
->name
, BAD_CAST("member"), 7)) // needed?
414 if (!processMember(member
, phases
[name
], tiles
))
421 bool Entity::processMember(xmlNode
* phase
, Animation
& anim
,
422 const TiledImage
& tiles
)
424 const xmlChar
* posStr
= xmlGetProp(phase
, BAD_CAST("pos"));
425 const unsigned pos
= (unsigned)atoi((const char*)posStr
); // atoi
426 // FIXME: check for out of bounds
427 anim
.addFrame(tiles
[pos
]);
431 bool Entity::processSounds(const xmlNode
* sounds
)
433 for (xmlNode
* sound
= sounds
->xmlChildrenNode
; sound
!= NULL
;
435 if (!xmlStrncmp(sound
->name
, BAD_CAST("sound"), 6)) // needed?
436 if (!processSound(sound
))
441 bool Entity::processSound(xmlNode
* sound
)
443 const std::string name
= (char*)xmlGetProp(sound
, BAD_CAST("name"));
444 const std::string filename
= (char*)xmlNodeGetContent(sound
);
445 // FIXME: check name, filename for 0 length
447 SampleRef s
= rc
->getSample(filename
);
451 Log::err(descriptor
, std::string("sound ") +
452 filename
+ " not found");