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::gotoUpperLeft()
239 setCoordsByTile(coord(1, 1, 0));
242 SampleRef
Entity::getSound(const std::string
& name
)
244 boost::unordered_map
<std::string
, SampleRef
>::iterator it
;
246 it
= sounds
.find(name
);
247 if (it
!= sounds
.end())
253 void Entity::calculateFacing(coord_t delta
)
259 else if (delta
.x
== 0)
266 else if (delta
.y
== 0)
271 facing
= facings
[y
][x
];
274 void Entity::preMove(coord_t delta
)
276 calculateFacing(delta
);
277 if (conf
->movemode
== TURN
)
280 setPhase("moving " + facing
);
283 void Entity::postMove()
285 if (conf
->movemode
!= TURN
)
288 coord_t tile
= getCoordsByTile();
290 script
.addFn("gotoUpperLeft", lua_Entity_gotoUpperLeft
);
291 script
.addInt("x", tile
.x
);
292 script
.addInt("y", tile
.y
);
293 script
.addData("entity", this);
294 script
.run("postMove.lua");
298 * Try to load in descriptor.
300 bool Entity::processDescriptor()
302 XMLDocRef doc
= rc
->getXMLDoc(descriptor
, "entity.dtd");
305 const xmlNode
* root
= xmlDocGetRootElement(doc
.get()); // <entity>
308 xmlNode
* node
= root
->xmlChildrenNode
; // children of <entity>
310 for (; node
!= NULL
; node
= node
->next
) {
311 if (!xmlStrncmp(node
->name
, BAD_CAST("sprite"), 6)) {
312 if (!processSprite(node
))
315 else if (!xmlStrncmp(node
->name
, BAD_CAST("sounds"), 7)) {
316 if (!processSounds(node
))
323 bool Entity::processSprite(const xmlNode
* sprite
)
326 for (xmlNode
* child
= sprite
->xmlChildrenNode
; child
!= NULL
;
327 child
= child
->next
) {
328 if (!xmlStrncmp(child
->name
, BAD_CAST("sheet"), 6)) {
329 str
= xmlNodeGetContent(child
);
330 xml
.sheet
= (char*)str
;
332 str
= xmlGetProp(child
, BAD_CAST("tilewidth"));
333 xml
.tileSize
.x
= atol((char*)str
); // atol
335 str
= xmlGetProp(child
, BAD_CAST("tileheight"));
336 xml
.tileSize
.y
= atol((char*)str
); // atol
338 else if (!xmlStrncmp(child
->name
, BAD_CAST("phases"), 7) &&
339 !processPhases(child
))
345 bool Entity::processPhases(const xmlNode
* phases
)
348 if (!rc
->getTiledImage(tiles
, xml
.sheet
, (unsigned)xml
.tileSize
.x
,
349 (unsigned)xml
.tileSize
.y
, false))
352 for (xmlNode
* phase
= phases
->xmlChildrenNode
; phase
!= NULL
;
354 if (!xmlStrncmp(phase
->name
, BAD_CAST("phase"), 6)) // needed?
355 if (!processPhase(phase
, tiles
))
360 bool Entity::processPhase(xmlNode
* phase
, const TiledImage
& tiles
)
362 /* Each phase requires a 'name'. Additionally,
363 * one of either 'pos' or 'speed' is needed.
364 * If speed is used, we have sub-elements. We
365 * can't have both pos and speed.
367 const std::string name
= (char*)xmlGetProp(phase
, BAD_CAST("name"));
369 const xmlChar
* posStr
= xmlGetProp(phase
, BAD_CAST("pos"));
370 const xmlChar
* speedStr
= xmlGetProp(phase
, BAD_CAST("speed"));
372 // FIXME: check name + pos | speed for 0 length
374 if (posStr
&& speedStr
) {
375 Log::err(descriptor
, "pos and speed attributes in "
376 "element phase are mutually exclusive");
379 if (!posStr
&& !speedStr
) {
380 Log::err(descriptor
, "must have pos or speed attribute "
387 const unsigned pos
= (unsigned)atoi((const char*)posStr
);
388 // FIXME: check for out of bounds
389 phases
[name
] = Animation(tiles
[pos
]);
393 const double speed
= (unsigned)atof((const char*)speedStr
);
394 // FIXME: check for out of bounds
396 phases
[name
] = Animation();
397 int len
= (int)(1000.0/speed
);
398 phases
[name
].setFrameLen(len
);
399 for (xmlNode
* member
= phase
->xmlChildrenNode
; member
!= NULL
;
400 member
= member
->next
)
401 if (!xmlStrncmp(member
->name
, BAD_CAST("member"), 7)) // needed?
402 if (!processMember(member
, phases
[name
], tiles
))
409 bool Entity::processMember(xmlNode
* phase
, Animation
& anim
,
410 const TiledImage
& tiles
)
412 const xmlChar
* posStr
= xmlGetProp(phase
, BAD_CAST("pos"));
413 const unsigned pos
= (unsigned)atoi((const char*)posStr
); // atoi
414 // FIXME: check for out of bounds
415 anim
.addFrame(tiles
[pos
]);
419 bool Entity::processSounds(const xmlNode
* sounds
)
421 for (xmlNode
* sound
= sounds
->xmlChildrenNode
; sound
!= NULL
;
423 if (!xmlStrncmp(sound
->name
, BAD_CAST("sound"), 6)) // needed?
424 if (!processSound(sound
))
429 bool Entity::processSound(xmlNode
* sound
)
431 const std::string name
= (char*)xmlGetProp(sound
, BAD_CAST("name"));
432 const std::string filename
= (char*)xmlNodeGetContent(sound
);
433 // FIXME: check name, filename for 0 length
435 SampleRef s
= rc
->getSample(filename
);
439 Log::err(descriptor
, std::string("sound ") +
440 filename
+ " not found");