1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
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
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>
39 #include "resourcer.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"},
61 nowalkFlags(TILE_NOWALK
| TILE_NOWALK_NPC
),
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()
86 area
->requestRedraw();
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.
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
;
111 doff
.x
+ r
.x
, doff
.y
+ r
.y
, z
,
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
)
129 switch (conf
.moveMode
) {
142 void Entity::tickTurn(time_t)
144 // FIXME Characters (!!) don't do anything in TILE mode.
147 void Entity::tickTile(time_t dt
)
153 double traveled
= speed
* (double)dt
;
154 double destDist
= Gosu::distance(r
.x
, r
.y
, destCoord
.x
, destCoord
.y
);
155 if (destDist
<= traveled
) {
161 double perc
= 1.0 - destDist
/traveled
;
162 time_t remt
= (time_t)(perc
* (double)dt
);
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)
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
208 rcoord
Entity::getPixelCoord() const
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
)
226 vicoord
virt(x
, y
, r
.z
);
228 r
= area
->virt2virt(virt
);
232 void Entity::setTileCoords(int x
, int y
, double z
)
235 vicoord
virt(x
, y
, z
);
237 r
= area
->virt2virt(virt
);
241 void Entity::setTileCoords(icoord phys
)
245 r
= area
->phys2virt_r(phys
);
249 void Entity::setTileCoords(vicoord virt
)
253 r
= area
->virt2virt(virt
);
257 void Entity::setTileCoords(rcoord virt
)
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();
277 return tile
->moveDest(here
, facing
);
279 return here
+ icoord(facing
.x
, facing
.y
, 0);
283 vicoord
Entity::moveDest(Tile
* t
, int dx
, int dy
)
285 icoord here
= getTileCoords_i();
290 dest
= t
->moveDest(here
, ivec2(dx
, dy
));
291 return t
->area
->phys2virt_vi(dest
);
294 dest
= here
+ icoord(dx
, dy
, 0);
295 return area
->phys2virt_vi(dest
);
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
)
323 bool inBounds
= area
->inBounds(dest
);
324 if (destTile
&& inBounds
) {
325 // Tile is inside map. Can we move?
326 if (nowalked(*destTile
))
328 if (destTile
->entCnt
)
329 // Space is occupied by another Entity.
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
)
360 if (canMove(moveDest(facing
)))
363 setPhase(directionStr(facing
));
366 void Entity::move(int, int)
368 throw "pure virtual function";
371 Area
* Entity::getArea()
376 void Entity::setArea(Area
* a
)
381 setSpeed(speedMul
); // Calculate new speed based on tile size.
385 double Entity::getSpeed() const
390 void Entity::setSpeed(double multiplier
)
392 speedMul
= multiplier
;
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
)
414 bool Entity::getFrozen()
419 FlagManip
Entity::exemptManip()
421 return FlagManip(&nowalkExempt
);
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())
451 ivec2
Entity::setFacing(ivec2 facing
)
453 this->facing
= ivec2(
454 Gosu::clamp(facing
.x
, -1, 1),
455 Gosu::clamp(facing
.y
, -1, 1)
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();
476 phase
->startOver(now
, ANIM_INFINITE_CYCLES
);
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()
493 fromTile
= getTile();
495 rcoord d
= destCoord
- fromCoord
;
496 deltaCoord
= area
->virt2virt(d
);
500 // Start moving animation.
501 switch (conf
.moveMode
) {
506 setPhase("moving " + getFacing());
513 fromTile
->runLeaveScript(this);
515 // Modify tile's entity count.
519 SampleRef step
= getSound("step");
523 // Set z right away so that we're on-level with the square we're
527 if (conf
.moveMode
== TURN
) {
528 // Movement is instantaneous.
534 // Movement happens over time. See tickTile().
538 void Entity::postMove()
543 boost::optional
<double> layermod
= destTile
->layermods
[EXIT_NORMAL
];
548 // Stop moving animation.
550 setPhase(getFacing());
554 destTile
->runEnterScript(this);
556 runTileEntryScript();
558 // TODO: move teleportation here
562 * moveArea(getExit());
569 void Entity::leaveTile()
576 void Entity::enterTile()
580 enterTile(getTile());
583 void Entity::enterTile(Tile
* t
)
589 void Entity::runTickScript()
591 pythonSetGlobal("Area", area
);
592 pythonSetGlobal("Entity", this);
593 pythonSetGlobal("Tile", getTile());
597 void Entity::runTurnScript()
599 pythonSetGlobal("Area", area
);
600 pythonSetGlobal("Entity", this);
601 pythonSetGlobal("Tile", getTile());
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");
632 const XMLNode root
= doc
->root(); // <entity>
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()));
651 bool Entity::processSprite(XMLNode node
)
653 Resourcer
* rc
= Resourcer::instance();
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
);
662 } else if (node
.is("phases")) {
663 ASSERT(processPhases(node
.childrenNode(), tiles
));
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
));
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");
684 Log::err(descriptor
, "<phase> name attribute is empty");
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");
696 if (isInteger(framesStr
)) {
697 int frame
= atoi(framesStr
.c_str());
698 if (frame
< 0 || (int)tiles
->size() < frame
) {
700 "<phase> frames attribute index out of bounds");
703 const ImageRef
& image
= (*tiles
.get())[frame
];
704 phases
[name
] = Animation(image
);
706 else if (isRanges(framesStr
)) {
707 if (!isDecimal(speedStr
)) {
709 "<phase> speed attribute must be present and "
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
) {
719 "<phase> frames attribute index out of bounds");
722 images
.push_back((*tiles
.get())[i
]);
725 phases
[name
] = Animation(images
, (time_t)(1000.0 / fps
));
729 "<phase> frames attribute not an int or int ranges");
736 bool Entity::processSounds(XMLNode node
)
738 for (; node
; node
= node
.next())
739 if (node
.is("sound"))
740 ASSERT(processSound(node
));
744 bool Entity::processSound(const XMLNode node
)
746 const std::string name
= node
.attr("name");
747 const std::string filename
= node
.content();
749 Log::err(descriptor
, "<sound> name attribute is empty");
751 } else if (filename
.empty()) {
752 Log::err(descriptor
, "<sound></sound> is empty");
756 Resourcer
* rc
= Resourcer::instance();
757 SampleRef s
= rc
->getSample(filename
);
763 bool Entity::processScripts(XMLNode node
)
765 for (; node
; node
= node
.next())
766 if (node
.is("script"))
767 ASSERT(processScript(node
));
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");
778 } else if (filename
.empty()) {
779 Log::err(descriptor
, "<script></script> is empty");
783 ScriptInst
script(filename
);
784 if (!script
.validate(descriptor
))
787 if (!setScript(trigger
, script
)) {
789 "unrecognized script trigger: " + trigger
);
796 bool Entity::setScript(const std::string
& trigger
, ScriptInst
& script
)
798 if (boost::iequals(trigger
, "on_tick")) {
802 if (boost::iequals(trigger
, "on_turn")) {
806 if (boost::equals(trigger
, "on_tile_entry")) {
807 tileEntryScript
= script
;
810 if (boost::iequals(trigger
, "on_tile_exit")) {
811 tileExitScript
= script
;
814 if (boost::iequals(trigger
, "on_delete")) {
815 deleteScript
= script
;
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
>()),
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
)
843 static_cast<void (Entity::*) (int,int,double)>
844 (&Entity::setTileCoords
))
845 .def("teleport", &Entity::teleport
)
846 .def("move", &Entity::move
)
848 static_cast<vicoord (Entity::*) (Tile
*,int,int)>
851 static_cast<bool (Entity::*) (int,int,double)>
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
)