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 <Gosu/Image.hpp>
30 #include <Gosu/Math.hpp>
31 #include <Gosu/Timing.hpp>
34 #include "client-conf.h"
38 #include "python-bindings-template.cpp"
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
),
71 pythonSetGlobal("Area", area
);
72 pythonSetGlobal("Entity", this);
73 deleteScript
->invoke();
77 bool Entity::init(const std::string
& descriptor
)
79 this->descriptor
= descriptor
;
80 return processDescriptor();
83 void Entity::destroy()
88 area
->requestRedraw();
100 time_t now
= World::instance()->time();
101 Image
* img
= phase
->frame(now
);
106 r
.z
+ area
->isometricZOff(rvec2(r
.x
, r
.y
))
110 bool Entity::needsRedraw() const
112 time_t now
= World::instance()->time();
113 return redraw
|| (phase
&& phase
->needsRedraw(now
));
117 void Entity::tick(time_t dt
)
120 switch (conf
.moveMode
) {
133 void Entity::tickTurn(time_t)
135 // FIXME Characters (!!) don't do anything in TILE mode.
138 void Entity::tickTile(time_t dt
)
144 double traveled
= speed
* (double)dt
;
145 double destDist
= Gosu::distance(r
.x
, r
.y
, destCoord
.x
, destCoord
.y
);
146 if (destDist
<= traveled
) {
152 double perc
= 1.0 - destDist
/traveled
;
153 time_t remt
= (time_t)(perc
* (double)dt
);
158 double angle
= atan2(destCoord
.y
- r
.y
, destCoord
.x
- r
.x
);
159 double dx
= cos(angle
);
160 double dy
= sin(angle
);
162 r
.x
+= dx
* traveled
;
163 r
.y
+= dy
* traveled
;
167 void Entity::tickNoTile(time_t)
177 const std::string
Entity::getFacing() const
179 return directionStr(facing
);
182 bool Entity::setPhase(const std::string
& name
)
184 enum SetPhaseResult res
;
185 res
= _setPhase(name
);
186 if (res
== PHASE_NOTFOUND
) {
187 res
= _setPhase("stance");
188 if (res
== PHASE_NOTFOUND
)
189 Log::err(descriptor
, "phase '" + name
+ "' not found");
191 return res
== PHASE_CHANGED
;
194 std::string
Entity::getPhase() const
199 rcoord
Entity::getPixelCoord() const
204 icoord
Entity::getTileCoords_i() const
206 return area
->virt2phys(r
);
209 vicoord
Entity::getTileCoords_vi() const
211 return area
->virt2virt(r
);
214 void Entity::setTileCoords(int x
, int y
)
217 vicoord
virt(x
, y
, r
.z
);
219 r
= area
->virt2virt(virt
);
223 void Entity::setTileCoords(int x
, int y
, double z
)
226 vicoord
virt(x
, y
, z
);
228 r
= area
->virt2virt(virt
);
232 void Entity::setTileCoords(icoord phys
)
236 r
= area
->phys2virt_r(phys
);
240 void Entity::setTileCoords(vicoord virt
)
244 r
= area
->virt2virt(virt
);
248 void Entity::setTileCoords(rcoord virt
)
256 void Entity::teleport(int, int)
258 throw "pure virtual function";
261 icoord
Entity::moveDest(ivec2 facing
)
263 Tile
* tile
= getTile();
264 icoord here
= getTileCoords_i();
268 return tile
->moveDest(here
, facing
);
270 return here
+ icoord(facing
.x
, facing
.y
, 0);
274 vicoord
Entity::moveDest(Tile
* t
, int dx
, int dy
)
276 icoord here
= getTileCoords_i();
281 dest
= t
->moveDest(here
, ivec2(dx
, dy
));
282 return t
->area
->phys2virt_vi(dest
);
285 dest
= here
+ icoord(dx
, dy
, 0);
286 return area
->phys2virt_vi(dest
);
291 bool Entity::canMove(int x
, int y
, double z
)
293 vicoord
virt(x
, y
, z
);
294 return canMove(area
->virt2phys(virt
));
297 bool Entity::canMove(icoord dest
)
299 icoord dxyz
= dest
- getTileCoords_i();
300 ivec2
dxy(dxyz
.x
, dxyz
.y
);
302 Tile
* curTile
= getTile();
303 this->destTile
= area
->getTile(dest
);
304 this->destCoord
= area
->phys2virt_r(dest
);
306 if ((curTile
&& curTile
->exitAt(dxy
)) ||
307 (destTile
&& destTile
->exits
[EXIT_NORMAL
])) {
308 // We can always take exits as long as we can take exits.
309 // (Even if they would cause us to be out of bounds.)
310 if (nowalkExempt
& TILE_NOWALK_EXIT
)
314 bool inBounds
= area
->inBounds(dest
);
315 if (destTile
&& inBounds
) {
316 // Tile is inside map. Can we move?
317 if (nowalked(*destTile
))
319 if (destTile
->entCnt
)
320 // Space is occupied by another Entity.
326 // The tile is legitimately off the map.
327 return nowalkExempt
& TILE_NOWALK_AREA_BOUND
;
330 bool Entity::canMove(vicoord dest
)
332 return canMove(area
->virt2phys(dest
));
335 bool Entity::isMoving() const
337 return moving
|| stillMoving
;
340 void Entity::moveByTile(int x
, int y
)
342 moveByTile(ivec2(x
, y
));
345 void Entity::moveByTile(ivec2 delta
)
351 if (canMove(moveDest(facing
)))
354 setPhase(directionStr(facing
));
357 void Entity::move(int, int)
359 throw "pure virtual function";
362 Area
* Entity::getArea()
367 void Entity::setArea(Area
* a
)
372 setSpeed(speedMul
); // Calculate new speed based on tile size.
376 double Entity::getSpeed() const
381 void Entity::setSpeed(double multiplier
)
383 speedMul
= multiplier
;
385 double tilesPerSecond
= area
->getTileDimensions().x
/ 1000.0;
386 speed
= baseSpeed
* speedMul
* tilesPerSecond
;
390 Tile
* Entity::getTile() const
392 return area
? area
->getTile(r
) : NULL
;
395 Tile
* Entity::getTile()
397 return area
? area
->getTile(r
) : NULL
;
400 void Entity::setFrozen(bool b
)
405 bool Entity::getFrozen()
410 FlagManip
Entity::exemptManip()
412 return FlagManip(&nowalkExempt
);
417 throw "pure virtual function";
420 void Entity::calcDraw()
422 ivec2 tile
= area
->getTileDimensions();
424 // X-axis is centered on tile.
425 doff
.x
= (tile
.x
- imgsz
.x
) / 2;
426 // Y-axis is aligned with bottom of tile.
427 doff
.y
= tile
.y
- imgsz
.y
;
430 SampleRef
Entity::getSound(const std::string
& name
) const
432 SampleMap::const_iterator it
;
433 it
= sounds
.find(name
);
434 if (it
!= sounds
.end())
440 ivec2
Entity::setFacing(ivec2 facing
)
442 this->facing
= ivec2(
443 Gosu::clamp(facing
.x
, -1, 1),
444 Gosu::clamp(facing
.y
, -1, 1)
449 const std::string
& Entity::directionStr(ivec2 facing
) const
451 return directions
[facing
.y
+1][facing
.x
+1];
454 enum SetPhaseResult
Entity::_setPhase(const std::string
& name
)
456 AnimationMap::iterator it
;
457 it
= phases
.find(name
);
458 if (it
== phases
.end()) {
459 return PHASE_NOTFOUND
;
461 Animation
* newPhase
= &it
->second
;
462 if (phase
!= newPhase
) {
463 time_t now
= World::instance()->time();
465 phase
->startOver(now
, ANIM_INFINITE_CYCLES
);
468 return PHASE_CHANGED
;
470 return PHASE_NOTCHANGED
;
473 bool Entity::nowalked(Tile
& t
)
475 unsigned flags
= nowalkFlags
& ~nowalkExempt
;
476 return t
.hasFlag(flags
);
479 void Entity::preMove()
482 fromTile
= getTile();
484 rcoord d
= destCoord
- fromCoord
;
485 deltaCoord
= area
->virt2virt(d
);
489 // Start moving animation.
490 switch (conf
.moveMode
) {
495 setPhase("moving " + getFacing());
502 fromTile
->runLeaveScript(this);
504 // Modify tile's entity count.
508 SampleRef step
= getSound("step");
512 // Set z right away so that we're on-level with the square we're
516 if (conf
.moveMode
== TURN
) {
517 // Movement is instantaneous.
523 // Movement happens over time. See tickTile().
527 void Entity::postMove()
532 double* layermod
= destTile
->layermods
[EXIT_NORMAL
];
537 // Stop moving animation.
539 setPhase(getFacing());
543 destTile
->runEnterScript(this);
545 runTileEntryScript();
547 // TODO: move teleportation here
551 * moveArea(getExit());
558 void Entity::leaveTile()
565 void Entity::enterTile()
569 enterTile(getTile());
572 void Entity::enterTile(Tile
* t
)
578 void Entity::runTickScript()
582 pythonSetGlobal("Area", area
);
583 pythonSetGlobal("Entity", this);
584 pythonSetGlobal("Tile", getTile());
585 tickScript
->invoke();
588 void Entity::runTurnScript()
592 pythonSetGlobal("Area", area
);
593 pythonSetGlobal("Entity", this);
594 pythonSetGlobal("Tile", getTile());
595 turnScript
->invoke();
598 void Entity::runTileExitScript()
602 pythonSetGlobal("Area", area
);
603 pythonSetGlobal("Entity", this);
604 pythonSetGlobal("Tile", getTile());
605 tileExitScript
->invoke();
608 void Entity::runTileEntryScript()
610 if (!tileEntryScript
)
612 pythonSetGlobal("Area", area
);
613 pythonSetGlobal("Entity", this);
614 pythonSetGlobal("Tile", getTile());
615 tileEntryScript
->invoke();
620 * DESCRIPTOR CODE BELOW
623 bool Entity::processDescriptor()
625 XMLRef doc
= Reader::getXMLDoc(descriptor
, "dtd/entity.dtd");
628 const XMLNode root
= doc
->root(); // <entity>
632 for (XMLNode node
= root
.childrenNode(); node
; node
= node
.next()) {
633 if (node
.is("speed")) {
634 ASSERT(node
.doubleContent(&baseSpeed
));
635 setSpeed(speedMul
); // Calculate speed from tile size.
636 } else if (node
.is("sprite")) {
637 ASSERT(processSprite(node
.childrenNode()));
638 } else if (node
.is("sounds")) {
639 ASSERT(processSounds(node
.childrenNode()));
640 } else if (node
.is("scripts")) {
641 ASSERT(processScripts(node
.childrenNode()));
647 bool Entity::processSprite(XMLNode node
)
650 for (; node
; node
= node
.next()) {
651 if (node
.is("sheet")) {
652 std::string imageSheet
= node
.content();
653 ASSERT(node
.intAttr("tile_width", &imgsz
.x
) &&
654 node
.intAttr("tile_height", &imgsz
.y
));
655 tiles
= Reader::getTiledImage(imageSheet
, imgsz
.x
, imgsz
.y
);
657 } else if (node
.is("phases")) {
658 ASSERT(processPhases(node
.childrenNode(), tiles
));
664 bool Entity::processPhases(XMLNode node
, const TiledImageRef
& tiles
)
666 for (; node
; node
= node
.next())
667 if (node
.is("phase"))
668 ASSERT(processPhase(node
, tiles
));
672 bool Entity::processPhase(const XMLNode node
, const TiledImageRef
& tiles
)
674 /* Each phase requires a 'name' and 'frames'. Additionally,
675 * 'speed' is required if 'frames' has more than one member.
677 const std::string name
= node
.attr("name");
679 Log::err(descriptor
, "<phase> name attribute is empty");
683 const std::string framesStr
= node
.attr("frames");
684 const std::string speedStr
= node
.attr("speed");
686 if (framesStr
.empty()) {
687 Log::err(descriptor
, "<phase> frames attribute empty");
691 if (isInteger(framesStr
)) {
692 int frame
= atoi(framesStr
.c_str());
693 if (frame
< 0 || (int)tiles
->size() < frame
) {
695 "<phase> frames attribute index out of bounds");
698 const ImageRef
& image
= (*tiles
.get())[frame
];
699 phases
[name
] = Animation(image
);
701 else if (isRanges(framesStr
)) {
702 if (!isDecimal(speedStr
)) {
704 "<phase> speed attribute must be present and "
707 double fps
= atof(speedStr
.c_str());
709 typedef std::vector
<int> IntVector
;
710 IntVector frames
= parseRanges(framesStr
);
711 std::vector
<ImageRef
> images
;
712 for (IntVector::iterator it
= frames
.begin(); it
!= frames
.end(); it
++) {
714 if (i
< 0 || (int)tiles
->size() < i
) {
716 "<phase> frames attribute index out of bounds");
719 images
.push_back((*tiles
.get())[i
]);
722 phases
[name
] = Animation(images
, (time_t)(1000.0 / fps
));
726 "<phase> frames attribute not an int or int ranges");
733 bool Entity::processSounds(XMLNode node
)
735 for (; node
; node
= node
.next())
736 if (node
.is("sound"))
737 ASSERT(processSound(node
));
741 bool Entity::processSound(const XMLNode node
)
743 const std::string name
= node
.attr("name");
744 const std::string filename
= node
.content();
746 Log::err(descriptor
, "<sound> name attribute is empty");
748 } else if (filename
.empty()) {
749 Log::err(descriptor
, "<sound></sound> is empty");
753 SampleRef s
= Reader::getSample(filename
);
759 bool Entity::processScripts(XMLNode node
)
761 for (; node
; node
= node
.next())
762 if (node
.is("script"))
763 ASSERT(processScript(node
));
767 bool Entity::processScript(const XMLNode node
)
769 const std::string trigger
= node
.attr("trigger");
770 const std::string filename
= node
.content();
771 if (trigger
.empty()) {
772 Log::err(descriptor
, "<script> trigger attribute is empty");
774 } else if (filename
.empty()) {
775 Log::err(descriptor
, "<script></script> is empty");
779 ScriptRef script
= Script::create(filename
);
780 if (!script
|| !script
->validate())
783 if (!setScript(trigger
, script
)) {
785 "unrecognized script trigger: " + trigger
);
792 bool Entity::setScript(const std::string
& trigger
, ScriptRef
& script
)
794 if (trigger
== "on_tick") {
798 if (trigger
== "on_turn") {
802 if (trigger
== "on_tile_entry") {
803 tileEntryScript
= script
;
806 if (trigger
== "on_tile_exit") {
807 tileExitScript
= script
;
810 if (trigger
== "on_delete") {
811 deleteScript
= script
;
820 using namespace boost::python
;
822 class_
<Entity
>("Entity", no_init
)
823 .def("init", &Entity::init
)
824 .def("delete", &Entity::destroy
)
825 .add_property("frozen", &Entity::getFrozen
, &Entity::setFrozen
)
826 .add_property("phase", &Entity::getPhase
, &Entity::setPhase
)
827 .add_property("area",
828 make_function(&Entity::getArea
,
829 return_value_policy
<reference_existing_object
>()),
831 .add_property("tile", make_function(
832 static_cast<Tile
* (Entity::*) ()> (&Entity::getTile
),
833 return_value_policy
<reference_existing_object
>()))
834 .add_property("speed", &Entity::getSpeed
, &Entity::setSpeed
)
835 .add_property("moving", &Entity::isMoving
)
836 .add_property("exempt", &Entity::exemptManip
)
837 .add_property("coords", &Entity::getTileCoords_vi
)
839 static_cast<void (Entity::*) (int,int,double)>
840 (&Entity::setTileCoords
))
841 .def("teleport", &Entity::teleport
)
842 .def("move", &Entity::move
)
844 static_cast<vicoord (Entity::*) (Tile
*,int,int)>
847 static_cast<bool (Entity::*) (int,int,double)>
849 // .def_readwrite("on_tick", &Entity::tickScript)
850 // .def_readwrite("on_turn", &Entity::turnScript)
851 // .def_readwrite("on_tile_entry", &Entity::tileEntryScript)
852 // .def_readwrite("on_tile_exit", &Entity::tileExitScript)
853 // .def_readwrite("on_delete", &Entity::deleteScript)