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>
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
);
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
)
118 switch (conf
.moveMode
) {
131 void Entity::tickTurn(time_t)
133 // FIXME Characters (!!) don't do anything in TILE mode.
136 void Entity::tickTile(time_t dt
)
142 double traveled
= speed
* (double)dt
;
143 double destDist
= Gosu::distance(r
.x
, r
.y
, destCoord
.x
, destCoord
.y
);
144 if (destDist
<= traveled
) {
150 double perc
= 1.0 - destDist
/traveled
;
151 time_t remt
= (time_t)(perc
* (double)dt
);
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)
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
197 rcoord
Entity::getPixelCoord() const
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
)
215 vicoord
virt(x
, y
, r
.z
);
217 r
= area
->virt2virt(virt
);
221 void Entity::setTileCoords(int x
, int y
, double z
)
224 vicoord
virt(x
, y
, z
);
226 r
= area
->virt2virt(virt
);
230 void Entity::setTileCoords(icoord phys
)
234 r
= area
->phys2virt_r(phys
);
238 void Entity::setTileCoords(vicoord virt
)
242 r
= area
->virt2virt(virt
);
246 void Entity::setTileCoords(rcoord virt
)
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();
266 return tile
->moveDest(here
, facing
);
268 return here
+ icoord(facing
.x
, facing
.y
, 0);
272 vicoord
Entity::moveDest(Tile
* t
, int dx
, int dy
)
274 icoord here
= getTileCoords_i();
279 dest
= t
->moveDest(here
, ivec2(dx
, dy
));
280 return t
->area
->phys2virt_vi(dest
);
283 dest
= here
+ icoord(dx
, dy
, 0);
284 return area
->phys2virt_vi(dest
);
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
)
312 bool inBounds
= area
->inBounds(dest
);
313 if (destTile
&& inBounds
) {
314 // Tile is inside map. Can we move?
315 if (nowalked(*destTile
))
317 if (destTile
->entCnt
)
318 // Space is occupied by another Entity.
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
)
349 if (canMove(moveDest(facing
)))
352 setPhase(directionStr(facing
));
355 void Entity::move(int, int)
357 throw "pure virtual function";
360 Area
* Entity::getArea()
365 void Entity::setArea(Area
* a
)
370 setSpeed(speedMul
); // Calculate new speed based on tile size.
374 double Entity::getSpeed() const
379 void Entity::setSpeed(double multiplier
)
381 speedMul
= multiplier
;
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
)
403 bool Entity::getFrozen()
408 FlagManip
Entity::exemptManip()
410 return FlagManip(&nowalkExempt
);
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())
438 ivec2
Entity::setFacing(ivec2 facing
)
440 this->facing
= ivec2(
441 Gosu::clamp(facing
.x
, -1, 1),
442 Gosu::clamp(facing
.y
, -1, 1)
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();
463 phase
->startOver(now
, ANIM_INFINITE_CYCLES
);
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()
480 fromTile
= getTile();
482 rcoord d
= destCoord
- fromCoord
;
483 deltaCoord
= area
->virt2virt(d
);
487 // Start moving animation.
488 switch (conf
.moveMode
) {
493 setPhase("moving " + getFacing());
500 fromTile
->runLeaveScript(this);
502 // Modify tile's entity count.
506 SampleRef step
= getSound("step");
510 // Set z right away so that we're on-level with the square we're
514 if (conf
.moveMode
== TURN
) {
515 // Movement is instantaneous.
521 // Movement happens over time. See tickTile().
525 void Entity::postMove()
530 boost::optional
<double> layermod
= destTile
->layermods
[EXIT_NORMAL
];
535 // Stop moving animation.
537 setPhase(getFacing());
541 destTile
->runEnterScript(this);
543 runTileEntryScript();
545 // TODO: move teleportation here
549 * moveArea(getExit());
556 void Entity::leaveTile()
563 void Entity::enterTile()
567 enterTile(getTile());
570 void Entity::enterTile(Tile
* t
)
576 void Entity::runTickScript()
578 pythonSetGlobal("Area", area
);
579 pythonSetGlobal("Entity", this);
580 pythonSetGlobal("Tile", getTile());
584 void Entity::runTurnScript()
586 pythonSetGlobal("Area", area
);
587 pythonSetGlobal("Entity", this);
588 pythonSetGlobal("Tile", getTile());
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");
618 const XMLNode root
= doc
->root(); // <entity>
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()));
637 bool Entity::processSprite(XMLNode node
)
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
);
647 } else if (node
.is("phases")) {
648 ASSERT(processPhases(node
.childrenNode(), tiles
));
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
));
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");
669 Log::err(descriptor
, "<phase> name attribute is empty");
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");
681 if (isInteger(framesStr
)) {
682 int frame
= atoi(framesStr
.c_str());
683 if (frame
< 0 || (int)tiles
->size() < frame
) {
685 "<phase> frames attribute index out of bounds");
688 const ImageRef
& image
= (*tiles
.get())[frame
];
689 phases
[name
] = Animation(image
);
691 else if (isRanges(framesStr
)) {
692 if (!isDecimal(speedStr
)) {
694 "<phase> speed attribute must be present and "
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
) {
704 "<phase> frames attribute index out of bounds");
707 images
.push_back((*tiles
.get())[i
]);
710 phases
[name
] = Animation(images
, (time_t)(1000.0 / fps
));
714 "<phase> frames attribute not an int or int ranges");
721 bool Entity::processSounds(XMLNode node
)
723 for (; node
; node
= node
.next())
724 if (node
.is("sound"))
725 ASSERT(processSound(node
));
729 bool Entity::processSound(const XMLNode node
)
731 const std::string name
= node
.attr("name");
732 const std::string filename
= node
.content();
734 Log::err(descriptor
, "<sound> name attribute is empty");
736 } else if (filename
.empty()) {
737 Log::err(descriptor
, "<sound></sound> is empty");
741 SampleRef s
= Reader::getSample(filename
);
747 bool Entity::processScripts(XMLNode node
)
749 for (; node
; node
= node
.next())
750 if (node
.is("script"))
751 ASSERT(processScript(node
));
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");
762 } else if (filename
.empty()) {
763 Log::err(descriptor
, "<script></script> is empty");
767 ScriptInst
script(filename
);
768 if (!script
.validate(descriptor
))
771 if (!setScript(trigger
, script
)) {
773 "unrecognized script trigger: " + trigger
);
780 bool Entity::setScript(const std::string
& trigger
, ScriptInst
& script
)
782 if (boost::iequals(trigger
, "on_tick")) {
786 if (boost::iequals(trigger
, "on_turn")) {
790 if (boost::equals(trigger
, "on_tile_entry")) {
791 tileEntryScript
= script
;
794 if (boost::iequals(trigger
, "on_tile_exit")) {
795 tileExitScript
= script
;
798 if (boost::iequals(trigger
, "on_delete")) {
799 deleteScript
= script
;
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
>()),
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
)
827 static_cast<void (Entity::*) (int,int,double)>
828 (&Entity::setTileCoords
))
829 .def("teleport", &Entity::teleport
)
830 .def("move", &Entity::move
)
832 static_cast<vicoord (Entity::*) (Tile
*,int,int)>
835 static_cast<bool (Entity::*) (int,int,double)>
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
)