1 /*
2  *  Copyright (C) 2011-2016  OpenDungeons Team
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "entities/MissileObject.h"
19 
20 #include "entities/Building.h"
21 #include "entities/GameEntityType.h"
22 #include "entities/MissileBoulder.h"
23 #include "entities/MissileOneHit.h"
24 #include "entities/Tile.h"
25 #include "game/Seat.h"
26 #include "gamemap/GameMap.h"
27 #include "network/ODPacket.h"
28 #include "utils/Helper.h"
29 #include "utils/LogManager.h"
30 
31 #include <iostream>
32 
MissileObject(GameMap * gameMap,Seat * seat,const std::string & senderName,const std::string & meshName,const Ogre::Vector3 & direction,double speed,GameEntity * entityTarget,bool damageAllies,bool koEnemyCreature)33 MissileObject::MissileObject(GameMap* gameMap, Seat* seat, const std::string& senderName, const std::string& meshName,
34         const Ogre::Vector3& direction, double speed, GameEntity* entityTarget, bool damageAllies, bool koEnemyCreature) :
35     RenderedMovableEntity(gameMap, senderName, meshName, 0.0f, false),
36     mDirection(direction),
37     mIsMissileAlive(true),
38     mEntityTarget(entityTarget),
39     mDamageAllies(damageAllies),
40     mKoEnemyCreature(koEnemyCreature),
41     mSpeed(speed)
42 {
43     setSeat(seat);
44 
45     if(mEntityTarget != nullptr)
46         mEntityTarget->addGameEntityListener(this);
47 }
48 
MissileObject(GameMap * gameMap)49 MissileObject::MissileObject(GameMap* gameMap) :
50     RenderedMovableEntity(gameMap),
51     mDirection(Ogre::Vector3::ZERO),
52     mIsMissileAlive(true),
53     mEntityTarget(nullptr),
54     mDamageAllies(false),
55     mKoEnemyCreature(false),
56     mSpeed(1.0)
57 {
58 }
59 
~MissileObject()60 MissileObject::~MissileObject()
61 {
62     if(mEntityTarget != nullptr)
63         mEntityTarget->removeGameEntityListener(this);
64 }
65 
getObjectType() const66 GameEntityType MissileObject::getObjectType() const
67 {
68     return GameEntityType::missileObject;
69 }
70 
doUpkeep()71 void MissileObject::doUpkeep()
72 {
73     if(!getIsOnMap())
74         return;
75 
76     if(!mIsMissileAlive)
77     {
78         if(!isMoving())
79         {
80             removeFromGameMap();
81             deleteYourself();
82         }
83         return;
84     }
85 
86     Tile* tile = getPositionTile();
87     if(tile == nullptr)
88     {
89         OD_LOG_ERR("entityName=" + getName());
90         removeFromGameMap();
91         deleteYourself();
92         return;
93     }
94 
95     // We check if a creature is in our way. We start by taking the tile we will be on
96     Ogre::Vector3 position = getPosition();
97     double moveDist = getMoveSpeed();
98     Ogre::Vector3 destination;
99     std::list<Tile*> tiles;
100     mIsMissileAlive = computeDestination(position, moveDist, mDirection, destination, tiles);
101 
102     std::vector<Ogre::Vector3> path;
103     Tile* lastTile = nullptr;
104     while(!tiles.empty() && mIsMissileAlive)
105     {
106         Tile* tmpTile = tiles.front();
107         tiles.pop_front();
108 
109         if(tmpTile == nullptr)
110         {
111             OD_LOG_ERR("Unexpected null tile when reading missile path name=" + getName() + ", lastTile=" + (lastTile == nullptr ? std::string("nullptr") : Tile::displayAsString(lastTile)));
112             continue;
113         }
114 
115         if(tmpTile->getFullness() > 0.0)
116         {
117             Ogre::Vector3 nextDirection;
118             OD_LOG_INF("missile name=" + getName() + ", hit wall on tile=" + Tile::displayAsString(tmpTile));
119             mIsMissileAlive = wallHitNextDirection(mDirection, lastTile, nextDirection);
120             if(!mIsMissileAlive)
121             {
122                 destination.x = static_cast<Ogre::Real>(lastTile->getX());
123                 destination.y = static_cast<Ogre::Real>(lastTile->getY());
124                 break;
125             }
126             else
127             {
128                 position.x = static_cast<Ogre::Real>(lastTile->getX());
129                 position.y = static_cast<Ogre::Real>(lastTile->getY());
130                 path.push_back(position);
131                 // We compute next position
132                 mDirection = nextDirection;
133                 mIsMissileAlive = computeDestination(position, moveDist, mDirection, destination, tiles);
134                 continue;
135             }
136         }
137 
138         if(lastTile != nullptr)
139         {
140             Ogre::Vector3 lastPos;
141             lastPos.x = static_cast<Ogre::Real>(lastTile->getX());
142             lastPos.y = static_cast<Ogre::Real>(lastTile->getY());
143             lastPos.z = position.z;
144             Ogre::Vector3 curPos;
145             curPos.x = static_cast<Ogre::Real>(tmpTile->getX());
146             curPos.y = static_cast<Ogre::Real>(tmpTile->getY());
147             curPos.z = position.z;
148             moveDist -= lastPos.distance(curPos);
149         }
150         lastTile = tmpTile;
151 
152         // If we are aiming a specific entity, we check if we hit
153         GameEntity* target = mEntityTarget;
154         if(target != nullptr)
155         {
156             // Check if we hit
157             if(tmpTile->isEntityOnTile(target))
158             {
159                 // hitTargetEntity might kill the target so we should take care to not use mEntityTarget after calling it
160                 // as it may be null
161                 hitTargetEntity(tmpTile, target);
162                 mIsMissileAlive = false;
163                 destination.x = static_cast<Ogre::Real>(tmpTile->getX());
164                 destination.y = static_cast<Ogre::Real>(tmpTile->getY());
165             }
166         }
167 
168         std::vector<Tile*> tileVector;
169         tileVector.push_back(tmpTile);
170         std::vector<GameEntity*> enemyCreatures = getGameMap()->getVisibleCreatures(tileVector, getSeat(), true);
171         for(std::vector<GameEntity*>::iterator it = enemyCreatures.begin(); it != enemyCreatures.end(); ++it)
172         {
173             GameEntity* creature = *it;
174             OD_LOG_INF("missile=" + getName() + " hit creature=" + creature->getName() + ", on tile=" + Tile::displayAsString(tmpTile));
175             if(!hitCreature(tmpTile, creature))
176             {
177                 destination -= moveDist * mDirection;
178                 mIsMissileAlive = false;
179                 break;
180             }
181         }
182 
183         if(!mDamageAllies || !mIsMissileAlive)
184             continue;
185 
186         std::vector<GameEntity*> alliedCreatures = getGameMap()->getVisibleCreatures(tileVector, getSeat(), false);
187         for(std::vector<GameEntity*>::iterator it = alliedCreatures.begin(); it != alliedCreatures.end(); ++it)
188         {
189             GameEntity* creature = *it;
190             OD_LOG_INF("missile=" + getName() + " hit creature=" + creature->getName() + ", on tile=" + Tile::displayAsString(tmpTile));
191             if(!hitCreature(tmpTile, creature))
192             {
193                 destination -= moveDist * mDirection;
194                 mIsMissileAlive = false;
195                 break;
196             }
197         }
198     }
199 
200     path.push_back(destination);
201     setWalkPath(EntityAnimation::idle_anim, EntityAnimation::idle_anim, true, true, path);
202 }
203 
computeDestination(const Ogre::Vector3 & position,double moveDist,const Ogre::Vector3 & direction,Ogre::Vector3 & destination,std::list<Tile * > & tiles)204 bool MissileObject::computeDestination(const Ogre::Vector3& position, double moveDist, const Ogre::Vector3& direction,
205         Ogre::Vector3& destination, std::list<Tile*>& tiles)
206 {
207     destination = position + (moveDist * direction);
208     tiles = getGameMap()->tilesBetween(Helper::round(position.x),
209         Helper::round(position.y), Helper::round(destination.x), Helper::round(destination.y));
210     if(tiles.empty())
211     {
212         OD_LOG_ERR("missile=" + getName() + " has unexpected empty tiles destination");
213         return false;
214     }
215 
216     // If we get out of the map, we take the last tile as the destination
217     if((direction.x > 0.0 && destination.x > static_cast<Ogre::Real>(getGameMap()->getMapSizeX() - 1)) ||
218        (direction.x < 0.0 && destination.x < 0.0) ||
219        (direction.y > 0 && destination.y > static_cast<Ogre::Real>(getGameMap()->getMapSizeY() - 1)) ||
220        (direction.y < 0 && destination.y < 0))
221     {
222         Tile* lastTile = tiles.back();
223         destination.x = static_cast<Ogre::Real>(lastTile->getX());
224         destination.y = static_cast<Ogre::Real>(lastTile->getY());
225 
226         // We are in the last position, we can die
227         if(tiles.size() <= 1)
228             return false;
229     }
230 
231     return true;
232 }
233 
notifyDead(GameEntity * entity)234 bool MissileObject::notifyDead(GameEntity* entity)
235 {
236     if(entity == mEntityTarget)
237     {
238         mEntityTarget = nullptr;
239         return false;
240     }
241     return true;
242 }
243 
notifyRemovedFromGameMap(GameEntity * entity)244 bool MissileObject::notifyRemovedFromGameMap(GameEntity* entity)
245 {
246     if(entity == mEntityTarget)
247     {
248         mEntityTarget = nullptr;
249         return false;
250     }
251     return true;
252 }
253 
notifyPickedUp(GameEntity * entity)254 bool MissileObject::notifyPickedUp(GameEntity* entity)
255 {
256     if(entity == mEntityTarget)
257     {
258         mEntityTarget = nullptr;
259         return false;
260     }
261     return true;
262 }
263 
notifyDropped(GameEntity * entity)264 bool MissileObject::notifyDropped(GameEntity* entity)
265 {
266     // That should not happen. For now, we only require events for attacked creatures. And when they
267     // are picked up, we should have cleared mEntityTarget
268     OD_LOG_ERR("name=" + getName() + ", entity=" + entity->getName());
269     return true;
270 }
271 
exportHeadersToStream(std::ostream & os) const272 void MissileObject::exportHeadersToStream(std::ostream& os) const
273 {
274     RenderedMovableEntity::exportHeadersToStream(os);
275     os << getMissileType() << "\t";
276 }
277 
exportHeadersToPacket(ODPacket & os) const278 void MissileObject::exportHeadersToPacket(ODPacket& os) const
279 {
280     RenderedMovableEntity::exportHeadersToPacket(os);
281     os << getMissileType();
282 }
283 
exportToPacket(ODPacket & os,const Seat * seat) const284 void MissileObject::exportToPacket(ODPacket& os, const Seat* seat) const
285 {
286     RenderedMovableEntity::exportToPacket(os, seat);
287     os << mSpeed;
288 }
289 
importFromPacket(ODPacket & is)290 void MissileObject::importFromPacket(ODPacket& is)
291 {
292     RenderedMovableEntity::importFromPacket(is);
293     OD_ASSERT_TRUE(is >> mSpeed);
294 }
295 
exportToStream(std::ostream & os) const296 void MissileObject::exportToStream(std::ostream& os) const
297 {
298     RenderedMovableEntity::exportToStream(os);
299     os << mDirection.x << "\t" << mDirection.y << "\t" << mDirection.z << "\t";
300     os << mIsMissileAlive << "\t";
301     os << mDamageAllies << "\t";
302     os << mKoEnemyCreature << "\t";
303     os << mSpeed << "\t";
304 }
305 
importFromStream(std::istream & is)306 bool MissileObject::importFromStream(std::istream& is)
307 {
308     if(!RenderedMovableEntity::importFromStream(is))
309         return false;
310     if(!(is >> mDirection.x >> mDirection.y >> mDirection.z))
311         return false;
312     if(!(is >> mIsMissileAlive))
313         return false;
314     if(!(is >> mDamageAllies))
315         return false;
316     if(!(is >> mKoEnemyCreature))
317         return false;
318     if(!(is >> mSpeed))
319         return false;
320 
321     return true;
322 }
323 
getMissileObjectStreamFormat()324 std::string MissileObject::getMissileObjectStreamFormat()
325 {
326     std::string format = RenderedMovableEntity::getRenderedMovableEntityStreamFormat();
327     if(!format.empty())
328         format += "\t";
329 
330     format += "directionX\tdirectionY\tdirectionZ\tmissileAlive\tdamageAllies\tspeed\toptionalData";
331 
332     return "missileType\t" + format;
333 }
334 
getMissileObjectFromStream(GameMap * gameMap,std::istream & is)335 MissileObject* MissileObject::getMissileObjectFromStream(GameMap* gameMap, std::istream& is)
336 {
337     MissileObject* obj = nullptr;
338     MissileObjectType type;
339     OD_ASSERT_TRUE(is >> type);
340     switch(type)
341     {
342         case MissileObjectType::oneHit:
343         {
344             obj = MissileOneHit::getMissileOneHitFromStream(gameMap, is);
345             break;
346         }
347         case MissileObjectType::boulder:
348         {
349             obj = MissileBoulder::getMissileBoulderFromStream(gameMap, is);
350             break;
351         }
352         default:
353             OD_LOG_ERR("Unknown enum value : " + Helper::toString(
354                 static_cast<int>(type)));
355             break;
356     }
357     return obj;
358 }
359 
getMissileObjectFromPacket(GameMap * gameMap,ODPacket & is)360 MissileObject* MissileObject::getMissileObjectFromPacket(GameMap* gameMap, ODPacket& is)
361 {
362     MissileObject* obj = nullptr;
363     MissileObjectType type;
364     OD_ASSERT_TRUE(is >> type);
365     switch(type)
366     {
367         case MissileObjectType::oneHit:
368         {
369             obj = MissileOneHit::getMissileOneHitFromPacket(gameMap, is);
370             break;
371         }
372         case MissileObjectType::boulder:
373         {
374             obj = MissileBoulder::getMissileBoulderFromPacket(gameMap, is);
375             break;
376         }
377         default:
378             OD_LOG_ERR("Unknown enum value : " + Helper::toString(
379                 static_cast<int>(type)));
380             break;
381     }
382     return obj;
383 }
384 
operator <<(ODPacket & os,const MissileObjectType & type)385 ODPacket& operator<<(ODPacket& os, const MissileObjectType& type)
386 {
387     uint32_t intType = static_cast<uint32_t>(type);
388     os << intType;
389     return os;
390 }
391 
operator >>(ODPacket & is,MissileObjectType & type)392 ODPacket& operator>>(ODPacket& is, MissileObjectType& type)
393 {
394     uint32_t intType;
395     is >> intType;
396     type = static_cast<MissileObjectType>(intType);
397     return is;
398 }
399 
operator <<(std::ostream & os,const MissileObjectType & type)400 std::ostream& operator<<(std::ostream& os, const MissileObjectType& type)
401 {
402     uint32_t intType = static_cast<uint32_t>(type);
403     os << intType;
404     return os;
405 }
406 
operator >>(std::istream & is,MissileObjectType & type)407 std::istream& operator>>(std::istream& is, MissileObjectType& type)
408 {
409     uint32_t intType;
410     is >> intType;
411     type = static_cast<MissileObjectType>(intType);
412     return is;
413 }
414