Resourcer => Reader, singletonize reader, music, getSong => Music
[Tsunagari.git] / src / entity.cpp
blob2af114f633a1068928ca8ee36ef1f7515aadbea1
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** entity.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
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 <boost/foreach.hpp>
31 #include <Gosu/Image.hpp>
32 #include <Gosu/Math.hpp>
33 #include <Gosu/Timing.hpp>
35 #include "area.h"
36 #include "entity.h"
37 #include "log.h"
38 #include "python.h"
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 pythonSetGlobal("Area", area);
71 pythonSetGlobal("Entity", this);
72 deleteScript.invoke();
75 bool Entity::init(const std::string& descriptor)
77 this->descriptor = descriptor;
78 return processDescriptor();
81 void Entity::destroy()
83 leaveTile();
84 if (area) {
85 erase();
86 area->requestRedraw();
88 area = NULL;
89 delete this;
92 void Entity::draw()
94 redraw = false;
95 if (!phase)
96 return;
98 time_t now = World::instance()->time();
99 Image* img = phase->frame(now);
101 img->draw(
102 doff.x + r.x,
103 doff.y + r.y,
104 r.z + area->isometricZOff(rvec2(r.x, r.y))
108 bool Entity::needsRedraw() const
110 time_t now = World::instance()->time();
111 return redraw || (phase && phase->needsRedraw(now));
115 void Entity::tick(time_t dt)
117 runTickScript();
118 switch (conf.moveMode) {
119 case TURN:
120 tickTurn(dt);
121 break;
122 case TILE:
123 tickTile(dt);
124 break;
125 case NOTILE:
126 tickNoTile(dt);
127 break;
131 void Entity::tickTurn(time_t)
133 // FIXME Characters (!!) don't do anything in TILE mode.
136 void Entity::tickTile(time_t dt)
138 if (!moving)
139 return;
141 redraw = true;
142 double traveled = speed * (double)dt;
143 double destDist = Gosu::distance(r.x, r.y, destCoord.x, destCoord.y);
144 if (destDist <= traveled) {
145 r = destCoord;
146 moving = false;
147 postMove();
148 if (moving) {
149 // Time rollover.
150 double perc = 1.0 - destDist/traveled;
151 time_t remt = (time_t)(perc * (double)dt);
152 tick(remt);
155 else {
156 double angle = atan2(destCoord.y - r.y, destCoord.x - r.x);
157 double dx = cos(angle);
158 double dy = sin(angle);
160 r.x += dx * traveled;
161 r.y += dy * traveled;
165 void Entity::tickNoTile(time_t)
167 // TODO
170 void Entity::turn()
172 runTurnScript();
175 const std::string Entity::getFacing() const
177 return directionStr(facing);
180 bool Entity::setPhase(const std::string& name)
182 enum SetPhaseResult res;
183 res = _setPhase(name);
184 if (res == PHASE_NOTFOUND) {
185 res = _setPhase("stance");
186 if (res == PHASE_NOTFOUND)
187 Log::err(descriptor, "phase '" + name + "' not found");
189 return res == PHASE_CHANGED;
192 std::string Entity::getPhase() const
194 return phaseName;
197 rcoord Entity::getPixelCoord() const
199 return r;
202 icoord Entity::getTileCoords_i() const
204 return area->virt2phys(r);
207 vicoord Entity::getTileCoords_vi() const
209 return area->virt2virt(r);
212 void Entity::setTileCoords(int x, int y)
214 leaveTile();
215 vicoord virt(x, y, r.z);
216 redraw = true;
217 r = area->virt2virt(virt);
218 enterTile();
221 void Entity::setTileCoords(int x, int y, double z)
223 leaveTile();
224 vicoord virt(x, y, z);
225 redraw = true;
226 r = area->virt2virt(virt);
227 enterTile();
230 void Entity::setTileCoords(icoord phys)
232 leaveTile();
233 redraw = true;
234 r = area->phys2virt_r(phys);
235 enterTile();
238 void Entity::setTileCoords(vicoord virt)
240 leaveTile();
241 redraw = true;
242 r = area->virt2virt(virt);
243 enterTile();
246 void Entity::setTileCoords(rcoord virt)
248 leaveTile();
249 redraw = true;
250 r = virt;
251 enterTile();
254 void Entity::teleport(int, int)
256 throw "pure virtual function";
259 icoord Entity::moveDest(ivec2 facing)
261 Tile* tile = getTile();
262 icoord here = getTileCoords_i();
264 if (tile)
265 // Handle layermod.
266 return tile->moveDest(here, facing);
267 else
268 return here + icoord(facing.x, facing.y, 0);
271 // Python API.
272 vicoord Entity::moveDest(Tile* t, int dx, int dy)
274 icoord here = getTileCoords_i();
275 icoord dest;
277 if (t) {
278 // Handle layermod.
279 dest = t->moveDest(here, ivec2(dx, dy));
280 return t->area->phys2virt_vi(dest);
282 else {
283 dest = here + icoord(dx, dy, 0);
284 return area->phys2virt_vi(dest);
288 // Python API.
289 bool Entity::canMove(int x, int y, double z)
291 vicoord virt(x, y, z);
292 return canMove(area->virt2phys(virt));
295 bool Entity::canMove(icoord dest)
297 icoord dxyz = dest - getTileCoords_i();
298 ivec2 dxy(dxyz.x, dxyz.y);
300 Tile* curTile = getTile();
301 this->destTile = area->getTile(dest);
302 this->destCoord = area->phys2virt_r(dest);
304 if ((curTile && curTile->exitAt(dxy)) ||
305 (destTile && destTile->exits[EXIT_NORMAL])) {
306 // We can always take exits as long as we can take exits.
307 // (Even if they would cause us to be out of bounds.)
308 if (nowalkExempt & TILE_NOWALK_EXIT)
309 return true;
312 bool inBounds = area->inBounds(dest);
313 if (destTile && inBounds) {
314 // Tile is inside map. Can we move?
315 if (nowalked(*destTile))
316 return false;
317 if (destTile->entCnt)
318 // Space is occupied by another Entity.
319 return false;
321 return true;
324 // The tile is legitimately off the map.
325 return nowalkExempt & TILE_NOWALK_AREA_BOUND;
328 bool Entity::canMove(vicoord dest)
330 return canMove(area->virt2phys(dest));
333 bool Entity::isMoving() const
335 return moving || stillMoving;
338 void Entity::moveByTile(int x, int y)
340 moveByTile(ivec2(x, y));
343 void Entity::moveByTile(ivec2 delta)
345 if (moving)
346 return;
347 setFacing(delta);
349 if (canMove(moveDest(facing)))
350 preMove();
351 else
352 setPhase(directionStr(facing));
355 void Entity::move(int, int)
357 throw "pure virtual function";
360 Area* Entity::getArea()
362 return area;
365 void Entity::setArea(Area* a)
367 leaveTile();
368 area = a;
369 calcDraw();
370 setSpeed(speedMul); // Calculate new speed based on tile size.
371 enterTile();
374 double Entity::getSpeed() const
376 return speedMul;
379 void Entity::setSpeed(double multiplier)
381 speedMul = multiplier;
382 if (area) {
383 double tilesPerSecond = area->getTileDimensions().x / 1000.0;
384 speed = baseSpeed * speedMul * tilesPerSecond;
388 Tile* Entity::getTile() const
390 return area ? area->getTile(r) : NULL;
393 Tile* Entity::getTile()
395 return area ? area->getTile(r) : NULL;
398 void Entity::setFrozen(bool b)
400 frozen = b;
403 bool Entity::getFrozen()
405 return frozen;
408 FlagManip Entity::exemptManip()
410 return FlagManip(&nowalkExempt);
413 void Entity::erase()
415 throw "pure virtual function";
418 void Entity::calcDraw()
420 ivec2 tile = area->getTileDimensions();
422 // X-axis is centered on tile.
423 doff.x = (tile.x - imgsz.x) / 2;
424 // Y-axis is aligned with bottom of tile.
425 doff.y = tile.y - imgsz.y;
428 SampleRef Entity::getSound(const std::string& name) const
430 SampleMap::const_iterator it;
431 it = sounds.find(name);
432 if (it != sounds.end())
433 return it->second;
434 else
435 return SampleRef();
438 ivec2 Entity::setFacing(ivec2 facing)
440 this->facing = ivec2(
441 Gosu::clamp(facing.x, -1, 1),
442 Gosu::clamp(facing.y, -1, 1)
444 return this->facing;
447 const std::string& Entity::directionStr(ivec2 facing) const
449 return directions[facing.y+1][facing.x+1];
452 enum SetPhaseResult Entity::_setPhase(const std::string& name)
454 AnimationMap::iterator it;
455 it = phases.find(name);
456 if (it == phases.end()) {
457 return PHASE_NOTFOUND;
459 Animation* newPhase = &it->second;
460 if (phase != newPhase) {
461 time_t now = World::instance()->time();
462 phase = newPhase;
463 phase->startOver(now, ANIM_INFINITE_CYCLES);
464 phaseName = name;
465 redraw = true;
466 return PHASE_CHANGED;
468 return PHASE_NOTCHANGED;
471 bool Entity::nowalked(Tile& t)
473 unsigned flags = nowalkFlags & ~nowalkExempt;
474 return t.hasFlag(flags);
477 void Entity::preMove()
479 fromCoord = r;
480 fromTile = getTile();
482 rcoord d = destCoord - fromCoord;
483 deltaCoord = area->virt2virt(d);
485 moving = true;
487 // Start moving animation.
488 switch (conf.moveMode) {
489 case TURN:
490 break;
491 case TILE:
492 case NOTILE:
493 setPhase("moving " + getFacing());
494 break;
497 // Process triggers.
498 runTileExitScript();
499 if (fromTile)
500 fromTile->runLeaveScript(this);
502 // Modify tile's entity count.
503 leaveTile();
504 enterTile(destTile);
506 SampleRef step = getSound("step");
507 if (step)
508 step->play();
510 // Set z right away so that we're on-level with the square we're
511 // entering.
512 r.z = destCoord.z;
514 if (conf.moveMode == TURN) {
515 // Movement is instantaneous.
516 redraw = true;
517 r = destCoord;
518 postMove();
520 else {
521 // Movement happens over time. See tickTile().
525 void Entity::postMove()
527 moving = false;
529 if (destTile) {
530 boost::optional<double> layermod = destTile->layermods[EXIT_NORMAL];
531 if (layermod)
532 r.z = *layermod;
535 // Stop moving animation.
536 if (!stillMoving)
537 setPhase(getFacing());
539 // Process triggers.
540 if (destTile)
541 destTile->runEnterScript(this);
543 runTileEntryScript();
545 // TODO: move teleportation here
547 * if (onExit()) {
548 * leaveTile();
549 * moveArea(getExit());
550 * postMoveScript();
551 * enterTile();
556 void Entity::leaveTile()
558 Tile* t = getTile();
559 if (t)
560 t->entCnt--;
563 void Entity::enterTile()
565 Tile* t = getTile();
566 if (t)
567 enterTile(getTile());
570 void Entity::enterTile(Tile* t)
572 if (t)
573 t->entCnt++;
576 void Entity::runTickScript()
578 pythonSetGlobal("Area", area);
579 pythonSetGlobal("Entity", this);
580 pythonSetGlobal("Tile", getTile());
581 tickScript.invoke();
584 void Entity::runTurnScript()
586 pythonSetGlobal("Area", area);
587 pythonSetGlobal("Entity", this);
588 pythonSetGlobal("Tile", getTile());
589 turnScript.invoke();
592 void Entity::runTileExitScript()
594 pythonSetGlobal("Area", area);
595 pythonSetGlobal("Entity", this);
596 pythonSetGlobal("Tile", getTile());
597 tileExitScript.invoke();
600 void Entity::runTileEntryScript()
602 pythonSetGlobal("Area", area);
603 pythonSetGlobal("Entity", this);
604 pythonSetGlobal("Tile", getTile());
605 tileEntryScript.invoke();
610 * DESCRIPTOR CODE BELOW
613 bool Entity::processDescriptor()
615 XMLRef doc = Reader::getXMLDoc(descriptor, "dtd/entity.dtd");
616 if (!doc)
617 return false;
618 const XMLNode root = doc->root(); // <entity>
619 if (!root)
620 return false;
622 for (XMLNode node = root.childrenNode(); node; node = node.next()) {
623 if (node.is("speed")) {
624 ASSERT(node.doubleContent(&baseSpeed));
625 setSpeed(speedMul); // Calculate speed from tile size.
626 } else if (node.is("sprite")) {
627 ASSERT(processSprite(node.childrenNode()));
628 } else if (node.is("sounds")) {
629 ASSERT(processSounds(node.childrenNode()));
630 } else if (node.is("scripts")) {
631 ASSERT(processScripts(node.childrenNode()));
634 return true;
637 bool Entity::processSprite(XMLNode node)
639 TiledImageRef tiles;
640 for (; node; node = node.next()) {
641 if (node.is("sheet")) {
642 std::string imageSheet = node.content();
643 ASSERT(node.intAttr("tile_width", &imgsz.x) &&
644 node.intAttr("tile_height", &imgsz.y));
645 tiles = Reader::getTiledImage(imageSheet, imgsz.x, imgsz.y);
646 ASSERT(tiles);
647 } else if (node.is("phases")) {
648 ASSERT(processPhases(node.childrenNode(), tiles));
651 return true;
654 bool Entity::processPhases(XMLNode node, const TiledImageRef& tiles)
656 for (; node; node = node.next())
657 if (node.is("phase"))
658 ASSERT(processPhase(node, tiles));
659 return true;
662 bool Entity::processPhase(const XMLNode node, const TiledImageRef& tiles)
664 /* Each phase requires a 'name' and 'frames'. Additionally,
665 * 'speed' is required if 'frames' has more than one member.
667 const std::string name = node.attr("name");
668 if (name.empty()) {
669 Log::err(descriptor, "<phase> name attribute is empty");
670 return false;
673 const std::string framesStr = node.attr("frames");
674 const std::string speedStr = node.attr("speed");
676 if (framesStr.empty()) {
677 Log::err(descriptor, "<phase> frames attribute empty");
678 return false;
681 if (isInteger(framesStr)) {
682 int frame = atoi(framesStr.c_str());
683 if (frame < 0 || (int)tiles->size() < frame) {
684 Log::err(descriptor,
685 "<phase> frames attribute index out of bounds");
686 return false;
688 const ImageRef& image = (*tiles.get())[frame];
689 phases[name] = Animation(image);
691 else if (isRanges(framesStr)) {
692 if (!isDecimal(speedStr)) {
693 Log::err(descriptor,
694 "<phase> speed attribute must be present and "
695 "must be decimal");
697 double fps = atof(speedStr.c_str());
699 std::vector<int> frames = parseRanges(framesStr);
700 std::vector<ImageRef> images;
701 BOOST_FOREACH(int i, frames) {
702 if (i < 0 || (int)tiles->size() < i) {
703 Log::err(descriptor,
704 "<phase> frames attribute index out of bounds");
705 return false;
707 images.push_back((*tiles.get())[i]);
710 phases[name] = Animation(images, (time_t)(1000.0 / fps));
712 else {
713 Log::err(descriptor,
714 "<phase> frames attribute not an int or int ranges");
715 return false;
718 return true;
721 bool Entity::processSounds(XMLNode node)
723 for (; node; node = node.next())
724 if (node.is("sound"))
725 ASSERT(processSound(node));
726 return true;
729 bool Entity::processSound(const XMLNode node)
731 const std::string name = node.attr("name");
732 const std::string filename = node.content();
733 if (name.empty()) {
734 Log::err(descriptor, "<sound> name attribute is empty");
735 return false;
736 } else if (filename.empty()) {
737 Log::err(descriptor, "<sound></sound> is empty");
738 return false;
741 SampleRef s = Reader::getSample(filename);
742 if (s)
743 sounds[name] = s;
744 return true;
747 bool Entity::processScripts(XMLNode node)
749 for (; node; node = node.next())
750 if (node.is("script"))
751 ASSERT(processScript(node));
752 return true;
755 bool Entity::processScript(const XMLNode node)
757 const std::string trigger = node.attr("trigger");
758 const std::string filename = node.content();
759 if (trigger.empty()) {
760 Log::err(descriptor, "<script> trigger attribute is empty");
761 return false;
762 } else if (filename.empty()) {
763 Log::err(descriptor, "<script></script> is empty");
764 return false;
767 ScriptInst script(filename);
768 if (!script.validate(descriptor))
769 return false;
771 if (!setScript(trigger, script)) {
772 Log::err(descriptor,
773 "unrecognized script trigger: " + trigger);
774 return false;
777 return true;
780 bool Entity::setScript(const std::string& trigger, ScriptInst& script)
782 if (boost::iequals(trigger, "on_tick")) {
783 tickScript = script;
784 return true;
786 if (boost::iequals(trigger, "on_turn")) {
787 turnScript = script;
788 return true;
790 if (boost::equals(trigger, "on_tile_entry")) {
791 tileEntryScript = script;
792 return true;
794 if (boost::iequals(trigger, "on_tile_exit")) {
795 tileExitScript = script;
796 return true;
798 if (boost::iequals(trigger, "on_delete")) {
799 deleteScript = script;
800 return true;
802 return false;
806 void exportEntity()
808 using namespace boost::python;
810 class_<Entity>("Entity", no_init)
811 .def("init", &Entity::init)
812 .def("delete", &Entity::destroy)
813 .add_property("frozen", &Entity::getFrozen, &Entity::setFrozen)
814 .add_property("phase", &Entity::getPhase, &Entity::setPhase)
815 .add_property("area",
816 make_function(&Entity::getArea,
817 return_value_policy<reference_existing_object>()),
818 &Entity::setArea)
819 .add_property("tile", make_function(
820 static_cast<Tile* (Entity::*) ()> (&Entity::getTile),
821 return_value_policy<reference_existing_object>()))
822 .add_property("speed", &Entity::getSpeed, &Entity::setSpeed)
823 .add_property("moving", &Entity::isMoving)
824 .add_property("exempt", &Entity::exemptManip)
825 .add_property("coords", &Entity::getTileCoords_vi)
826 .def("set_coords",
827 static_cast<void (Entity::*) (int,int,double)>
828 (&Entity::setTileCoords))
829 .def("teleport", &Entity::teleport)
830 .def("move", &Entity::move)
831 .def("move_dest",
832 static_cast<vicoord (Entity::*) (Tile*,int,int)>
833 (&Entity::moveDest))
834 .def("can_move",
835 static_cast<bool (Entity::*) (int,int,double)>
836 (&Entity::canMove))
837 .def_readwrite("on_tick", &Entity::tickScript)
838 .def_readwrite("on_turn", &Entity::turnScript)
839 .def_readwrite("on_tile_entry", &Entity::tileEntryScript)
840 .def_readwrite("on_tile_exit", &Entity::tileExitScript)
841 .def_readwrite("on_delete", &Entity::deleteScript)