1 /***************************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2013 PariahSoft LLC **
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 <Gosu/Image.hpp>
31 #include <Gosu/Math.hpp>
32 #include <Gosu/Timing.hpp>
35 #include "client-conf.h"
39 #include "python-bindings-template.cpp"
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"},
62 nowalkFlags(TILE_NOWALK
| TILE_NOWALK_NPC
),
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()
89 area
->requestRedraw();
101 time_t now
= World::instance()->time();
102 Image
* img
= phase
->frame(now
);
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
)
121 switch (conf
.moveMode
) {
134 void Entity::tickTurn(time_t)
136 // FIXME Characters (!!) don't do anything in TILE mode.
139 void Entity::tickTile(time_t dt
)
145 double traveled
= speed
* (double)dt
;
146 double destDist
= Gosu::distance(r
.x
, r
.y
, destCoord
.x
, destCoord
.y
);
147 if (destDist
<= traveled
) {
153 double perc
= 1.0 - destDist
/traveled
;
154 time_t remt
= (time_t)(perc
* (double)dt
);
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)
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
200 rcoord
Entity::getPixelCoord() const
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
)
218 vicoord
virt(x
, y
, r
.z
);
220 r
= area
->virt2virt(virt
);
224 void Entity::setTileCoords(int x
, int y
, double z
)
227 vicoord
virt(x
, y
, z
);
229 r
= area
->virt2virt(virt
);
233 void Entity::setTileCoords(icoord phys
)
237 r
= area
->phys2virt_r(phys
);
241 void Entity::setTileCoords(vicoord virt
)
245 r
= area
->virt2virt(virt
);
249 void Entity::setTileCoords(rcoord virt
)
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();
269 return tile
->moveDest(here
, facing
);
271 return here
+ icoord(facing
.x
, facing
.y
, 0);
275 vicoord
Entity::moveDest(Tile
* t
, int dx
, int dy
)
277 icoord here
= getTileCoords_i();
282 dest
= t
->moveDest(here
, ivec2(dx
, dy
));
283 return t
->area
->phys2virt_vi(dest
);
286 dest
= here
+ icoord(dx
, dy
, 0);
287 return area
->phys2virt_vi(dest
);
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
)
315 bool inBounds
= area
->inBounds(dest
);
316 if (destTile
&& inBounds
) {
317 // Tile is inside map. Can we move?
318 if (nowalked(*destTile
))
320 if (destTile
->entCnt
)
321 // Space is occupied by another Entity.
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
)
352 if (canMove(moveDest(facing
)))
355 setPhase(directionStr(facing
));
358 void Entity::move(int, int)
360 throw "pure virtual function";
363 Area
* Entity::getArea()
368 void Entity::setArea(Area
* a
)
373 setSpeed(speedMul
); // Calculate new speed based on tile size.
377 double Entity::getSpeed() const
382 void Entity::setSpeed(double multiplier
)
384 speedMul
= multiplier
;
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
)
406 bool Entity::getFrozen()
411 FlagManip
Entity::exemptManip()
413 return FlagManip(&nowalkExempt
);
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())
441 ivec2
Entity::setFacing(ivec2 facing
)
443 this->facing
= ivec2(
444 Gosu::clamp(facing
.x
, -1, 1),
445 Gosu::clamp(facing
.y
, -1, 1)
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();
466 phase
->startOver(now
, ANIM_INFINITE_CYCLES
);
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()
483 fromTile
= getTile();
485 rcoord d
= destCoord
- fromCoord
;
486 deltaCoord
= area
->virt2virt(d
);
490 // Start moving animation.
491 switch (conf
.moveMode
) {
496 setPhase("moving " + getFacing());
503 fromTile
->runLeaveScript(this);
505 // Modify tile's entity count.
509 SampleRef step
= getSound("step");
513 // Set z right away so that we're on-level with the square we're
517 if (conf
.moveMode
== TURN
) {
518 // Movement is instantaneous.
524 // Movement happens over time. See tickTile().
528 void Entity::postMove()
533 double* layermod
= destTile
->layermods
[EXIT_NORMAL
];
538 // Stop moving animation.
540 setPhase(getFacing());
544 destTile
->runEnterScript(this);
546 runTileEntryScript();
548 // TODO: move teleportation here
552 * moveArea(getExit());
559 void Entity::leaveTile()
566 void Entity::enterTile()
570 enterTile(getTile());
573 void Entity::enterTile(Tile
* t
)
579 void Entity::runTickScript()
583 pythonSetGlobal("Area", area
);
584 pythonSetGlobal("Entity", this);
585 pythonSetGlobal("Tile", getTile());
586 tickScript
->invoke();
589 void Entity::runTurnScript()
593 pythonSetGlobal("Area", area
);
594 pythonSetGlobal("Entity", this);
595 pythonSetGlobal("Tile", getTile());
596 turnScript
->invoke();
599 void Entity::runTileExitScript()
603 pythonSetGlobal("Area", area
);
604 pythonSetGlobal("Entity", this);
605 pythonSetGlobal("Tile", getTile());
606 tileExitScript
->invoke();
609 void Entity::runTileEntryScript()
611 if (!tileEntryScript
)
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");
629 const XMLNode root
= doc
->root(); // <entity>
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()));
648 bool Entity::processSprite(XMLNode node
)
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
);
658 } else if (node
.is("phases")) {
659 ASSERT(processPhases(node
.childrenNode(), tiles
));
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
));
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");
680 Log::err(descriptor
, "<phase> name attribute is empty");
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");
692 if (isInteger(framesStr
)) {
693 int frame
= atoi(framesStr
.c_str());
694 if (frame
< 0 || (int)tiles
->size() < frame
) {
696 "<phase> frames attribute index out of bounds");
699 const ImageRef
& image
= (*tiles
.get())[frame
];
700 phases
[name
] = Animation(image
);
702 else if (isRanges(framesStr
)) {
703 if (!isDecimal(speedStr
)) {
705 "<phase> speed attribute must be present and "
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
++) {
715 if (i
< 0 || (int)tiles
->size() < i
) {
717 "<phase> frames attribute index out of bounds");
720 images
.push_back((*tiles
.get())[i
]);
723 phases
[name
] = Animation(images
, (time_t)(1000.0 / fps
));
727 "<phase> frames attribute not an int or int ranges");
734 bool Entity::processSounds(XMLNode node
)
736 for (; node
; node
= node
.next())
737 if (node
.is("sound"))
738 ASSERT(processSound(node
));
742 bool Entity::processSound(const XMLNode node
)
744 const std::string name
= node
.attr("name");
745 const std::string filename
= node
.content();
747 Log::err(descriptor
, "<sound> name attribute is empty");
749 } else if (filename
.empty()) {
750 Log::err(descriptor
, "<sound></sound> is empty");
754 SampleRef s
= Reader::getSample(filename
);
760 bool Entity::processScripts(XMLNode node
)
762 for (; node
; node
= node
.next())
763 if (node
.is("script"))
764 ASSERT(processScript(node
));
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");
775 } else if (filename
.empty()) {
776 Log::err(descriptor
, "<script></script> is empty");
780 ScriptRef script
= Script::create(filename
);
781 if (!script
|| !script
->validate())
784 if (!setScript(trigger
, script
)) {
786 "unrecognized script trigger: " + trigger
);
793 bool Entity::setScript(const std::string
& trigger
, ScriptRef
& script
)
795 if (boost::iequals(trigger
, "on_tick")) {
799 if (boost::iequals(trigger
, "on_turn")) {
803 if (boost::equals(trigger
, "on_tile_entry")) {
804 tileEntryScript
= script
;
807 if (boost::iequals(trigger
, "on_tile_exit")) {
808 tileExitScript
= script
;
811 if (boost::iequals(trigger
, "on_delete")) {
812 deleteScript
= script
;
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
>()),
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
)
840 static_cast<void (Entity::*) (int,int,double)>
841 (&Entity::setTileCoords
))
842 .def("teleport", &Entity::teleport
)
843 .def("move", &Entity::move
)
845 static_cast<vicoord (Entity::*) (Tile
*,int,int)>
848 static_cast<bool (Entity::*) (int,int,double)>
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)