scripts load from world
[Tsunagari.git] / src / entity.cpp
bloba4056b5496ad385f1eda25056efa652d1bc37c9a
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::gotoRandomTile()
239 coord_t map = area->getDimensions();
240 coord_t pos;
241 Area::Tile* tile;
242 do {
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())
256 return it->second;
257 else
258 return SampleRef();
261 void Entity::calculateFacing(coord_t delta)
263 int x, y;
265 if (delta.x < 0)
266 x = 0;
267 else if (delta.x == 0)
268 x = 1;
269 else
270 x = 2;
272 if (delta.y < 0)
273 y = 0;
274 else if (delta.y == 0)
275 y = 1;
276 else
277 y = 2;
279 facing = facings[y][x];
282 void Entity::preMove(coord_t delta)
284 calculateFacing(delta);
285 if (conf->movemode == TURN)
286 setPhase(facing);
287 else
288 setPhase("moving " + facing);
291 void Entity::postMove()
293 if (conf->movemode != TURN)
294 setPhase(facing);
295 postMoveHook();
298 void Entity::postMoveHook()
300 coord_t tile = getCoordsByTile();
301 Script script;
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(rc, "postMove.lua");
310 * Try to load in descriptor.
312 bool Entity::processDescriptor()
314 XMLDocRef doc = rc->getXMLDoc(descriptor, "entity.dtd");
315 if (!doc)
316 return false;
317 const xmlNode* root = xmlDocGetRootElement(doc.get()); // <entity>
318 if (!root)
319 return false;
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))
325 return false;
327 else if (!xmlStrncmp(node->name, BAD_CAST("sounds"), 7)) {
328 if (!processSounds(node))
329 return false;
332 return true;
335 bool Entity::processSprite(const xmlNode* sprite)
337 xmlChar* str;
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))
352 return false;
354 return true;
357 bool Entity::processPhases(const xmlNode* phases)
359 TiledImage tiles;
360 if (!rc->getTiledImage(tiles, xml.sheet, (unsigned)xml.tileSize.x,
361 (unsigned)xml.tileSize.y, false))
362 return false;
364 for (xmlNode* phase = phases->xmlChildrenNode; phase != NULL;
365 phase = phase->next)
366 if (!xmlStrncmp(phase->name, BAD_CAST("phase"), 6)) // needed?
367 if (!processPhase(phase, tiles))
368 return false;
369 return true;
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");
389 return false;
391 if (!posStr && !speedStr) {
392 Log::err(descriptor, "must have pos or speed attribute "
393 "in element phase");
394 return false;
397 if (posStr) {
398 // atoi
399 const unsigned pos = (unsigned)atoi((const char*)posStr);
400 // FIXME: check for out of bounds
401 phases[name] = Animation(tiles[pos]);
403 else { // speedStr
404 // atoi
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))
415 return false;
418 return true;
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]);
428 return true;
431 bool Entity::processSounds(const xmlNode* sounds)
433 for (xmlNode* sound = sounds->xmlChildrenNode; sound != NULL;
434 sound = sound->next)
435 if (!xmlStrncmp(sound->name, BAD_CAST("sound"), 6)) // needed?
436 if (!processSound(sound))
437 return false;
438 return true;
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);
448 if (s)
449 sounds[name] = s;
450 else
451 Log::err(descriptor, std::string("sound ") +
452 filename + " not found");
453 return s;