Fix INT_MAX definition on some newer systems.
[Tsunagari.git] / src / entity.cpp
blob98798485f36cd37a57ee2700742aa5dc029f9446
1 /***************************************
2 ** Tsunagari Tile Engine **
3 ** entity.cpp **
4 ** Copyright 2011-2013 PariahSoft LLC **
5 ***************************************/
7 // **********
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // **********
27 #include <math.h>
29 #include <Gosu/Image.hpp>
30 #include <Gosu/Math.hpp>
31 #include <Gosu/Timing.hpp>
33 #include "area.h"
34 #include "client-conf.h"
35 #include "entity.h"
36 #include "log.h"
37 #include "python.h"
38 #include "python-bindings-template.cpp"
39 #include "reader.h"
40 #include "string.h"
41 #include "world.h"
42 #include "xml.h"
44 #define ASSERT(x) if (!(x)) { return false; }
46 static std::string directions[][3] = {
47 {"up-left", "up", "up-right"},
48 {"left", "stance", "right"},
49 {"down-left", "down", "down-right"},
53 Entity::Entity()
54 : redraw(true),
55 area(NULL),
56 r(0.0, 0.0, 0.0),
57 frozen(false),
58 speedMul(1.0),
59 moving(false),
60 stillMoving(false),
61 nowalkFlags(TILE_NOWALK | TILE_NOWALK_NPC),
62 nowalkExempt(0),
63 phase(NULL),
64 phaseName("")
68 Entity::~Entity()
70 if (deleteScript) {
71 pythonSetGlobal("Area", area);
72 pythonSetGlobal("Entity", this);
73 deleteScript->invoke();
77 bool Entity::init(const std::string& descriptor)
79 this->descriptor = descriptor;
80 return processDescriptor();
83 void Entity::destroy()
85 leaveTile();
86 if (area) {
87 erase();
88 area->requestRedraw();
90 area = NULL;
91 delete this;
94 void Entity::draw()
96 redraw = false;
97 if (!phase)
98 return;
100 time_t now = World::instance()->time();
101 Image* img = phase->frame(now);
103 img->draw(
104 doff.x + r.x,
105 doff.y + r.y,
106 r.z + area->isometricZOff(rvec2(r.x, r.y))
110 bool Entity::needsRedraw() const
112 time_t now = World::instance()->time();
113 return redraw || (phase && phase->needsRedraw(now));
117 void Entity::tick(time_t dt)
119 runTickScript();
120 switch (conf.moveMode) {
121 case TURN:
122 tickTurn(dt);
123 break;
124 case TILE:
125 tickTile(dt);
126 break;
127 case NOTILE:
128 tickNoTile(dt);
129 break;
133 void Entity::tickTurn(time_t)
135 // FIXME Characters (!!) don't do anything in TILE mode.
138 void Entity::tickTile(time_t dt)
140 if (!moving)
141 return;
143 redraw = true;
144 double traveled = speed * (double)dt;
145 double destDist = Gosu::distance(r.x, r.y, destCoord.x, destCoord.y);
146 if (destDist <= traveled) {
147 r = destCoord;
148 moving = false;
149 postMove();
150 if (moving) {
151 // Time rollover.
152 double perc = 1.0 - destDist/traveled;
153 time_t remt = (time_t)(perc * (double)dt);
154 tick(remt);
157 else {
158 double angle = atan2(destCoord.y - r.y, destCoord.x - r.x);
159 double dx = cos(angle);
160 double dy = sin(angle);
162 r.x += dx * traveled;
163 r.y += dy * traveled;
167 void Entity::tickNoTile(time_t)
169 // TODO
172 void Entity::turn()
174 runTurnScript();
177 const std::string Entity::getFacing() const
179 return directionStr(facing);
182 bool Entity::setPhase(const std::string& name)
184 enum SetPhaseResult res;
185 res = _setPhase(name);
186 if (res == PHASE_NOTFOUND) {
187 res = _setPhase("stance");
188 if (res == PHASE_NOTFOUND)
189 Log::err(descriptor, "phase '" + name + "' not found");
191 return res == PHASE_CHANGED;
194 std::string Entity::getPhase() const
196 return phaseName;
199 rcoord Entity::getPixelCoord() const
201 return r;
204 icoord Entity::getTileCoords_i() const
206 return area->virt2phys(r);
209 vicoord Entity::getTileCoords_vi() const
211 return area->virt2virt(r);
214 void Entity::setTileCoords(int x, int y)
216 leaveTile();
217 vicoord virt(x, y, r.z);
218 redraw = true;
219 r = area->virt2virt(virt);
220 enterTile();
223 void Entity::setTileCoords(int x, int y, double z)
225 leaveTile();
226 vicoord virt(x, y, z);
227 redraw = true;
228 r = area->virt2virt(virt);
229 enterTile();
232 void Entity::setTileCoords(icoord phys)
234 leaveTile();
235 redraw = true;
236 r = area->phys2virt_r(phys);
237 enterTile();
240 void Entity::setTileCoords(vicoord virt)
242 leaveTile();
243 redraw = true;
244 r = area->virt2virt(virt);
245 enterTile();
248 void Entity::setTileCoords(rcoord virt)
250 leaveTile();
251 redraw = true;
252 r = virt;
253 enterTile();
256 void Entity::teleport(int, int)
258 throw "pure virtual function";
261 icoord Entity::moveDest(ivec2 facing)
263 Tile* tile = getTile();
264 icoord here = getTileCoords_i();
266 if (tile)
267 // Handle layermod.
268 return tile->moveDest(here, facing);
269 else
270 return here + icoord(facing.x, facing.y, 0);
273 // Python API.
274 vicoord Entity::moveDest(Tile* t, int dx, int dy)
276 icoord here = getTileCoords_i();
277 icoord dest;
279 if (t) {
280 // Handle layermod.
281 dest = t->moveDest(here, ivec2(dx, dy));
282 return t->area->phys2virt_vi(dest);
284 else {
285 dest = here + icoord(dx, dy, 0);
286 return area->phys2virt_vi(dest);
290 // Python API.
291 bool Entity::canMove(int x, int y, double z)
293 vicoord virt(x, y, z);
294 return canMove(area->virt2phys(virt));
297 bool Entity::canMove(icoord dest)
299 icoord dxyz = dest - getTileCoords_i();
300 ivec2 dxy(dxyz.x, dxyz.y);
302 Tile* curTile = getTile();
303 this->destTile = area->getTile(dest);
304 this->destCoord = area->phys2virt_r(dest);
306 if ((curTile && curTile->exitAt(dxy)) ||
307 (destTile && destTile->exits[EXIT_NORMAL])) {
308 // We can always take exits as long as we can take exits.
309 // (Even if they would cause us to be out of bounds.)
310 if (nowalkExempt & TILE_NOWALK_EXIT)
311 return true;
314 bool inBounds = area->inBounds(dest);
315 if (destTile && inBounds) {
316 // Tile is inside map. Can we move?
317 if (nowalked(*destTile))
318 return false;
319 if (destTile->entCnt)
320 // Space is occupied by another Entity.
321 return false;
323 return true;
326 // The tile is legitimately off the map.
327 return nowalkExempt & TILE_NOWALK_AREA_BOUND;
330 bool Entity::canMove(vicoord dest)
332 return canMove(area->virt2phys(dest));
335 bool Entity::isMoving() const
337 return moving || stillMoving;
340 void Entity::moveByTile(int x, int y)
342 moveByTile(ivec2(x, y));
345 void Entity::moveByTile(ivec2 delta)
347 if (moving)
348 return;
349 setFacing(delta);
351 if (canMove(moveDest(facing)))
352 preMove();
353 else
354 setPhase(directionStr(facing));
357 void Entity::move(int, int)
359 throw "pure virtual function";
362 Area* Entity::getArea()
364 return area;
367 void Entity::setArea(Area* a)
369 leaveTile();
370 area = a;
371 calcDraw();
372 setSpeed(speedMul); // Calculate new speed based on tile size.
373 enterTile();
376 double Entity::getSpeed() const
378 return speedMul;
381 void Entity::setSpeed(double multiplier)
383 speedMul = multiplier;
384 if (area) {
385 double tilesPerSecond = area->getTileDimensions().x / 1000.0;
386 speed = baseSpeed * speedMul * tilesPerSecond;
390 Tile* Entity::getTile() const
392 return area ? area->getTile(r) : NULL;
395 Tile* Entity::getTile()
397 return area ? area->getTile(r) : NULL;
400 void Entity::setFrozen(bool b)
402 frozen = b;
405 bool Entity::getFrozen()
407 return frozen;
410 FlagManip Entity::exemptManip()
412 return FlagManip(&nowalkExempt);
415 void Entity::erase()
417 throw "pure virtual function";
420 void Entity::calcDraw()
422 ivec2 tile = area->getTileDimensions();
424 // X-axis is centered on tile.
425 doff.x = (tile.x - imgsz.x) / 2;
426 // Y-axis is aligned with bottom of tile.
427 doff.y = tile.y - imgsz.y;
430 SampleRef Entity::getSound(const std::string& name) const
432 SampleMap::const_iterator it;
433 it = sounds.find(name);
434 if (it != sounds.end())
435 return it->second;
436 else
437 return SampleRef();
440 ivec2 Entity::setFacing(ivec2 facing)
442 this->facing = ivec2(
443 Gosu::clamp(facing.x, -1, 1),
444 Gosu::clamp(facing.y, -1, 1)
446 return this->facing;
449 const std::string& Entity::directionStr(ivec2 facing) const
451 return directions[facing.y+1][facing.x+1];
454 enum SetPhaseResult Entity::_setPhase(const std::string& name)
456 AnimationMap::iterator it;
457 it = phases.find(name);
458 if (it == phases.end()) {
459 return PHASE_NOTFOUND;
461 Animation* newPhase = &it->second;
462 if (phase != newPhase) {
463 time_t now = World::instance()->time();
464 phase = newPhase;
465 phase->startOver(now, ANIM_INFINITE_CYCLES);
466 phaseName = name;
467 redraw = true;
468 return PHASE_CHANGED;
470 return PHASE_NOTCHANGED;
473 bool Entity::nowalked(Tile& t)
475 unsigned flags = nowalkFlags & ~nowalkExempt;
476 return t.hasFlag(flags);
479 void Entity::preMove()
481 fromCoord = r;
482 fromTile = getTile();
484 rcoord d = destCoord - fromCoord;
485 deltaCoord = area->virt2virt(d);
487 moving = true;
489 // Start moving animation.
490 switch (conf.moveMode) {
491 case TURN:
492 break;
493 case TILE:
494 case NOTILE:
495 setPhase("moving " + getFacing());
496 break;
499 // Process triggers.
500 runTileExitScript();
501 if (fromTile)
502 fromTile->runLeaveScript(this);
504 // Modify tile's entity count.
505 leaveTile();
506 enterTile(destTile);
508 SampleRef step = getSound("step");
509 if (step)
510 step->play();
512 // Set z right away so that we're on-level with the square we're
513 // entering.
514 r.z = destCoord.z;
516 if (conf.moveMode == TURN) {
517 // Movement is instantaneous.
518 redraw = true;
519 r = destCoord;
520 postMove();
522 else {
523 // Movement happens over time. See tickTile().
527 void Entity::postMove()
529 moving = false;
531 if (destTile) {
532 double* layermod = destTile->layermods[EXIT_NORMAL];
533 if (layermod)
534 r.z = *layermod;
537 // Stop moving animation.
538 if (!stillMoving)
539 setPhase(getFacing());
541 // Process triggers.
542 if (destTile)
543 destTile->runEnterScript(this);
545 runTileEntryScript();
547 // TODO: move teleportation here
549 * if (onExit()) {
550 * leaveTile();
551 * moveArea(getExit());
552 * postMoveScript();
553 * enterTile();
558 void Entity::leaveTile()
560 Tile* t = getTile();
561 if (t)
562 t->entCnt--;
565 void Entity::enterTile()
567 Tile* t = getTile();
568 if (t)
569 enterTile(getTile());
572 void Entity::enterTile(Tile* t)
574 if (t)
575 t->entCnt++;
578 void Entity::runTickScript()
580 if (!tickScript)
581 return;
582 pythonSetGlobal("Area", area);
583 pythonSetGlobal("Entity", this);
584 pythonSetGlobal("Tile", getTile());
585 tickScript->invoke();
588 void Entity::runTurnScript()
590 if (!turnScript)
591 return;
592 pythonSetGlobal("Area", area);
593 pythonSetGlobal("Entity", this);
594 pythonSetGlobal("Tile", getTile());
595 turnScript->invoke();
598 void Entity::runTileExitScript()
600 if (!tileExitScript)
601 return;
602 pythonSetGlobal("Area", area);
603 pythonSetGlobal("Entity", this);
604 pythonSetGlobal("Tile", getTile());
605 tileExitScript->invoke();
608 void Entity::runTileEntryScript()
610 if (!tileEntryScript)
611 return;
612 pythonSetGlobal("Area", area);
613 pythonSetGlobal("Entity", this);
614 pythonSetGlobal("Tile", getTile());
615 tileEntryScript->invoke();
620 * DESCRIPTOR CODE BELOW
623 bool Entity::processDescriptor()
625 XMLRef doc = Reader::getXMLDoc(descriptor, "dtd/entity.dtd");
626 if (!doc)
627 return false;
628 const XMLNode root = doc->root(); // <entity>
629 if (!root)
630 return false;
632 for (XMLNode node = root.childrenNode(); node; node = node.next()) {
633 if (node.is("speed")) {
634 ASSERT(node.doubleContent(&baseSpeed));
635 setSpeed(speedMul); // Calculate speed from tile size.
636 } else if (node.is("sprite")) {
637 ASSERT(processSprite(node.childrenNode()));
638 } else if (node.is("sounds")) {
639 ASSERT(processSounds(node.childrenNode()));
640 } else if (node.is("scripts")) {
641 ASSERT(processScripts(node.childrenNode()));
644 return true;
647 bool Entity::processSprite(XMLNode node)
649 TiledImageRef tiles;
650 for (; node; node = node.next()) {
651 if (node.is("sheet")) {
652 std::string imageSheet = node.content();
653 ASSERT(node.intAttr("tile_width", &imgsz.x) &&
654 node.intAttr("tile_height", &imgsz.y));
655 tiles = Reader::getTiledImage(imageSheet, imgsz.x, imgsz.y);
656 ASSERT(tiles);
657 } else if (node.is("phases")) {
658 ASSERT(processPhases(node.childrenNode(), tiles));
661 return true;
664 bool Entity::processPhases(XMLNode node, const TiledImageRef& tiles)
666 for (; node; node = node.next())
667 if (node.is("phase"))
668 ASSERT(processPhase(node, tiles));
669 return true;
672 bool Entity::processPhase(const XMLNode node, const TiledImageRef& tiles)
674 /* Each phase requires a 'name' and 'frames'. Additionally,
675 * 'speed' is required if 'frames' has more than one member.
677 const std::string name = node.attr("name");
678 if (name.empty()) {
679 Log::err(descriptor, "<phase> name attribute is empty");
680 return false;
683 const std::string framesStr = node.attr("frames");
684 const std::string speedStr = node.attr("speed");
686 if (framesStr.empty()) {
687 Log::err(descriptor, "<phase> frames attribute empty");
688 return false;
691 if (isInteger(framesStr)) {
692 int frame = atoi(framesStr.c_str());
693 if (frame < 0 || (int)tiles->size() < frame) {
694 Log::err(descriptor,
695 "<phase> frames attribute index out of bounds");
696 return false;
698 const ImageRef& image = (*tiles.get())[frame];
699 phases[name] = Animation(image);
701 else if (isRanges(framesStr)) {
702 if (!isDecimal(speedStr)) {
703 Log::err(descriptor,
704 "<phase> speed attribute must be present and "
705 "must be decimal");
707 double fps = atof(speedStr.c_str());
709 typedef std::vector<int> IntVector;
710 IntVector frames = parseRanges(framesStr);
711 std::vector<ImageRef> images;
712 for (IntVector::iterator it = frames.begin(); it != frames.end(); it++) {
713 int i = *it;
714 if (i < 0 || (int)tiles->size() < i) {
715 Log::err(descriptor,
716 "<phase> frames attribute index out of bounds");
717 return false;
719 images.push_back((*tiles.get())[i]);
722 phases[name] = Animation(images, (time_t)(1000.0 / fps));
724 else {
725 Log::err(descriptor,
726 "<phase> frames attribute not an int or int ranges");
727 return false;
730 return true;
733 bool Entity::processSounds(XMLNode node)
735 for (; node; node = node.next())
736 if (node.is("sound"))
737 ASSERT(processSound(node));
738 return true;
741 bool Entity::processSound(const XMLNode node)
743 const std::string name = node.attr("name");
744 const std::string filename = node.content();
745 if (name.empty()) {
746 Log::err(descriptor, "<sound> name attribute is empty");
747 return false;
748 } else if (filename.empty()) {
749 Log::err(descriptor, "<sound></sound> is empty");
750 return false;
753 SampleRef s = Reader::getSample(filename);
754 if (s)
755 sounds[name] = s;
756 return true;
759 bool Entity::processScripts(XMLNode node)
761 for (; node; node = node.next())
762 if (node.is("script"))
763 ASSERT(processScript(node));
764 return true;
767 bool Entity::processScript(const XMLNode node)
769 const std::string trigger = node.attr("trigger");
770 const std::string filename = node.content();
771 if (trigger.empty()) {
772 Log::err(descriptor, "<script> trigger attribute is empty");
773 return false;
774 } else if (filename.empty()) {
775 Log::err(descriptor, "<script></script> is empty");
776 return false;
779 ScriptRef script = Script::create(filename);
780 if (!script || !script->validate())
781 return false;
783 if (!setScript(trigger, script)) {
784 Log::err(descriptor,
785 "unrecognized script trigger: " + trigger);
786 return false;
789 return true;
792 bool Entity::setScript(const std::string& trigger, ScriptRef& script)
794 if (trigger == "on_tick") {
795 tickScript = script;
796 return true;
798 if (trigger == "on_turn") {
799 turnScript = script;
800 return true;
802 if (trigger == "on_tile_entry") {
803 tileEntryScript = script;
804 return true;
806 if (trigger == "on_tile_exit") {
807 tileExitScript = script;
808 return true;
810 if (trigger == "on_delete") {
811 deleteScript = script;
812 return true;
814 return false;
818 void exportEntity()
820 using namespace boost::python;
822 class_<Entity>("Entity", no_init)
823 .def("init", &Entity::init)
824 .def("delete", &Entity::destroy)
825 .add_property("frozen", &Entity::getFrozen, &Entity::setFrozen)
826 .add_property("phase", &Entity::getPhase, &Entity::setPhase)
827 .add_property("area",
828 make_function(&Entity::getArea,
829 return_value_policy<reference_existing_object>()),
830 &Entity::setArea)
831 .add_property("tile", make_function(
832 static_cast<Tile* (Entity::*) ()> (&Entity::getTile),
833 return_value_policy<reference_existing_object>()))
834 .add_property("speed", &Entity::getSpeed, &Entity::setSpeed)
835 .add_property("moving", &Entity::isMoving)
836 .add_property("exempt", &Entity::exemptManip)
837 .add_property("coords", &Entity::getTileCoords_vi)
838 .def("set_coords",
839 static_cast<void (Entity::*) (int,int,double)>
840 (&Entity::setTileCoords))
841 .def("teleport", &Entity::teleport)
842 .def("move", &Entity::move)
843 .def("move_dest",
844 static_cast<vicoord (Entity::*) (Tile*,int,int)>
845 (&Entity::moveDest))
846 .def("can_move",
847 static_cast<bool (Entity::*) (int,int,double)>
848 (&Entity::canMove))
849 // .def_readwrite("on_tick", &Entity::tickScript)
850 // .def_readwrite("on_turn", &Entity::turnScript)
851 // .def_readwrite("on_tile_entry", &Entity::tileEntryScript)
852 // .def_readwrite("on_tile_exit", &Entity::tileExitScript)
853 // .def_readwrite("on_delete", &Entity::deleteScript)