finish move to Python-handled imports
[Tsunagari.git] / src / entity.cpp
blob60754941e07c506ce39558e0914a12379681da87
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 <boost/algorithm/string.hpp> // for iequals
30 #include <Gosu/Image.hpp>
31 #include <Gosu/Math.hpp>
32 #include <Gosu/Timing.hpp>
34 #include "area.h"
35 #include "client-conf.h"
36 #include "entity.h"
37 #include "log.h"
38 #include "python.h"
39 #include "python-bindings-template.cpp"
40 #include "reader.h"
41 #include "string.h"
42 #include "world.h"
43 #include "xml.h"
45 #define ASSERT(x) if (!(x)) { return false; }
47 static std::string directions[][3] = {
48 {"up-left", "up", "up-right"},
49 {"left", "stance", "right"},
50 {"down-left", "down", "down-right"},
54 Entity::Entity()
55 : redraw(true),
56 area(NULL),
57 r(0.0, 0.0, 0.0),
58 frozen(false),
59 speedMul(1.0),
60 moving(false),
61 stillMoving(false),
62 nowalkFlags(TILE_NOWALK | TILE_NOWALK_NPC),
63 nowalkExempt(0),
64 phase(NULL),
65 phaseName("")
69 Entity::~Entity()
71 if (deleteScript) {
72 pythonSetGlobal("Area", area);
73 pythonSetGlobal("Entity", this);
74 deleteScript->invoke();
78 bool Entity::init(const std::string& descriptor)
80 this->descriptor = descriptor;
81 return processDescriptor();
84 void Entity::destroy()
86 leaveTile();
87 if (area) {
88 erase();
89 area->requestRedraw();
91 area = NULL;
92 delete this;
95 void Entity::draw()
97 redraw = false;
98 if (!phase)
99 return;
101 time_t now = World::instance()->time();
102 Image* img = phase->frame(now);
104 img->draw(
105 doff.x + r.x,
106 doff.y + r.y,
107 r.z + area->isometricZOff(rvec2(r.x, r.y))
111 bool Entity::needsRedraw() const
113 time_t now = World::instance()->time();
114 return redraw || (phase && phase->needsRedraw(now));
118 void Entity::tick(time_t dt)
120 runTickScript();
121 switch (conf.moveMode) {
122 case TURN:
123 tickTurn(dt);
124 break;
125 case TILE:
126 tickTile(dt);
127 break;
128 case NOTILE:
129 tickNoTile(dt);
130 break;
134 void Entity::tickTurn(time_t)
136 // FIXME Characters (!!) don't do anything in TILE mode.
139 void Entity::tickTile(time_t dt)
141 if (!moving)
142 return;
144 redraw = true;
145 double traveled = speed * (double)dt;
146 double destDist = Gosu::distance(r.x, r.y, destCoord.x, destCoord.y);
147 if (destDist <= traveled) {
148 r = destCoord;
149 moving = false;
150 postMove();
151 if (moving) {
152 // Time rollover.
153 double perc = 1.0 - destDist/traveled;
154 time_t remt = (time_t)(perc * (double)dt);
155 tick(remt);
158 else {
159 double angle = atan2(destCoord.y - r.y, destCoord.x - r.x);
160 double dx = cos(angle);
161 double dy = sin(angle);
163 r.x += dx * traveled;
164 r.y += dy * traveled;
168 void Entity::tickNoTile(time_t)
170 // TODO
173 void Entity::turn()
175 runTurnScript();
178 const std::string Entity::getFacing() const
180 return directionStr(facing);
183 bool Entity::setPhase(const std::string& name)
185 enum SetPhaseResult res;
186 res = _setPhase(name);
187 if (res == PHASE_NOTFOUND) {
188 res = _setPhase("stance");
189 if (res == PHASE_NOTFOUND)
190 Log::err(descriptor, "phase '" + name + "' not found");
192 return res == PHASE_CHANGED;
195 std::string Entity::getPhase() const
197 return phaseName;
200 rcoord Entity::getPixelCoord() const
202 return r;
205 icoord Entity::getTileCoords_i() const
207 return area->virt2phys(r);
210 vicoord Entity::getTileCoords_vi() const
212 return area->virt2virt(r);
215 void Entity::setTileCoords(int x, int y)
217 leaveTile();
218 vicoord virt(x, y, r.z);
219 redraw = true;
220 r = area->virt2virt(virt);
221 enterTile();
224 void Entity::setTileCoords(int x, int y, double z)
226 leaveTile();
227 vicoord virt(x, y, z);
228 redraw = true;
229 r = area->virt2virt(virt);
230 enterTile();
233 void Entity::setTileCoords(icoord phys)
235 leaveTile();
236 redraw = true;
237 r = area->phys2virt_r(phys);
238 enterTile();
241 void Entity::setTileCoords(vicoord virt)
243 leaveTile();
244 redraw = true;
245 r = area->virt2virt(virt);
246 enterTile();
249 void Entity::setTileCoords(rcoord virt)
251 leaveTile();
252 redraw = true;
253 r = virt;
254 enterTile();
257 void Entity::teleport(int, int)
259 throw "pure virtual function";
262 icoord Entity::moveDest(ivec2 facing)
264 Tile* tile = getTile();
265 icoord here = getTileCoords_i();
267 if (tile)
268 // Handle layermod.
269 return tile->moveDest(here, facing);
270 else
271 return here + icoord(facing.x, facing.y, 0);
274 // Python API.
275 vicoord Entity::moveDest(Tile* t, int dx, int dy)
277 icoord here = getTileCoords_i();
278 icoord dest;
280 if (t) {
281 // Handle layermod.
282 dest = t->moveDest(here, ivec2(dx, dy));
283 return t->area->phys2virt_vi(dest);
285 else {
286 dest = here + icoord(dx, dy, 0);
287 return area->phys2virt_vi(dest);
291 // Python API.
292 bool Entity::canMove(int x, int y, double z)
294 vicoord virt(x, y, z);
295 return canMove(area->virt2phys(virt));
298 bool Entity::canMove(icoord dest)
300 icoord dxyz = dest - getTileCoords_i();
301 ivec2 dxy(dxyz.x, dxyz.y);
303 Tile* curTile = getTile();
304 this->destTile = area->getTile(dest);
305 this->destCoord = area->phys2virt_r(dest);
307 if ((curTile && curTile->exitAt(dxy)) ||
308 (destTile && destTile->exits[EXIT_NORMAL])) {
309 // We can always take exits as long as we can take exits.
310 // (Even if they would cause us to be out of bounds.)
311 if (nowalkExempt & TILE_NOWALK_EXIT)
312 return true;
315 bool inBounds = area->inBounds(dest);
316 if (destTile && inBounds) {
317 // Tile is inside map. Can we move?
318 if (nowalked(*destTile))
319 return false;
320 if (destTile->entCnt)
321 // Space is occupied by another Entity.
322 return false;
324 return true;
327 // The tile is legitimately off the map.
328 return nowalkExempt & TILE_NOWALK_AREA_BOUND;
331 bool Entity::canMove(vicoord dest)
333 return canMove(area->virt2phys(dest));
336 bool Entity::isMoving() const
338 return moving || stillMoving;
341 void Entity::moveByTile(int x, int y)
343 moveByTile(ivec2(x, y));
346 void Entity::moveByTile(ivec2 delta)
348 if (moving)
349 return;
350 setFacing(delta);
352 if (canMove(moveDest(facing)))
353 preMove();
354 else
355 setPhase(directionStr(facing));
358 void Entity::move(int, int)
360 throw "pure virtual function";
363 Area* Entity::getArea()
365 return area;
368 void Entity::setArea(Area* a)
370 leaveTile();
371 area = a;
372 calcDraw();
373 setSpeed(speedMul); // Calculate new speed based on tile size.
374 enterTile();
377 double Entity::getSpeed() const
379 return speedMul;
382 void Entity::setSpeed(double multiplier)
384 speedMul = multiplier;
385 if (area) {
386 double tilesPerSecond = area->getTileDimensions().x / 1000.0;
387 speed = baseSpeed * speedMul * tilesPerSecond;
391 Tile* Entity::getTile() const
393 return area ? area->getTile(r) : NULL;
396 Tile* Entity::getTile()
398 return area ? area->getTile(r) : NULL;
401 void Entity::setFrozen(bool b)
403 frozen = b;
406 bool Entity::getFrozen()
408 return frozen;
411 FlagManip Entity::exemptManip()
413 return FlagManip(&nowalkExempt);
416 void Entity::erase()
418 throw "pure virtual function";
421 void Entity::calcDraw()
423 ivec2 tile = area->getTileDimensions();
425 // X-axis is centered on tile.
426 doff.x = (tile.x - imgsz.x) / 2;
427 // Y-axis is aligned with bottom of tile.
428 doff.y = tile.y - imgsz.y;
431 SampleRef Entity::getSound(const std::string& name) const
433 SampleMap::const_iterator it;
434 it = sounds.find(name);
435 if (it != sounds.end())
436 return it->second;
437 else
438 return SampleRef();
441 ivec2 Entity::setFacing(ivec2 facing)
443 this->facing = ivec2(
444 Gosu::clamp(facing.x, -1, 1),
445 Gosu::clamp(facing.y, -1, 1)
447 return this->facing;
450 const std::string& Entity::directionStr(ivec2 facing) const
452 return directions[facing.y+1][facing.x+1];
455 enum SetPhaseResult Entity::_setPhase(const std::string& name)
457 AnimationMap::iterator it;
458 it = phases.find(name);
459 if (it == phases.end()) {
460 return PHASE_NOTFOUND;
462 Animation* newPhase = &it->second;
463 if (phase != newPhase) {
464 time_t now = World::instance()->time();
465 phase = newPhase;
466 phase->startOver(now, ANIM_INFINITE_CYCLES);
467 phaseName = name;
468 redraw = true;
469 return PHASE_CHANGED;
471 return PHASE_NOTCHANGED;
474 bool Entity::nowalked(Tile& t)
476 unsigned flags = nowalkFlags & ~nowalkExempt;
477 return t.hasFlag(flags);
480 void Entity::preMove()
482 fromCoord = r;
483 fromTile = getTile();
485 rcoord d = destCoord - fromCoord;
486 deltaCoord = area->virt2virt(d);
488 moving = true;
490 // Start moving animation.
491 switch (conf.moveMode) {
492 case TURN:
493 break;
494 case TILE:
495 case NOTILE:
496 setPhase("moving " + getFacing());
497 break;
500 // Process triggers.
501 runTileExitScript();
502 if (fromTile)
503 fromTile->runLeaveScript(this);
505 // Modify tile's entity count.
506 leaveTile();
507 enterTile(destTile);
509 SampleRef step = getSound("step");
510 if (step)
511 step->play();
513 // Set z right away so that we're on-level with the square we're
514 // entering.
515 r.z = destCoord.z;
517 if (conf.moveMode == TURN) {
518 // Movement is instantaneous.
519 redraw = true;
520 r = destCoord;
521 postMove();
523 else {
524 // Movement happens over time. See tickTile().
528 void Entity::postMove()
530 moving = false;
532 if (destTile) {
533 double* layermod = destTile->layermods[EXIT_NORMAL];
534 if (layermod)
535 r.z = *layermod;
538 // Stop moving animation.
539 if (!stillMoving)
540 setPhase(getFacing());
542 // Process triggers.
543 if (destTile)
544 destTile->runEnterScript(this);
546 runTileEntryScript();
548 // TODO: move teleportation here
550 * if (onExit()) {
551 * leaveTile();
552 * moveArea(getExit());
553 * postMoveScript();
554 * enterTile();
559 void Entity::leaveTile()
561 Tile* t = getTile();
562 if (t)
563 t->entCnt--;
566 void Entity::enterTile()
568 Tile* t = getTile();
569 if (t)
570 enterTile(getTile());
573 void Entity::enterTile(Tile* t)
575 if (t)
576 t->entCnt++;
579 void Entity::runTickScript()
581 if (!tickScript)
582 return;
583 pythonSetGlobal("Area", area);
584 pythonSetGlobal("Entity", this);
585 pythonSetGlobal("Tile", getTile());
586 tickScript->invoke();
589 void Entity::runTurnScript()
591 if (!turnScript)
592 return;
593 pythonSetGlobal("Area", area);
594 pythonSetGlobal("Entity", this);
595 pythonSetGlobal("Tile", getTile());
596 turnScript->invoke();
599 void Entity::runTileExitScript()
601 if (!tileExitScript)
602 return;
603 pythonSetGlobal("Area", area);
604 pythonSetGlobal("Entity", this);
605 pythonSetGlobal("Tile", getTile());
606 tileExitScript->invoke();
609 void Entity::runTileEntryScript()
611 if (!tileEntryScript)
612 return;
613 pythonSetGlobal("Area", area);
614 pythonSetGlobal("Entity", this);
615 pythonSetGlobal("Tile", getTile());
616 tileEntryScript->invoke();
621 * DESCRIPTOR CODE BELOW
624 bool Entity::processDescriptor()
626 XMLRef doc = Reader::getXMLDoc(descriptor, "dtd/entity.dtd");
627 if (!doc)
628 return false;
629 const XMLNode root = doc->root(); // <entity>
630 if (!root)
631 return false;
633 for (XMLNode node = root.childrenNode(); node; node = node.next()) {
634 if (node.is("speed")) {
635 ASSERT(node.doubleContent(&baseSpeed));
636 setSpeed(speedMul); // Calculate speed from tile size.
637 } else if (node.is("sprite")) {
638 ASSERT(processSprite(node.childrenNode()));
639 } else if (node.is("sounds")) {
640 ASSERT(processSounds(node.childrenNode()));
641 } else if (node.is("scripts")) {
642 ASSERT(processScripts(node.childrenNode()));
645 return true;
648 bool Entity::processSprite(XMLNode node)
650 TiledImageRef tiles;
651 for (; node; node = node.next()) {
652 if (node.is("sheet")) {
653 std::string imageSheet = node.content();
654 ASSERT(node.intAttr("tile_width", &imgsz.x) &&
655 node.intAttr("tile_height", &imgsz.y));
656 tiles = Reader::getTiledImage(imageSheet, imgsz.x, imgsz.y);
657 ASSERT(tiles);
658 } else if (node.is("phases")) {
659 ASSERT(processPhases(node.childrenNode(), tiles));
662 return true;
665 bool Entity::processPhases(XMLNode node, const TiledImageRef& tiles)
667 for (; node; node = node.next())
668 if (node.is("phase"))
669 ASSERT(processPhase(node, tiles));
670 return true;
673 bool Entity::processPhase(const XMLNode node, const TiledImageRef& tiles)
675 /* Each phase requires a 'name' and 'frames'. Additionally,
676 * 'speed' is required if 'frames' has more than one member.
678 const std::string name = node.attr("name");
679 if (name.empty()) {
680 Log::err(descriptor, "<phase> name attribute is empty");
681 return false;
684 const std::string framesStr = node.attr("frames");
685 const std::string speedStr = node.attr("speed");
687 if (framesStr.empty()) {
688 Log::err(descriptor, "<phase> frames attribute empty");
689 return false;
692 if (isInteger(framesStr)) {
693 int frame = atoi(framesStr.c_str());
694 if (frame < 0 || (int)tiles->size() < frame) {
695 Log::err(descriptor,
696 "<phase> frames attribute index out of bounds");
697 return false;
699 const ImageRef& image = (*tiles.get())[frame];
700 phases[name] = Animation(image);
702 else if (isRanges(framesStr)) {
703 if (!isDecimal(speedStr)) {
704 Log::err(descriptor,
705 "<phase> speed attribute must be present and "
706 "must be decimal");
708 double fps = atof(speedStr.c_str());
710 typedef std::vector<int> IntVector;
711 IntVector frames = parseRanges(framesStr);
712 std::vector<ImageRef> images;
713 for (IntVector::iterator it = frames.begin(); it != frames.end(); it++) {
714 int i = *it;
715 if (i < 0 || (int)tiles->size() < i) {
716 Log::err(descriptor,
717 "<phase> frames attribute index out of bounds");
718 return false;
720 images.push_back((*tiles.get())[i]);
723 phases[name] = Animation(images, (time_t)(1000.0 / fps));
725 else {
726 Log::err(descriptor,
727 "<phase> frames attribute not an int or int ranges");
728 return false;
731 return true;
734 bool Entity::processSounds(XMLNode node)
736 for (; node; node = node.next())
737 if (node.is("sound"))
738 ASSERT(processSound(node));
739 return true;
742 bool Entity::processSound(const XMLNode node)
744 const std::string name = node.attr("name");
745 const std::string filename = node.content();
746 if (name.empty()) {
747 Log::err(descriptor, "<sound> name attribute is empty");
748 return false;
749 } else if (filename.empty()) {
750 Log::err(descriptor, "<sound></sound> is empty");
751 return false;
754 SampleRef s = Reader::getSample(filename);
755 if (s)
756 sounds[name] = s;
757 return true;
760 bool Entity::processScripts(XMLNode node)
762 for (; node; node = node.next())
763 if (node.is("script"))
764 ASSERT(processScript(node));
765 return true;
768 bool Entity::processScript(const XMLNode node)
770 const std::string trigger = node.attr("trigger");
771 const std::string filename = node.content();
772 if (trigger.empty()) {
773 Log::err(descriptor, "<script> trigger attribute is empty");
774 return false;
775 } else if (filename.empty()) {
776 Log::err(descriptor, "<script></script> is empty");
777 return false;
780 ScriptRef script = Script::create(filename);
781 if (!script || !script->validate())
782 return false;
784 if (!setScript(trigger, script)) {
785 Log::err(descriptor,
786 "unrecognized script trigger: " + trigger);
787 return false;
790 return true;
793 bool Entity::setScript(const std::string& trigger, ScriptRef& script)
795 if (boost::iequals(trigger, "on_tick")) {
796 tickScript = script;
797 return true;
799 if (boost::iequals(trigger, "on_turn")) {
800 turnScript = script;
801 return true;
803 if (boost::equals(trigger, "on_tile_entry")) {
804 tileEntryScript = script;
805 return true;
807 if (boost::iequals(trigger, "on_tile_exit")) {
808 tileExitScript = script;
809 return true;
811 if (boost::iequals(trigger, "on_delete")) {
812 deleteScript = script;
813 return true;
815 return false;
819 void exportEntity()
821 using namespace boost::python;
823 class_<Entity>("Entity", no_init)
824 .def("init", &Entity::init)
825 .def("delete", &Entity::destroy)
826 .add_property("frozen", &Entity::getFrozen, &Entity::setFrozen)
827 .add_property("phase", &Entity::getPhase, &Entity::setPhase)
828 .add_property("area",
829 make_function(&Entity::getArea,
830 return_value_policy<reference_existing_object>()),
831 &Entity::setArea)
832 .add_property("tile", make_function(
833 static_cast<Tile* (Entity::*) ()> (&Entity::getTile),
834 return_value_policy<reference_existing_object>()))
835 .add_property("speed", &Entity::getSpeed, &Entity::setSpeed)
836 .add_property("moving", &Entity::isMoving)
837 .add_property("exempt", &Entity::exemptManip)
838 .add_property("coords", &Entity::getTileCoords_vi)
839 .def("set_coords",
840 static_cast<void (Entity::*) (int,int,double)>
841 (&Entity::setTileCoords))
842 .def("teleport", &Entity::teleport)
843 .def("move", &Entity::move)
844 .def("move_dest",
845 static_cast<vicoord (Entity::*) (Tile*,int,int)>
846 (&Entity::moveDest))
847 .def("can_move",
848 static_cast<bool (Entity::*) (int,int,double)>
849 (&Entity::canMove))
850 // .def_readwrite("on_tick", &Entity::tickScript)
851 // .def_readwrite("on_turn", &Entity::turnScript)
852 // .def_readwrite("on_tile_entry", &Entity::tileEntryScript)
853 // .def_readwrite("on_tile_exit", &Entity::tileExitScript)
854 // .def_readwrite("on_delete", &Entity::deleteScript)