create Script class
[Tsunagari.git] / src / entity.cpp
blob1f708f2ffc34f6f856f9afe9989f09e026338d7a
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** entity.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <math.h>
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>
15 #include "area.h"
16 #include "config.h"
17 #include "entity.h"
18 #include "entity-lua.h"
19 #include "log.h"
20 #include "resourcer.h"
21 #include "script.h"
22 #include "window.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)
32 : rc(rc),
33 redraw(true),
34 moving(false),
35 speed(240.0 / 1000), // FIXME
36 area(area),
37 conf(conf)
39 c.x = c.y = c.z = 0;
42 Entity::~Entity()
46 bool Entity::init(const std::string& descriptor)
48 this->descriptor = descriptor;
49 if (!processDescriptor())
50 return false;
52 // Set an initial phase
53 phase = &phases.begin()->second;
54 return true;
57 void Entity::draw()
59 int millis = GameWindow::getWindow().time();
60 phase->updateFrame(millis);
61 phase->frame()->draw((double)c.x, (double)c.y, (double)0);
62 redraw = false;
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)
74 double angle = 0.0;
76 // Moving at an angle
77 if (x != 0 && y != 0) {
78 angle = atan((double)y / (double)x);
79 if (y < 0 && x < 0)
81 else if (y < 0 && x > 0)
82 angle += M_PI;
83 else if (y > 0 && x < 0)
84 angle += M_PI*2;
85 else if (y > 0 && x > 0)
86 angle += M_PI;
89 // Moving straight
90 else {
91 if (x < 0)
92 angle = 0;
93 else if (x > 0)
94 angle = M_PI;
95 else if (y < 0)
96 angle = M_PI_2;
97 else if (y > 0)
98 angle = 3*M_PI_2;
101 return angle;
104 void Entity::update(unsigned long dt)
106 if (conf->movemode == TILE && moving) {
107 redraw = true;
109 double destDist = Gosu::distance((double)c.x, (double)c.y,
110 (double)dest.x, (double)dest.y);
111 if (destDist < speed * (double)dt) {
112 c = dest;
113 moving = false;
114 postMove();
116 else {
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)
123 dx = 0.0;
124 if (-1e-10 < dy && dy < 1e-10)
125 dy = 0.0;
127 // Save state of partial pixels traveled in double
128 rx += dx * speed * (double)dt;
129 ry += dy * speed * (double)dt;
131 c.x = (long)rx;
132 c.y = (long)ry;
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) {
144 phase = newPhase;
145 redraw = true;
146 return true;
149 return false;
152 coord_t Entity::getCoordsByPixel() const
154 return c;
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)
166 c = coords;
167 redraw = true;
170 void Entity::setCoordsByTile(coord_t coords)
172 coord_t tileDim = area->getTileDimensions();
173 c = coords;
174 c.x *= tileDim.x;
175 c.y *= tileDim.y;
176 // XXX: set c.z when we have Z-buffers
177 redraw = true;
180 void Entity::moveByPixel(coord_t delta)
182 c.x += delta.x;
183 c.y += delta.y;
184 c.z += delta.z;
185 redraw = true;
188 void Entity::moveByTile(coord_t delta)
190 if (conf->movemode == TILE && moving)
191 // support queueing moves?
192 return;
194 coord_t newCoord = getCoordsByTile();
195 newCoord.x += delta.x;
196 newCoord.y += delta.y;
197 newCoord.z += delta.z;
199 // Can we move?
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);
206 setPhase(facing);
207 return;
210 // Move!
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
215 redraw = true;
217 preMove(delta);
219 if (conf->movemode == TURN) {
220 c.x = dest.x;
221 c.y = dest.y;
222 // XXX: set c.z when we have Z-buffers
223 postMove();
225 else if (conf->movemode == TILE) {
226 moving = true;
227 rx = (double)c.x;
228 ry = (double)c.y;
232 void Entity::setArea(Area* a)
234 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())
248 return it->second;
249 else
250 return SampleRef();
253 void Entity::calculateFacing(coord_t delta)
255 int x, y;
257 if (delta.x < 0)
258 x = 0;
259 else if (delta.x == 0)
260 x = 1;
261 else
262 x = 2;
264 if (delta.y < 0)
265 y = 0;
266 else if (delta.y == 0)
267 y = 1;
268 else
269 y = 2;
271 facing = facings[y][x];
274 void Entity::preMove(coord_t delta)
276 calculateFacing(delta);
277 if (conf->movemode == TURN)
278 setPhase(facing);
279 else
280 setPhase("moving " + facing);
283 void Entity::postMove()
285 if (conf->movemode != TURN)
286 setPhase(facing);
288 coord_t tile = getCoordsByTile();
289 Script script;
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");
303 if (!doc)
304 return false;
305 const xmlNode* root = xmlDocGetRootElement(doc.get()); // <entity>
306 if (!root)
307 return false;
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))
313 return false;
315 else if (!xmlStrncmp(node->name, BAD_CAST("sounds"), 7)) {
316 if (!processSounds(node))
317 return false;
320 return true;
323 bool Entity::processSprite(const xmlNode* sprite)
325 xmlChar* str;
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))
340 return false;
342 return true;
345 bool Entity::processPhases(const xmlNode* phases)
347 TiledImage tiles;
348 if (!rc->getTiledImage(tiles, xml.sheet, (unsigned)xml.tileSize.x,
349 (unsigned)xml.tileSize.y, false))
350 return false;
352 for (xmlNode* phase = phases->xmlChildrenNode; phase != NULL;
353 phase = phase->next)
354 if (!xmlStrncmp(phase->name, BAD_CAST("phase"), 6)) // needed?
355 if (!processPhase(phase, tiles))
356 return false;
357 return true;
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");
377 return false;
379 if (!posStr && !speedStr) {
380 Log::err(descriptor, "must have pos or speed attribute "
381 "in element phase");
382 return false;
385 if (posStr) {
386 // atoi
387 const unsigned pos = (unsigned)atoi((const char*)posStr);
388 // FIXME: check for out of bounds
389 phases[name] = Animation(tiles[pos]);
391 else { // speedStr
392 // atoi
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))
403 return false;
406 return true;
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]);
416 return true;
419 bool Entity::processSounds(const xmlNode* sounds)
421 for (xmlNode* sound = sounds->xmlChildrenNode; sound != NULL;
422 sound = sound->next)
423 if (!xmlStrncmp(sound->name, BAD_CAST("sound"), 6)) // needed?
424 if (!processSound(sound))
425 return false;
426 return true;
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);
436 if (s)
437 sounds[name] = s;
438 else
439 Log::err(descriptor, std::string("sound ") +
440 filename + " not found");
441 return s;