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