remove +x from source
[Tsunagari.git] / src / entity.cpp
bloba6c3fbccdac9b4ffd5b37e4bb3e07ee88da4b04a
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** entity.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 #include <math.h>
9 #include <boost/algorithm/string.hpp> // for iequals
10 #include <boost/foreach.hpp>
11 #include <Gosu/Image.hpp>
12 #include <Gosu/Math.hpp>
13 #include <Gosu/Timing.hpp>
15 #include "area.h"
16 #include "config.h"
17 #include "entity.h"
18 #include "log.h"
19 #include "python.h"
20 #include "resourcer.h"
21 #include "window.h"
22 #include "xml.h"
24 #define ASSERT(x) if (!(x)) return false
26 static std::string directions[][3] = {
27 {"up-left", "up", "up-right"},
28 {"left", "", "right"},
29 {"down-left", "down", "down-right"},
33 Entity::Entity()
34 : redraw(true),
35 area(NULL),
36 r(0.0, 0.0, 0.0),
37 frozen(false),
38 speedMul(1.0),
39 moving(false),
40 stillMoving(false),
41 nowalkFlags(TILE_NOWALK | TILE_NOWALK_NPC),
42 nowalkExempt(0),
43 phase(NULL),
44 phaseName("")
48 Entity::~Entity()
50 pythonSetGlobal("Area", area);
51 pythonSetGlobal("Entity", this);
52 deleteScript.invoke();
55 bool Entity::init(const std::string& descriptor)
57 this->descriptor = descriptor;
58 return processDescriptor();
61 void Entity::destroy()
63 leaveTile();
64 if (area) {
65 erase();
66 area->requestRedraw();
68 area = NULL;
69 delete this;
72 void Entity::draw()
74 int now = GameWindow::instance().time();
75 phase->frame(now)->draw(doff.x + r.x, doff.y + r.y, r.z);
76 redraw = false;
79 bool Entity::needsRedraw() const
81 int now = GameWindow::instance().time();
82 return redraw || phase->needsRedraw(now);
86 void Entity::tick(unsigned long dt)
88 runTickScript();
89 switch (conf.moveMode) {
90 case TURN:
91 tickTurn(dt);
92 break;
93 case TILE:
94 tickTile(dt);
95 break;
96 case NOTILE:
97 tickNoTile(dt);
98 break;
102 void Entity::tickTurn(unsigned long)
104 // FIXME Characters (!!) don't do anything in TILE mode.
107 void Entity::tickTile(unsigned long dt)
109 if (!moving)
110 return;
112 redraw = true;
113 double traveled = speed * (double)dt;
114 double destDist = Gosu::distance(r.x, r.y, destCoord.x, destCoord.y);
115 if (destDist <= traveled) {
116 r = destCoord;
117 moving = false;
118 postMove();
119 if (moving) {
120 // Time rollover.
121 double perc = 1.0 - destDist/traveled;
122 unsigned long remt = (unsigned long)(perc * (double)dt);
123 tick(remt);
126 else {
127 double angle = atan2(destCoord.y - r.y, destCoord.x - r.x);
128 double dx = cos(angle);
129 double dy = sin(angle);
131 r.x += dx * traveled;
132 r.y += dy * traveled;
136 void Entity::tickNoTile(unsigned long)
138 // TODO
141 void Entity::turn()
143 runTurnScript();
146 const std::string Entity::getFacing() const
148 return directionStr(facing);
151 bool Entity::setPhase(const std::string& name)
153 AnimationMap::iterator it;
154 it = phases.find(name);
155 if (it == phases.end()) {
156 Log::err(descriptor, "phase '" + name + "' not found");
157 return false;
159 Animation* newPhase = &it->second;
160 if (phase != newPhase) {
161 int now = GameWindow::instance().time();
162 phase = newPhase;
163 phase->startOver(now);
164 phaseName = name;
165 redraw = true;
166 return true;
168 return false;
171 std::string Entity::getPhase() const
173 return phaseName;
176 rcoord Entity::getPixelCoord() const
178 return r;
181 icoord Entity::getTileCoords_i() const
183 return area->virt2phys(r);
186 vicoord Entity::getTileCoords_vi() const
188 return area->virt2virt(r);
191 void Entity::setTileCoords(int x, int y)
193 leaveTile();
194 vicoord virt(x, y, r.z);
195 redraw = true;
196 r = area->virt2virt(virt);
197 enterTile();
200 void Entity::setTileCoords(int x, int y, double z)
202 leaveTile();
203 vicoord virt(x, y, z);
204 redraw = true;
205 r = area->virt2virt(virt);
206 enterTile();
209 void Entity::setTileCoords(icoord phys)
211 leaveTile();
212 redraw = true;
213 r = area->phys2virt_r(phys);
214 enterTile();
217 void Entity::setTileCoords(vicoord virt)
219 leaveTile();
220 redraw = true;
221 r = area->virt2virt(virt);
222 enterTile();
225 void Entity::setTileCoords(rcoord virt)
227 leaveTile();
228 redraw = true;
229 r = virt;
230 enterTile();
233 void Entity::teleport(int, int)
235 throw "pure virtual function";
238 icoord Entity::moveDest(ivec2 facing)
240 Tile* tile = getTile();
241 icoord here = getTileCoords_i();
243 if (tile)
244 // Handle layermod.
245 return tile->moveDest(here, facing);
246 else
247 return here + icoord(facing.x, facing.y, 0);
250 // Python API.
251 vicoord Entity::moveDest(Tile* t, int dx, int dy)
253 icoord here = getTileCoords_i();
254 icoord dest;
256 if (t) {
257 // Handle layermod.
258 dest = t->moveDest(here, ivec2(dx, dy));
259 return t->area->phys2virt_vi(dest);
261 else {
262 dest = here + icoord(dx, dy, 0);
263 return area->phys2virt_vi(dest);
267 bool Entity::isMoving() const
269 return moving || stillMoving;
272 void Entity::moveByTile(int x, int y)
274 moveByTile(ivec2(x, y));
277 void Entity::moveByTile(ivec2 delta)
279 if (moving)
280 return;
281 setFacing(delta);
283 if (canMove(moveDest(facing)))
284 preMove();
285 else
286 setPhase(directionStr(facing));
289 void Entity::move(int, int)
291 throw "pure virtual function";
294 Area* Entity::getArea()
296 return area;
299 void Entity::setArea(Area* a)
301 leaveTile();
302 area = a;
303 calcDoff();
304 setSpeed(speedMul); // Calculate new speed based on tile size.
305 enterTile();
308 double Entity::getSpeed() const
310 return speedMul;
313 void Entity::setSpeed(double multiplier)
315 speedMul = multiplier;
316 if (area) {
317 double tilesPerSecond = area->getTileDimensions().x / 1000.0;
318 speed = baseSpeed * speedMul * tilesPerSecond;
322 Tile* Entity::getTile() const
324 return area ? area->getTile(r) : NULL;
327 Tile* Entity::getTile()
329 return area ? area->getTile(r) : NULL;
332 void Entity::setFrozen(bool b)
334 frozen = b;
337 bool Entity::getFrozen()
339 return frozen;
342 FlagManip Entity::exemptManip()
344 return FlagManip(&nowalkExempt);
347 void Entity::erase()
349 throw "pure virtual function";
352 void Entity::calcDoff()
354 // X-axis is centered on tile.
355 doff.x = (area->getTileDimensions().x - imgw) / 2;
356 // Y-axis is aligned with bottom of tile.
357 doff.y = area->getTileDimensions().y - imgh - 1;
360 SampleRef Entity::getSound(const std::string& name) const
362 SampleMap::const_iterator it;
363 it = sounds.find(name);
364 if (it != sounds.end())
365 return it->second;
366 else
367 return SampleRef();
370 ivec2 Entity::setFacing(ivec2 facing)
372 this->facing = ivec2(
373 Gosu::clamp(facing.x, -1, 1),
374 Gosu::clamp(facing.y, -1, 1)
376 return this->facing;
379 const std::string& Entity::directionStr(ivec2 facing) const
381 return directions[facing.y+1][facing.x+1];
384 bool Entity::canMove(int x, int y, double z)
386 vicoord virt(x, y, z);
387 return canMove(area->virt2phys(virt));
390 bool Entity::canMove(icoord dest)
392 icoord dxyz = dest - getTileCoords_i();
393 ivec2 dxy(dxyz.x, dxyz.y);
395 Tile* curTile = getTile();
396 this->destTile = area->getTile(dest);
397 this->destCoord = area->phys2virt_r(dest);
399 if ((curTile && curTile->exitAt(dxy)) ||
400 (destTile && destTile->exits[EXIT_NORMAL])) {
401 // We can always take exits as long as we can take exits.
402 // (Even if they would cause us to be out of bounds.)
403 if (nowalkExempt & TILE_NOWALK_EXIT)
404 return true;
407 bool inBounds = area->inBounds(dest);
408 if (destTile && inBounds) {
409 // Tile is inside map. Can we move?
410 if (nowalked(*destTile))
411 return false;
412 if (destTile->entCnt)
413 // Space is occupied by another Entity.
414 return false;
416 return true;
419 // The tile is legitimately off the map.
420 return nowalkExempt & TILE_NOWALK_AREA_BOUND;
423 bool Entity::canMove(vicoord dest)
425 return canMove(area->virt2phys(dest));
428 bool Entity::nowalked(Tile& t)
430 unsigned flags = nowalkFlags & ~nowalkExempt;
431 return t.hasFlag(flags);
434 void Entity::preMove()
436 fromCoord = r;
437 fromTile = getTile();
439 rcoord d = destCoord - fromCoord;
440 deltaCoord = area->virt2virt(d);
442 moving = true;
444 // Start moving animation.
445 switch (conf.moveMode) {
446 case TURN:
447 break;
448 case TILE:
449 case NOTILE:
450 setPhase("moving " + getFacing());
451 break;
454 // Process triggers.
455 runTileExitScript();
456 if (fromTile)
457 fromTile->runLeaveScript(this);
459 // Modify tile's entity count.
460 leaveTile();
461 enterTile(destTile);
463 SampleRef step = getSound("step");
464 if (step)
465 step->play();
467 // Set z right away so that we're on-level with the square we're
468 // entering.
469 r.z = destCoord.z;
471 if (conf.moveMode == TURN) {
472 // Movement is instantaneous.
473 redraw = true;
474 r = destCoord;
475 postMove();
477 else {
478 // Movement happens over time. See tickTile().
482 void Entity::postMove()
484 moving = false;
486 if (destTile) {
487 boost::optional<double> layermod = destTile->layermods[EXIT_NORMAL];
488 if (layermod)
489 r.z = *layermod;
492 // Stop moving animation.
493 if (!stillMoving)
494 setPhase(getFacing());
496 // Process triggers.
497 if (destTile)
498 destTile->runEnterScript(this);
500 runTileEntryScript();
502 // TODO: move teleportation here
504 * if (onExit()) {
505 * leaveTile();
506 * moveArea(getExit());
507 * postMoveScript();
508 * enterTile();
513 void Entity::leaveTile()
515 Tile* t = getTile();
516 if (t)
517 t->entCnt--;
520 void Entity::enterTile()
522 Tile* t = getTile();
523 if (t)
524 enterTile(getTile());
527 void Entity::enterTile(Tile* t)
529 if (t)
530 t->entCnt++;
533 void Entity::runTickScript()
535 pythonSetGlobal("Area", area);
536 pythonSetGlobal("Entity", this);
537 pythonSetGlobal("Tile", getTile());
538 tickScript.invoke();
541 void Entity::runTurnScript()
543 pythonSetGlobal("Area", area);
544 pythonSetGlobal("Entity", this);
545 pythonSetGlobal("Tile", getTile());
546 turnScript.invoke();
549 void Entity::runTileExitScript()
551 pythonSetGlobal("Area", area);
552 pythonSetGlobal("Entity", this);
553 pythonSetGlobal("Tile", getTile());
554 tileExitScript.invoke();
557 void Entity::runTileEntryScript()
559 pythonSetGlobal("Area", area);
560 pythonSetGlobal("Entity", this);
561 pythonSetGlobal("Tile", getTile());
562 tileEntryScript.invoke();
567 * DESCRIPTOR CODE BELOW
570 bool Entity::processDescriptor()
572 Resourcer* rc = Resourcer::instance();
573 XMLRef doc = rc->getXMLDoc(descriptor, "entity.dtd");
574 if (!doc)
575 return false;
576 const XMLNode root = doc->root(); // <entity>
577 if (!root)
578 return false;
580 for (XMLNode node = root.childrenNode(); node; node = node.next()) {
581 if (node.is("speed")) {
582 ASSERT(node.doubleContent(&baseSpeed));
583 setSpeed(speedMul); // Calculate speed from tile size.
584 } else if (node.is("sprite")) {
585 ASSERT(processSprite(node.childrenNode()));
586 } else if (node.is("sounds")) {
587 ASSERT(processSounds(node.childrenNode()));
588 } else if (node.is("scripts")) {
589 ASSERT(processScripts(node.childrenNode()));
592 return true;
595 bool Entity::processSprite(XMLNode node)
597 Resourcer* rc = Resourcer::instance();
598 TiledImage tiles;
599 for (; node; node = node.next()) {
600 if (node.is("sheet")) {
601 std::string imageSheet = node.content();
602 ASSERT(node.intAttr("tile_width", &imgw) &&
603 node.intAttr("tile_height", &imgh));
604 ASSERT(rc->getTiledImage(tiles, imageSheet,
605 imgw, imgh, false));
606 } else if (node.is("phases")) {
607 ASSERT(processPhases(node.childrenNode(), tiles));
610 return true;
613 bool Entity::processPhases(XMLNode node, const TiledImage& tiles)
615 for (; node; node = node.next())
616 if (node.is("phase"))
617 ASSERT(processPhase(node, tiles));
618 return true;
621 bool Entity::processPhase(const XMLNode node, const TiledImage& tiles)
623 /* Each phase requires a 'name'. Additionally,
624 * one of either 'pos' or 'speed' is needed.
625 * If speed is used, we have sub-elements. We
626 * can't have both pos and speed.
628 const std::string name = node.attr("name");
629 if (name.empty()) {
630 Log::err(descriptor, "<phase> name attribute is empty");
631 return false;
634 const std::string posStr = node.attr("pos");
635 const std::string speedStr = node.attr("speed");
637 if (posStr.size() && speedStr.size()) {
638 Log::err(descriptor, "pos and speed attributes in "
639 "phase element are mutually exclusive");
640 return false;
641 } else if (posStr.empty() && speedStr.empty()) {
642 Log::err(descriptor, "must have pos or speed attribute "
643 "in phase element");
644 return false;
647 if (posStr.size()) {
648 int pos;
649 ASSERT(node.intAttr("pos", &pos));
650 if (pos < 0 || (int)tiles.size() < pos) {
651 Log::err(descriptor,
652 "<phase></phase> index out of bounds");
653 return false;
655 phases[name] = Animation(tiles[pos]);
657 else {
658 int speed;
659 ASSERT(node.intAttr("speed", &speed));
661 int frameLen = (int)(1000.0/speed);
662 std::vector<ImageRef> frames;
664 ASSERT(processMembers(node.childrenNode(), frames, tiles));
665 phases[name] = Animation(frames, frameLen);
668 return true;
671 bool Entity::processMembers(XMLNode node, std::vector<ImageRef>& frames,
672 const TiledImage& tiles)
674 for (; node; node = node.next())
675 if (node.is("member"))
676 ASSERT(processMember(node, frames, tiles));
677 return true;
680 bool Entity::processMember(const XMLNode node, std::vector<ImageRef>& frames,
681 const TiledImage& tiles)
683 int pos;
684 ASSERT(node.intAttr("pos", &pos));
685 if (pos < 0 || (int)tiles.size() < pos) {
686 Log::err(descriptor, "<member></member> index out of bounds");
687 return false;
689 frames.push_back(tiles[pos]);
690 return true;
693 bool Entity::processSounds(XMLNode node)
695 for (; node; node = node.next())
696 if (node.is("sound"))
697 ASSERT(processSound(node));
698 return true;
701 bool Entity::processSound(const XMLNode node)
703 const std::string name = node.attr("name");
704 const std::string filename = node.content();
705 if (name.empty()) {
706 Log::err(descriptor, "<sound> name attribute is empty");
707 return false;
708 } else if (filename.empty()) {
709 Log::err(descriptor, "<sound></sound> is empty");
710 return false;
713 Resourcer* rc = Resourcer::instance();
714 SampleRef s = rc->getSample(filename);
715 if (s)
716 sounds[name] = s;
717 return true;
720 bool Entity::processScripts(XMLNode node)
722 for (; node; node = node.next())
723 if (node.is("script"))
724 ASSERT(processScript(node));
725 return true;
728 bool Entity::processScript(const XMLNode node)
730 const std::string trigger = node.attr("trigger");
731 const std::string filename = node.content();
732 if (trigger.empty()) {
733 Log::err(descriptor, "<script> trigger attribute is empty");
734 return false;
735 } else if (filename.empty()) {
736 Log::err(descriptor, "<script></script> is empty");
737 return false;
740 ScriptInst script(filename);
741 if (!script.validate(descriptor))
742 return false;
744 if (!setScript(trigger, script)) {
745 Log::err(descriptor,
746 "unrecognized script trigger: " + trigger);
747 return false;
750 return true;
753 bool Entity::setScript(const std::string& trigger, ScriptInst& script)
755 if (boost::iequals(trigger, "on_tick")) {
756 tickScript = script;
757 return true;
759 if (boost::iequals(trigger, "on_turn")) {
760 turnScript = script;
761 return true;
763 if (boost::equals(trigger, "on_tile_entry")) {
764 tileEntryScript = script;
765 return true;
767 if (boost::iequals(trigger, "on_tile_exit")) {
768 tileExitScript = script;
769 return true;
771 if (boost::iequals(trigger, "on_delete")) {
772 deleteScript = script;
773 return true;
775 return false;
779 void exportEntity()
781 using namespace boost::python;
783 class_<Entity>("Entity", no_init)
784 .def("init", &Entity::init)
785 .def("delete", &Entity::destroy)
786 .add_property("frozen", &Entity::getFrozen, &Entity::setFrozen)
787 .add_property("phase", &Entity::getPhase, &Entity::setPhase)
788 .add_property("area",
789 make_function(&Entity::getArea,
790 return_value_policy<reference_existing_object>()),
791 &Entity::setArea)
792 .add_property("tile", make_function(
793 static_cast<Tile* (Entity::*) ()> (&Entity::getTile),
794 return_value_policy<reference_existing_object>()))
795 .add_property("speed", &Entity::getSpeed, &Entity::setSpeed)
796 .add_property("moving", &Entity::isMoving)
797 .add_property("exempt", &Entity::exemptManip)
798 .add_property("coords", &Entity::getTileCoords_vi)
799 .def("set_coords",
800 static_cast<void (Entity::*) (int,int,double)>
801 (&Entity::setTileCoords))
802 .def("teleport", &Entity::teleport)
803 .def("move", &Entity::move)
804 .def("move_dest",
805 static_cast<vicoord (Entity::*) (Tile*,int,int)>
806 (&Entity::moveDest))
807 .def("can_move",
808 static_cast<bool (Entity::*) (int,int,double)>
809 (&Entity::canMove))
810 .def_readwrite("on_tick", &Entity::tickScript)
811 .def_readwrite("on_turn", &Entity::turnScript)
812 .def_readwrite("on_tile_entry", &Entity::tileEntryScript)
813 .def_readwrite("on_tile_exit", &Entity::tileExitScript)
814 .def_readwrite("on_delete", &Entity::deleteScript)