1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
9 #include <boost/algorithm/string.hpp> // for iequals
10 #include <boost/foreach.hpp>
11 #include <Gosu/Image.hpp>
12 #include <Gosu/Math.hpp>
13 #include <Gosu/Timing.hpp>
20 #include "resourcer.h"
24 #define ASSERT(x) if (!(x)) return false
26 static std::string directions
[][3] = {
27 {"up-left", "up", "up-right"},
28 {"left", "", "right"},
29 {"down-left", "down", "down-right"},
41 nowalkFlags(TILE_NOWALK
| TILE_NOWALK_NPC
),
50 pythonSetGlobal("Area", area
);
51 pythonSetGlobal("Entity", this);
52 deleteScript
.invoke();
55 bool Entity::init(const std::string
& descriptor
)
57 this->descriptor
= descriptor
;
58 return processDescriptor();
61 void Entity::destroy()
66 area
->requestRedraw();
74 int now
= GameWindow::instance().time();
75 phase
->frame(now
)->draw(doff
.x
+ r
.x
, doff
.y
+ r
.y
, r
.z
);
79 bool Entity::needsRedraw() const
81 int now
= GameWindow::instance().time();
82 return redraw
|| phase
->needsRedraw(now
);
86 void Entity::tick(unsigned long dt
)
89 switch (conf
.moveMode
) {
102 void Entity::tickTurn(unsigned long)
104 // FIXME Characters (!!) don't do anything in TILE mode.
107 void Entity::tickTile(unsigned long dt
)
113 double traveled
= speed
* (double)dt
;
114 double destDist
= Gosu::distance(r
.x
, r
.y
, destCoord
.x
, destCoord
.y
);
115 if (destDist
<= traveled
) {
121 double perc
= 1.0 - destDist
/traveled
;
122 unsigned long remt
= (unsigned long)(perc
* (double)dt
);
127 double angle
= atan2(destCoord
.y
- r
.y
, destCoord
.x
- r
.x
);
128 double dx
= cos(angle
);
129 double dy
= sin(angle
);
131 r
.x
+= dx
* traveled
;
132 r
.y
+= dy
* traveled
;
136 void Entity::tickNoTile(unsigned long)
146 const std::string
Entity::getFacing() const
148 return directionStr(facing
);
151 bool Entity::setPhase(const std::string
& name
)
153 AnimationMap::iterator it
;
154 it
= phases
.find(name
);
155 if (it
== phases
.end()) {
156 Log::err(descriptor
, "phase '" + name
+ "' not found");
159 Animation
* newPhase
= &it
->second
;
160 if (phase
!= newPhase
) {
161 int now
= GameWindow::instance().time();
163 phase
->startOver(now
);
171 std::string
Entity::getPhase() const
176 rcoord
Entity::getPixelCoord() const
181 icoord
Entity::getTileCoords_i() const
183 return area
->virt2phys(r
);
186 vicoord
Entity::getTileCoords_vi() const
188 return area
->virt2virt(r
);
191 void Entity::setTileCoords(int x
, int y
)
194 vicoord
virt(x
, y
, r
.z
);
196 r
= area
->virt2virt(virt
);
200 void Entity::setTileCoords(int x
, int y
, double z
)
203 vicoord
virt(x
, y
, z
);
205 r
= area
->virt2virt(virt
);
209 void Entity::setTileCoords(icoord phys
)
213 r
= area
->phys2virt_r(phys
);
217 void Entity::setTileCoords(vicoord virt
)
221 r
= area
->virt2virt(virt
);
225 void Entity::setTileCoords(rcoord virt
)
233 void Entity::teleport(int, int)
235 throw "pure virtual function";
238 icoord
Entity::moveDest(ivec2 facing
)
240 Tile
* tile
= getTile();
241 icoord here
= getTileCoords_i();
245 return tile
->moveDest(here
, facing
);
247 return here
+ icoord(facing
.x
, facing
.y
, 0);
251 vicoord
Entity::moveDest(Tile
* t
, int dx
, int dy
)
253 icoord here
= getTileCoords_i();
258 dest
= t
->moveDest(here
, ivec2(dx
, dy
));
259 return t
->area
->phys2virt_vi(dest
);
262 dest
= here
+ icoord(dx
, dy
, 0);
263 return area
->phys2virt_vi(dest
);
267 bool Entity::isMoving() const
269 return moving
|| stillMoving
;
272 void Entity::moveByTile(int x
, int y
)
274 moveByTile(ivec2(x
, y
));
277 void Entity::moveByTile(ivec2 delta
)
283 if (canMove(moveDest(facing
)))
286 setPhase(directionStr(facing
));
289 void Entity::move(int, int)
291 throw "pure virtual function";
294 Area
* Entity::getArea()
299 void Entity::setArea(Area
* a
)
304 setSpeed(speedMul
); // Calculate new speed based on tile size.
308 double Entity::getSpeed() const
313 void Entity::setSpeed(double multiplier
)
315 speedMul
= multiplier
;
317 double tilesPerSecond
= area
->getTileDimensions().x
/ 1000.0;
318 speed
= baseSpeed
* speedMul
* tilesPerSecond
;
322 Tile
* Entity::getTile() const
324 return area
? area
->getTile(r
) : NULL
;
327 Tile
* Entity::getTile()
329 return area
? area
->getTile(r
) : NULL
;
332 void Entity::setFrozen(bool b
)
337 bool Entity::getFrozen()
342 FlagManip
Entity::exemptManip()
344 return FlagManip(&nowalkExempt
);
349 throw "pure virtual function";
352 void Entity::calcDoff()
354 // X-axis is centered on tile.
355 doff
.x
= (area
->getTileDimensions().x
- imgw
) / 2;
356 // Y-axis is aligned with bottom of tile.
357 doff
.y
= area
->getTileDimensions().y
- imgh
- 1;
360 SampleRef
Entity::getSound(const std::string
& name
) const
362 SampleMap::const_iterator it
;
363 it
= sounds
.find(name
);
364 if (it
!= sounds
.end())
370 ivec2
Entity::setFacing(ivec2 facing
)
372 this->facing
= ivec2(
373 Gosu::clamp(facing
.x
, -1, 1),
374 Gosu::clamp(facing
.y
, -1, 1)
379 const std::string
& Entity::directionStr(ivec2 facing
) const
381 return directions
[facing
.y
+1][facing
.x
+1];
384 bool Entity::canMove(int x
, int y
, double z
)
386 vicoord
virt(x
, y
, z
);
387 return canMove(area
->virt2phys(virt
));
390 bool Entity::canMove(icoord dest
)
392 icoord dxyz
= dest
- getTileCoords_i();
393 ivec2
dxy(dxyz
.x
, dxyz
.y
);
395 Tile
* curTile
= getTile();
396 this->destTile
= area
->getTile(dest
);
397 this->destCoord
= area
->phys2virt_r(dest
);
399 if ((curTile
&& curTile
->exitAt(dxy
)) ||
400 (destTile
&& destTile
->exits
[EXIT_NORMAL
])) {
401 // We can always take exits as long as we can take exits.
402 // (Even if they would cause us to be out of bounds.)
403 if (nowalkExempt
& TILE_NOWALK_EXIT
)
407 bool inBounds
= area
->inBounds(dest
);
408 if (destTile
&& inBounds
) {
409 // Tile is inside map. Can we move?
410 if (nowalked(*destTile
))
412 if (destTile
->entCnt
)
413 // Space is occupied by another Entity.
419 // The tile is legitimately off the map.
420 return nowalkExempt
& TILE_NOWALK_AREA_BOUND
;
423 bool Entity::canMove(vicoord dest
)
425 return canMove(area
->virt2phys(dest
));
428 bool Entity::nowalked(Tile
& t
)
430 unsigned flags
= nowalkFlags
& ~nowalkExempt
;
431 return t
.hasFlag(flags
);
434 void Entity::preMove()
437 fromTile
= getTile();
439 rcoord d
= destCoord
- fromCoord
;
440 deltaCoord
= area
->virt2virt(d
);
444 // Start moving animation.
445 switch (conf
.moveMode
) {
450 setPhase("moving " + getFacing());
457 fromTile
->runLeaveScript(this);
459 // Modify tile's entity count.
463 SampleRef step
= getSound("step");
467 // Set z right away so that we're on-level with the square we're
471 if (conf
.moveMode
== TURN
) {
472 // Movement is instantaneous.
478 // Movement happens over time. See tickTile().
482 void Entity::postMove()
487 boost::optional
<double> layermod
= destTile
->layermods
[EXIT_NORMAL
];
492 // Stop moving animation.
494 setPhase(getFacing());
498 destTile
->runEnterScript(this);
500 runTileEntryScript();
502 // TODO: move teleportation here
506 * moveArea(getExit());
513 void Entity::leaveTile()
520 void Entity::enterTile()
524 enterTile(getTile());
527 void Entity::enterTile(Tile
* t
)
533 void Entity::runTickScript()
535 pythonSetGlobal("Area", area
);
536 pythonSetGlobal("Entity", this);
537 pythonSetGlobal("Tile", getTile());
541 void Entity::runTurnScript()
543 pythonSetGlobal("Area", area
);
544 pythonSetGlobal("Entity", this);
545 pythonSetGlobal("Tile", getTile());
549 void Entity::runTileExitScript()
551 pythonSetGlobal("Area", area
);
552 pythonSetGlobal("Entity", this);
553 pythonSetGlobal("Tile", getTile());
554 tileExitScript
.invoke();
557 void Entity::runTileEntryScript()
559 pythonSetGlobal("Area", area
);
560 pythonSetGlobal("Entity", this);
561 pythonSetGlobal("Tile", getTile());
562 tileEntryScript
.invoke();
567 * DESCRIPTOR CODE BELOW
570 bool Entity::processDescriptor()
572 Resourcer
* rc
= Resourcer::instance();
573 XMLRef doc
= rc
->getXMLDoc(descriptor
, "entity.dtd");
576 const XMLNode root
= doc
->root(); // <entity>
580 for (XMLNode node
= root
.childrenNode(); node
; node
= node
.next()) {
581 if (node
.is("speed")) {
582 ASSERT(node
.doubleContent(&baseSpeed
));
583 setSpeed(speedMul
); // Calculate speed from tile size.
584 } else if (node
.is("sprite")) {
585 ASSERT(processSprite(node
.childrenNode()));
586 } else if (node
.is("sounds")) {
587 ASSERT(processSounds(node
.childrenNode()));
588 } else if (node
.is("scripts")) {
589 ASSERT(processScripts(node
.childrenNode()));
595 bool Entity::processSprite(XMLNode node
)
597 Resourcer
* rc
= Resourcer::instance();
599 for (; node
; node
= node
.next()) {
600 if (node
.is("sheet")) {
601 std::string imageSheet
= node
.content();
602 ASSERT(node
.intAttr("tile_width", &imgw
) &&
603 node
.intAttr("tile_height", &imgh
));
604 ASSERT(rc
->getTiledImage(tiles
, imageSheet
,
606 } else if (node
.is("phases")) {
607 ASSERT(processPhases(node
.childrenNode(), tiles
));
613 bool Entity::processPhases(XMLNode node
, const TiledImage
& tiles
)
615 for (; node
; node
= node
.next())
616 if (node
.is("phase"))
617 ASSERT(processPhase(node
, tiles
));
621 bool Entity::processPhase(const XMLNode node
, const TiledImage
& tiles
)
623 /* Each phase requires a 'name'. Additionally,
624 * one of either 'pos' or 'speed' is needed.
625 * If speed is used, we have sub-elements. We
626 * can't have both pos and speed.
628 const std::string name
= node
.attr("name");
630 Log::err(descriptor
, "<phase> name attribute is empty");
634 const std::string posStr
= node
.attr("pos");
635 const std::string speedStr
= node
.attr("speed");
637 if (posStr
.size() && speedStr
.size()) {
638 Log::err(descriptor
, "pos and speed attributes in "
639 "phase element are mutually exclusive");
641 } else if (posStr
.empty() && speedStr
.empty()) {
642 Log::err(descriptor
, "must have pos or speed attribute "
649 ASSERT(node
.intAttr("pos", &pos
));
650 if (pos
< 0 || (int)tiles
.size() < pos
) {
652 "<phase></phase> index out of bounds");
655 phases
[name
] = Animation(tiles
[pos
]);
659 ASSERT(node
.intAttr("speed", &speed
));
661 int frameLen
= (int)(1000.0/speed
);
662 std::vector
<ImageRef
> frames
;
664 ASSERT(processMembers(node
.childrenNode(), frames
, tiles
));
665 phases
[name
] = Animation(frames
, frameLen
);
671 bool Entity::processMembers(XMLNode node
, std::vector
<ImageRef
>& frames
,
672 const TiledImage
& tiles
)
674 for (; node
; node
= node
.next())
675 if (node
.is("member"))
676 ASSERT(processMember(node
, frames
, tiles
));
680 bool Entity::processMember(const XMLNode node
, std::vector
<ImageRef
>& frames
,
681 const TiledImage
& tiles
)
684 ASSERT(node
.intAttr("pos", &pos
));
685 if (pos
< 0 || (int)tiles
.size() < pos
) {
686 Log::err(descriptor
, "<member></member> index out of bounds");
689 frames
.push_back(tiles
[pos
]);
693 bool Entity::processSounds(XMLNode node
)
695 for (; node
; node
= node
.next())
696 if (node
.is("sound"))
697 ASSERT(processSound(node
));
701 bool Entity::processSound(const XMLNode node
)
703 const std::string name
= node
.attr("name");
704 const std::string filename
= node
.content();
706 Log::err(descriptor
, "<sound> name attribute is empty");
708 } else if (filename
.empty()) {
709 Log::err(descriptor
, "<sound></sound> is empty");
713 Resourcer
* rc
= Resourcer::instance();
714 SampleRef s
= rc
->getSample(filename
);
720 bool Entity::processScripts(XMLNode node
)
722 for (; node
; node
= node
.next())
723 if (node
.is("script"))
724 ASSERT(processScript(node
));
728 bool Entity::processScript(const XMLNode node
)
730 const std::string trigger
= node
.attr("trigger");
731 const std::string filename
= node
.content();
732 if (trigger
.empty()) {
733 Log::err(descriptor
, "<script> trigger attribute is empty");
735 } else if (filename
.empty()) {
736 Log::err(descriptor
, "<script></script> is empty");
740 ScriptInst
script(filename
);
741 if (!script
.validate(descriptor
))
744 if (!setScript(trigger
, script
)) {
746 "unrecognized script trigger: " + trigger
);
753 bool Entity::setScript(const std::string
& trigger
, ScriptInst
& script
)
755 if (boost::iequals(trigger
, "on_tick")) {
759 if (boost::iequals(trigger
, "on_turn")) {
763 if (boost::equals(trigger
, "on_tile_entry")) {
764 tileEntryScript
= script
;
767 if (boost::iequals(trigger
, "on_tile_exit")) {
768 tileExitScript
= script
;
771 if (boost::iequals(trigger
, "on_delete")) {
772 deleteScript
= script
;
781 using namespace boost::python
;
783 class_
<Entity
>("Entity", no_init
)
784 .def("init", &Entity::init
)
785 .def("delete", &Entity::destroy
)
786 .add_property("frozen", &Entity::getFrozen
, &Entity::setFrozen
)
787 .add_property("phase", &Entity::getPhase
, &Entity::setPhase
)
788 .add_property("area",
789 make_function(&Entity::getArea
,
790 return_value_policy
<reference_existing_object
>()),
792 .add_property("tile", make_function(
793 static_cast<Tile
* (Entity::*) ()> (&Entity::getTile
),
794 return_value_policy
<reference_existing_object
>()))
795 .add_property("speed", &Entity::getSpeed
, &Entity::setSpeed
)
796 .add_property("moving", &Entity::isMoving
)
797 .add_property("exempt", &Entity::exemptManip
)
798 .add_property("coords", &Entity::getTileCoords_vi
)
800 static_cast<void (Entity::*) (int,int,double)>
801 (&Entity::setTileCoords
))
802 .def("teleport", &Entity::teleport
)
803 .def("move", &Entity::move
)
805 static_cast<vicoord (Entity::*) (Tile
*,int,int)>
808 static_cast<bool (Entity::*) (int,int,double)>
810 .def_readwrite("on_tick", &Entity::tickScript
)
811 .def_readwrite("on_turn", &Entity::turnScript
)
812 .def_readwrite("on_tile_entry", &Entity::tileEntryScript
)
813 .def_readwrite("on_tile_exit", &Entity::tileExitScript
)
814 .def_readwrite("on_delete", &Entity::deleteScript
)