Add license text to the top of every source file.
[Tsunagari.git] / src / entity.cpp
blob82435d864dcd66163d315f14cbdd5d2b07690055
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 "resourcer.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);
100 ivec2 tile = area->getTileDimensions();
102 // If an Entity spans multiple tiles, vertically, we split up the
103 // drawing process into one draw per vertical tile. Each tile "higher"
104 // we go, we add to the draw operation's Z-offset by Z_PER_TILE. This
105 // allows Entities behind one another to be correctly obscured.
106 int hoff = 0;
107 for (int i = 0; i < vtiles; i++) {
108 double z = r.z + (vtiles - i - 1) * Z_PER_TILE;
109 int height = i ? tile.y : imgsz.y % tile.y;
110 img->drawSubrect(
111 doff.x + r.x, doff.y + r.y, z,
112 0 , hoff,
113 imgsz.x , height
115 hoff += height;
119 bool Entity::needsRedraw() const
121 time_t now = World::instance()->time();
122 return redraw || (phase && phase->needsRedraw(now));
126 void Entity::tick(time_t dt)
128 runTickScript();
129 switch (conf.moveMode) {
130 case TURN:
131 tickTurn(dt);
132 break;
133 case TILE:
134 tickTile(dt);
135 break;
136 case NOTILE:
137 tickNoTile(dt);
138 break;
142 void Entity::tickTurn(time_t)
144 // FIXME Characters (!!) don't do anything in TILE mode.
147 void Entity::tickTile(time_t dt)
149 if (!moving)
150 return;
152 redraw = true;
153 double traveled = speed * (double)dt;
154 double destDist = Gosu::distance(r.x, r.y, destCoord.x, destCoord.y);
155 if (destDist <= traveled) {
156 r = destCoord;
157 moving = false;
158 postMove();
159 if (moving) {
160 // Time rollover.
161 double perc = 1.0 - destDist/traveled;
162 time_t remt = (time_t)(perc * (double)dt);
163 tick(remt);
166 else {
167 double angle = atan2(destCoord.y - r.y, destCoord.x - r.x);
168 double dx = cos(angle);
169 double dy = sin(angle);
171 r.x += dx * traveled;
172 r.y += dy * traveled;
176 void Entity::tickNoTile(time_t)
178 // TODO
181 void Entity::turn()
183 runTurnScript();
186 const std::string Entity::getFacing() const
188 return directionStr(facing);
191 bool Entity::setPhase(const std::string& name)
193 enum SetPhaseResult res;
194 res = _setPhase(name);
195 if (res == PHASE_NOTFOUND) {
196 res = _setPhase("stance");
197 if (res == PHASE_NOTFOUND)
198 Log::err(descriptor, "phase '" + name + "' not found");
200 return res == PHASE_CHANGED;
203 std::string Entity::getPhase() const
205 return phaseName;
208 rcoord Entity::getPixelCoord() const
210 return r;
213 icoord Entity::getTileCoords_i() const
215 return area->virt2phys(r);
218 vicoord Entity::getTileCoords_vi() const
220 return area->virt2virt(r);
223 void Entity::setTileCoords(int x, int y)
225 leaveTile();
226 vicoord virt(x, y, r.z);
227 redraw = true;
228 r = area->virt2virt(virt);
229 enterTile();
232 void Entity::setTileCoords(int x, int y, double z)
234 leaveTile();
235 vicoord virt(x, y, z);
236 redraw = true;
237 r = area->virt2virt(virt);
238 enterTile();
241 void Entity::setTileCoords(icoord phys)
243 leaveTile();
244 redraw = true;
245 r = area->phys2virt_r(phys);
246 enterTile();
249 void Entity::setTileCoords(vicoord virt)
251 leaveTile();
252 redraw = true;
253 r = area->virt2virt(virt);
254 enterTile();
257 void Entity::setTileCoords(rcoord virt)
259 leaveTile();
260 redraw = true;
261 r = virt;
262 enterTile();
265 void Entity::teleport(int, int)
267 throw "pure virtual function";
270 icoord Entity::moveDest(ivec2 facing)
272 Tile* tile = getTile();
273 icoord here = getTileCoords_i();
275 if (tile)
276 // Handle layermod.
277 return tile->moveDest(here, facing);
278 else
279 return here + icoord(facing.x, facing.y, 0);
282 // Python API.
283 vicoord Entity::moveDest(Tile* t, int dx, int dy)
285 icoord here = getTileCoords_i();
286 icoord dest;
288 if (t) {
289 // Handle layermod.
290 dest = t->moveDest(here, ivec2(dx, dy));
291 return t->area->phys2virt_vi(dest);
293 else {
294 dest = here + icoord(dx, dy, 0);
295 return area->phys2virt_vi(dest);
299 // Python API.
300 bool Entity::canMove(int x, int y, double z)
302 vicoord virt(x, y, z);
303 return canMove(area->virt2phys(virt));
306 bool Entity::canMove(icoord dest)
308 icoord dxyz = dest - getTileCoords_i();
309 ivec2 dxy(dxyz.x, dxyz.y);
311 Tile* curTile = getTile();
312 this->destTile = area->getTile(dest);
313 this->destCoord = area->phys2virt_r(dest);
315 if ((curTile && curTile->exitAt(dxy)) ||
316 (destTile && destTile->exits[EXIT_NORMAL])) {
317 // We can always take exits as long as we can take exits.
318 // (Even if they would cause us to be out of bounds.)
319 if (nowalkExempt & TILE_NOWALK_EXIT)
320 return true;
323 bool inBounds = area->inBounds(dest);
324 if (destTile && inBounds) {
325 // Tile is inside map. Can we move?
326 if (nowalked(*destTile))
327 return false;
328 if (destTile->entCnt)
329 // Space is occupied by another Entity.
330 return false;
332 return true;
335 // The tile is legitimately off the map.
336 return nowalkExempt & TILE_NOWALK_AREA_BOUND;
339 bool Entity::canMove(vicoord dest)
341 return canMove(area->virt2phys(dest));
344 bool Entity::isMoving() const
346 return moving || stillMoving;
349 void Entity::moveByTile(int x, int y)
351 moveByTile(ivec2(x, y));
354 void Entity::moveByTile(ivec2 delta)
356 if (moving)
357 return;
358 setFacing(delta);
360 if (canMove(moveDest(facing)))
361 preMove();
362 else
363 setPhase(directionStr(facing));
366 void Entity::move(int, int)
368 throw "pure virtual function";
371 Area* Entity::getArea()
373 return area;
376 void Entity::setArea(Area* a)
378 leaveTile();
379 area = a;
380 calcDraw();
381 setSpeed(speedMul); // Calculate new speed based on tile size.
382 enterTile();
385 double Entity::getSpeed() const
387 return speedMul;
390 void Entity::setSpeed(double multiplier)
392 speedMul = multiplier;
393 if (area) {
394 double tilesPerSecond = area->getTileDimensions().x / 1000.0;
395 speed = baseSpeed * speedMul * tilesPerSecond;
399 Tile* Entity::getTile() const
401 return area ? area->getTile(r) : NULL;
404 Tile* Entity::getTile()
406 return area ? area->getTile(r) : NULL;
409 void Entity::setFrozen(bool b)
411 frozen = b;
414 bool Entity::getFrozen()
416 return frozen;
419 FlagManip Entity::exemptManip()
421 return FlagManip(&nowalkExempt);
424 void Entity::erase()
426 throw "pure virtual function";
429 void Entity::calcDraw()
431 ivec2 tile = area->getTileDimensions();
433 // X-axis is centered on tile.
434 doff.x = (tile.x - imgsz.x) / 2;
435 // Y-axis is aligned with bottom of tile.
436 doff.y = tile.y - imgsz.y;
437 // We take up this many tiles, vertically.
438 vtiles = (int)ceilf((float)imgsz.y / (float)tile.y);
441 SampleRef Entity::getSound(const std::string& name) const
443 SampleMap::const_iterator it;
444 it = sounds.find(name);
445 if (it != sounds.end())
446 return it->second;
447 else
448 return SampleRef();
451 ivec2 Entity::setFacing(ivec2 facing)
453 this->facing = ivec2(
454 Gosu::clamp(facing.x, -1, 1),
455 Gosu::clamp(facing.y, -1, 1)
457 return this->facing;
460 const std::string& Entity::directionStr(ivec2 facing) const
462 return directions[facing.y+1][facing.x+1];
465 enum SetPhaseResult Entity::_setPhase(const std::string& name)
467 AnimationMap::iterator it;
468 it = phases.find(name);
469 if (it == phases.end()) {
470 return PHASE_NOTFOUND;
472 Animation* newPhase = &it->second;
473 if (phase != newPhase) {
474 time_t now = World::instance()->time();
475 phase = newPhase;
476 phase->startOver(now, ANIM_INFINITE_CYCLES);
477 phaseName = name;
478 redraw = true;
479 return PHASE_CHANGED;
481 return PHASE_NOTCHANGED;
484 bool Entity::nowalked(Tile& t)
486 unsigned flags = nowalkFlags & ~nowalkExempt;
487 return t.hasFlag(flags);
490 void Entity::preMove()
492 fromCoord = r;
493 fromTile = getTile();
495 rcoord d = destCoord - fromCoord;
496 deltaCoord = area->virt2virt(d);
498 moving = true;
500 // Start moving animation.
501 switch (conf.moveMode) {
502 case TURN:
503 break;
504 case TILE:
505 case NOTILE:
506 setPhase("moving " + getFacing());
507 break;
510 // Process triggers.
511 runTileExitScript();
512 if (fromTile)
513 fromTile->runLeaveScript(this);
515 // Modify tile's entity count.
516 leaveTile();
517 enterTile(destTile);
519 SampleRef step = getSound("step");
520 if (step)
521 step->play();
523 // Set z right away so that we're on-level with the square we're
524 // entering.
525 r.z = destCoord.z;
527 if (conf.moveMode == TURN) {
528 // Movement is instantaneous.
529 redraw = true;
530 r = destCoord;
531 postMove();
533 else {
534 // Movement happens over time. See tickTile().
538 void Entity::postMove()
540 moving = false;
542 if (destTile) {
543 boost::optional<double> layermod = destTile->layermods[EXIT_NORMAL];
544 if (layermod)
545 r.z = *layermod;
548 // Stop moving animation.
549 if (!stillMoving)
550 setPhase(getFacing());
552 // Process triggers.
553 if (destTile)
554 destTile->runEnterScript(this);
556 runTileEntryScript();
558 // TODO: move teleportation here
560 * if (onExit()) {
561 * leaveTile();
562 * moveArea(getExit());
563 * postMoveScript();
564 * enterTile();
569 void Entity::leaveTile()
571 Tile* t = getTile();
572 if (t)
573 t->entCnt--;
576 void Entity::enterTile()
578 Tile* t = getTile();
579 if (t)
580 enterTile(getTile());
583 void Entity::enterTile(Tile* t)
585 if (t)
586 t->entCnt++;
589 void Entity::runTickScript()
591 pythonSetGlobal("Area", area);
592 pythonSetGlobal("Entity", this);
593 pythonSetGlobal("Tile", getTile());
594 tickScript.invoke();
597 void Entity::runTurnScript()
599 pythonSetGlobal("Area", area);
600 pythonSetGlobal("Entity", this);
601 pythonSetGlobal("Tile", getTile());
602 turnScript.invoke();
605 void Entity::runTileExitScript()
607 pythonSetGlobal("Area", area);
608 pythonSetGlobal("Entity", this);
609 pythonSetGlobal("Tile", getTile());
610 tileExitScript.invoke();
613 void Entity::runTileEntryScript()
615 pythonSetGlobal("Area", area);
616 pythonSetGlobal("Entity", this);
617 pythonSetGlobal("Tile", getTile());
618 tileEntryScript.invoke();
623 * DESCRIPTOR CODE BELOW
626 bool Entity::processDescriptor()
628 Resourcer* rc = Resourcer::instance();
629 XMLRef doc = rc->getXMLDoc(descriptor, "dtd/entity.dtd");
630 if (!doc)
631 return false;
632 const XMLNode root = doc->root(); // <entity>
633 if (!root)
634 return false;
636 for (XMLNode node = root.childrenNode(); node; node = node.next()) {
637 if (node.is("speed")) {
638 ASSERT(node.doubleContent(&baseSpeed));
639 setSpeed(speedMul); // Calculate speed from tile size.
640 } else if (node.is("sprite")) {
641 ASSERT(processSprite(node.childrenNode()));
642 } else if (node.is("sounds")) {
643 ASSERT(processSounds(node.childrenNode()));
644 } else if (node.is("scripts")) {
645 ASSERT(processScripts(node.childrenNode()));
648 return true;
651 bool Entity::processSprite(XMLNode node)
653 Resourcer* rc = Resourcer::instance();
654 TiledImageRef tiles;
655 for (; node; node = node.next()) {
656 if (node.is("sheet")) {
657 std::string imageSheet = node.content();
658 ASSERT(node.intAttr("tile_width", &imgsz.x) &&
659 node.intAttr("tile_height", &imgsz.y));
660 tiles = rc->getTiledImage(imageSheet, imgsz.x, imgsz.y);
661 ASSERT(tiles);
662 } else if (node.is("phases")) {
663 ASSERT(processPhases(node.childrenNode(), tiles));
666 return true;
669 bool Entity::processPhases(XMLNode node, const TiledImageRef& tiles)
671 for (; node; node = node.next())
672 if (node.is("phase"))
673 ASSERT(processPhase(node, tiles));
674 return true;
677 bool Entity::processPhase(const XMLNode node, const TiledImageRef& tiles)
679 /* Each phase requires a 'name' and 'frames'. Additionally,
680 * 'speed' is required if 'frames' has more than one member.
682 const std::string name = node.attr("name");
683 if (name.empty()) {
684 Log::err(descriptor, "<phase> name attribute is empty");
685 return false;
688 const std::string framesStr = node.attr("frames");
689 const std::string speedStr = node.attr("speed");
691 if (framesStr.empty()) {
692 Log::err(descriptor, "<phase> frames attribute empty");
693 return false;
696 if (isInteger(framesStr)) {
697 int frame = atoi(framesStr.c_str());
698 if (frame < 0 || (int)tiles->size() < frame) {
699 Log::err(descriptor,
700 "<phase> frames attribute index out of bounds");
701 return false;
703 const ImageRef& image = (*tiles.get())[frame];
704 phases[name] = Animation(image);
706 else if (isRanges(framesStr)) {
707 if (!isDecimal(speedStr)) {
708 Log::err(descriptor,
709 "<phase> speed attribute must be present and "
710 "must be decimal");
712 double fps = atof(speedStr.c_str());
714 std::vector<int> frames = parseRanges(framesStr);
715 std::vector<ImageRef> images;
716 BOOST_FOREACH(int i, frames) {
717 if (i < 0 || (int)tiles->size() < i) {
718 Log::err(descriptor,
719 "<phase> frames attribute index out of bounds");
720 return false;
722 images.push_back((*tiles.get())[i]);
725 phases[name] = Animation(images, (time_t)(1000.0 / fps));
727 else {
728 Log::err(descriptor,
729 "<phase> frames attribute not an int or int ranges");
730 return false;
733 return true;
736 bool Entity::processSounds(XMLNode node)
738 for (; node; node = node.next())
739 if (node.is("sound"))
740 ASSERT(processSound(node));
741 return true;
744 bool Entity::processSound(const XMLNode node)
746 const std::string name = node.attr("name");
747 const std::string filename = node.content();
748 if (name.empty()) {
749 Log::err(descriptor, "<sound> name attribute is empty");
750 return false;
751 } else if (filename.empty()) {
752 Log::err(descriptor, "<sound></sound> is empty");
753 return false;
756 Resourcer* rc = Resourcer::instance();
757 SampleRef s = rc->getSample(filename);
758 if (s)
759 sounds[name] = s;
760 return true;
763 bool Entity::processScripts(XMLNode node)
765 for (; node; node = node.next())
766 if (node.is("script"))
767 ASSERT(processScript(node));
768 return true;
771 bool Entity::processScript(const XMLNode node)
773 const std::string trigger = node.attr("trigger");
774 const std::string filename = node.content();
775 if (trigger.empty()) {
776 Log::err(descriptor, "<script> trigger attribute is empty");
777 return false;
778 } else if (filename.empty()) {
779 Log::err(descriptor, "<script></script> is empty");
780 return false;
783 ScriptInst script(filename);
784 if (!script.validate(descriptor))
785 return false;
787 if (!setScript(trigger, script)) {
788 Log::err(descriptor,
789 "unrecognized script trigger: " + trigger);
790 return false;
793 return true;
796 bool Entity::setScript(const std::string& trigger, ScriptInst& script)
798 if (boost::iequals(trigger, "on_tick")) {
799 tickScript = script;
800 return true;
802 if (boost::iequals(trigger, "on_turn")) {
803 turnScript = script;
804 return true;
806 if (boost::equals(trigger, "on_tile_entry")) {
807 tileEntryScript = script;
808 return true;
810 if (boost::iequals(trigger, "on_tile_exit")) {
811 tileExitScript = script;
812 return true;
814 if (boost::iequals(trigger, "on_delete")) {
815 deleteScript = script;
816 return true;
818 return false;
822 void exportEntity()
824 using namespace boost::python;
826 class_<Entity>("Entity", no_init)
827 .def("init", &Entity::init)
828 .def("delete", &Entity::destroy)
829 .add_property("frozen", &Entity::getFrozen, &Entity::setFrozen)
830 .add_property("phase", &Entity::getPhase, &Entity::setPhase)
831 .add_property("area",
832 make_function(&Entity::getArea,
833 return_value_policy<reference_existing_object>()),
834 &Entity::setArea)
835 .add_property("tile", make_function(
836 static_cast<Tile* (Entity::*) ()> (&Entity::getTile),
837 return_value_policy<reference_existing_object>()))
838 .add_property("speed", &Entity::getSpeed, &Entity::setSpeed)
839 .add_property("moving", &Entity::isMoving)
840 .add_property("exempt", &Entity::exemptManip)
841 .add_property("coords", &Entity::getTileCoords_vi)
842 .def("set_coords",
843 static_cast<void (Entity::*) (int,int,double)>
844 (&Entity::setTileCoords))
845 .def("teleport", &Entity::teleport)
846 .def("move", &Entity::move)
847 .def("move_dest",
848 static_cast<vicoord (Entity::*) (Tile*,int,int)>
849 (&Entity::moveDest))
850 .def("can_move",
851 static_cast<bool (Entity::*) (int,int,double)>
852 (&Entity::canMove))
853 .def_readwrite("on_tick", &Entity::tickScript)
854 .def_readwrite("on_turn", &Entity::turnScript)
855 .def_readwrite("on_tile_entry", &Entity::tileEntryScript)
856 .def_readwrite("on_tile_exit", &Entity::tileExitScript)
857 .def_readwrite("on_delete", &Entity::deleteScript)