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 "rooms/RoomPrison.h"
19 
20 #include "creatureaction/CreatureActionUseRoom.h"
21 #include "entities/BuildingObject.h"
22 #include "entities/Creature.h"
23 #include "entities/GameEntityType.h"
24 #include "entities/SmallSpiderEntity.h"
25 #include "entities/Tile.h"
26 #include "game/Player.h"
27 #include "game/Seat.h"
28 #include "gamemap/GameMap.h"
29 #include "network/ODServer.h"
30 #include "network/ServerNotification.h"
31 #include "rooms/RoomManager.h"
32 #include "utils/ConfigManager.h"
33 #include "utils/Helper.h"
34 #include "utils/LogManager.h"
35 #include "utils/MakeUnique.h"
36 #include "utils/Random.h"
37 
38 const std::string RoomPrisonName = "Prison";
39 const std::string RoomPrisonNameDisplay = "Prison room";
40 const RoomType RoomPrison::mRoomType = RoomType::prison;
41 
42 namespace
43 {
44 class RoomPrisonFactory : public RoomFactory
45 {
getRoomType() const46     RoomType getRoomType() const override
47     { return RoomPrison::mRoomType; }
48 
getName() const49     const std::string& getName() const override
50     { return RoomPrisonName; }
51 
getNameReadable() const52     const std::string& getNameReadable() const override
53     { return RoomPrisonNameDisplay; }
54 
getCostPerTile() const55     int getCostPerTile() const override
56     { return ConfigManager::getSingleton().getRoomConfigInt32("PrisonCostPerTile"); }
57 
checkBuildRoom(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const58     void checkBuildRoom(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
59     {
60         checkBuildRoomDefault(gameMap, RoomPrison::mRoomType, inputManager, inputCommand);
61     }
62 
buildRoom(GameMap * gameMap,Player * player,ODPacket & packet) const63     bool buildRoom(GameMap* gameMap, Player* player, ODPacket& packet) const override
64     {
65         std::vector<Tile*> tiles;
66         if(!getRoomTilesDefault(tiles, gameMap, player, packet))
67             return false;
68 
69         int32_t pricePerTarget = RoomManager::costPerTile(RoomPrison::mRoomType);
70         int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
71         if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
72             return false;
73 
74         RoomPrison* room = new RoomPrison(gameMap);
75         return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
76     }
77 
checkBuildRoomEditor(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const78     void checkBuildRoomEditor(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
79     {
80         checkBuildRoomDefaultEditor(gameMap, RoomPrison::mRoomType, inputManager, inputCommand);
81     }
82 
buildRoomEditor(GameMap * gameMap,ODPacket & packet) const83     bool buildRoomEditor(GameMap* gameMap, ODPacket& packet) const override
84     {
85         RoomPrison* room = new RoomPrison(gameMap);
86         return buildRoomDefaultEditor(gameMap, room, packet);
87     }
88 
getRoomFromStream(GameMap * gameMap,std::istream & is) const89     Room* getRoomFromStream(GameMap* gameMap, std::istream& is) const override
90     {
91         RoomPrison* room = new RoomPrison(gameMap);
92         if(!Room::importRoomFromStream(*room, is))
93         {
94             OD_LOG_ERR("Error while building a room from the stream");
95         }
96         return room;
97     }
98 
buildRoomOnTiles(GameMap * gameMap,Player * player,const std::vector<Tile * > & tiles) const99     bool buildRoomOnTiles(GameMap* gameMap, Player* player, const std::vector<Tile*>& tiles) const override
100     {
101         int32_t pricePerTarget = RoomManager::costPerTile(RoomPrison::mRoomType);
102         int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
103         if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
104             return false;
105 
106         RoomPrison* room = new RoomPrison(gameMap);
107         return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
108     }
109 };
110 
111 // Register the factory
112 static RoomRegister reg(new RoomPrisonFactory);
113 }
114 
115 static const int32_t OFFSET_TILE_X = 0;
116 static const int32_t OFFSET_TILE_Y = -1;
117 
RoomPrison(GameMap * gameMap)118 RoomPrison::RoomPrison(GameMap* gameMap) :
119     Room(gameMap)
120 {
121     setMeshName("PrisonGround");
122 }
123 
notifyActiveSpotCreated(ActiveSpotPlace place,Tile * tile)124 BuildingObject* RoomPrison::notifyActiveSpotCreated(ActiveSpotPlace place, Tile* tile)
125 {
126     switch(place)
127     {
128         case ActiveSpotPlace::activeSpotCenter:
129         {
130             // Prison do not have central active spot
131             return nullptr;
132         }
133         case ActiveSpotPlace::activeSpotLeft:
134         {
135             return new BuildingObject(getGameMap(), *this, "Skull", *tile, 90.0, false);
136         }
137         case ActiveSpotPlace::activeSpotRight:
138         {
139             return new BuildingObject(getGameMap(), *this, "Skull", *tile, 270.0, false);
140         }
141         case ActiveSpotPlace::activeSpotTop:
142         {
143             return new BuildingObject(getGameMap(), *this, "Skull", *tile, 0.0, false);
144         }
145         case ActiveSpotPlace::activeSpotBottom:
146         {
147             return new BuildingObject(getGameMap(), *this, "Skull", *tile, 180.0, false);
148         }
149         default:
150             break;
151     }
152     return nullptr;
153 }
154 
absorbRoom(Room * r)155 void RoomPrison::absorbRoom(Room *r)
156 {
157     if(r->getType() != getType())
158     {
159         OD_LOG_ERR("Trying to merge incompatible rooms: " + getName() + ", type=" + RoomManager::getRoomNameFromRoomType(getType()) + ", with " + r->getName() + ", type=" + RoomManager::getRoomNameFromRoomType(r->getType()));
160         return;
161     }
162     RoomPrison* rc = static_cast<RoomPrison*>(r);
163     mPendingPrisoners.insert(mPendingPrisoners.end(), rc->mPendingPrisoners.begin(), rc->mPendingPrisoners.end());
164     rc->mPendingPrisoners.clear();
165 
166     Room::absorbRoom(r);
167 }
168 
doUpkeep()169 void RoomPrison::doUpkeep()
170 {
171     Room::doUpkeep();
172 
173     if(mCoveredTiles.empty())
174         return;
175 
176     // We check if we have enough room for all prisoners
177     uint32_t nbCreatures = 0;
178     for(Tile* tile : mCoveredTiles)
179     {
180         for(GameEntity* entity : tile->getEntitiesInTile())
181         {
182             if(entity->getObjectType() != GameEntityType::creature)
183                 continue;
184 
185             Creature* creature = static_cast<Creature*>(entity);
186             if(creature->getSeatPrison() != getSeat())
187                 continue;
188 
189             Tile* creatureTile = creature->getPositionTile();
190             if(creatureTile == nullptr)
191             {
192                 OD_LOG_ERR("creatureName=" + creature->getName() + ", position=" + Helper::toString(creature->getPosition()));
193                 continue;
194             }
195 
196             if(nbCreatures >= mCentralActiveSpotTiles.size())
197             {
198                 // We have more prisoner than room. We free them
199                 creature->clearActionQueue();
200                 continue;
201             }
202 
203             // If the creature is dead (by slapping for example), we remove it without
204             // spawning any creature
205             if(!creature->isAlive())
206             {
207                 creature->clearActionQueue();
208                 continue;
209             }
210 
211             ++nbCreatures;
212             // We slightly damage the prisoner
213             double damage = ConfigManager::getSingleton().getRoomConfigDouble("PrisonDamagePerTurn");
214             creature->takeDamage(this, damage, 0.0, 0.0, 0.0, creatureTile, false);
215             creature->increaseTurnsPrison();
216 
217             if(creature->isAlive())
218                 continue;
219 
220             // The creature is dead. We can release it
221             OD_LOG_INF("creature=" + creature->getName() + " died in prison=" + getName());
222 
223             if((getSeat()->getPlayer() != nullptr) &&
224                getSeat()->getPlayer()->getIsHuman() &&
225                !getSeat()->getPlayer()->getHasLost())
226             {
227                 ServerNotification *serverNotification = new ServerNotification(
228                     ServerNotificationType::chatServer, getSeat()->getPlayer());
229                 std::string msg = "A creature died starving in your prison";
230                 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
231                 ODServer::getSingleton().queueServerNotification(serverNotification);
232             }
233 
234             creature->clearActionQueue();
235             creature->removeFromGameMap();
236             creature->deleteYourself();
237 
238             const std::string& className = ConfigManager::getSingleton().getRoomConfigString("PrisonSpawnClass");
239             const CreatureDefinition* classToSpawn = getGameMap()->getClassDescription(className);
240             if(classToSpawn == nullptr)
241             {
242                 OD_LOG_ERR("className=" + className);
243                 continue;
244             }
245             // Create a new creature and copy over the class-based creature parameters.
246             Creature* newCreature = new Creature(getGameMap(), classToSpawn, getSeat());
247 
248             // Add the creature to the gameMap and create meshes so it is visible.
249             newCreature->addToGameMap();
250             newCreature->setPosition(Ogre::Vector3(creatureTile->getX(), creatureTile->getY(), 0.0f));
251             newCreature->createMesh();
252         }
253     }
254 }
255 
exportToStream(std::ostream & os) const256 void RoomPrison::exportToStream(std::ostream& os) const
257 {
258     Room::exportToStream(os);
259 
260     std::vector<Creature*> creatures;
261     for(Tile* tile : mCoveredTiles)
262     {
263         for(GameEntity* entity : tile->getEntitiesInTile())
264         {
265             if(entity->getObjectType() != GameEntityType::creature)
266                 continue;
267 
268             Creature* creature = static_cast<Creature*>(entity);
269             if(creature->getSeatPrison() != getSeat())
270                 continue;
271 
272             creatures.push_back(creature);
273         }
274     }
275 
276     uint32_t nb = creatures.size();
277     os << nb;
278     for(Creature* creature : creatures)
279     {
280         os << "\t" << creature->getName();
281     }
282     os << "\n";
283 }
284 
importFromStream(std::istream & is)285 bool RoomPrison::importFromStream(std::istream& is)
286 {
287     if(!Room::importFromStream(is))
288         return false;
289 
290     uint32_t nb;
291     if(!(is >> nb))
292         return false;
293     while(nb > 0)
294     {
295         std::string creatureName;
296         if(!(is >> creatureName))
297             return false;
298 
299         mPrisonersLoad.push_back(creatureName);
300         nb--;
301     }
302 
303     return true;
304 }
305 
restoreInitialEntityState()306 void RoomPrison::restoreInitialEntityState()
307 {
308     for(const std::string& creatureName : mPrisonersLoad)
309     {
310         Creature* creature = getGameMap()->getCreature(creatureName);
311         if(creature == nullptr)
312         {
313             OD_LOG_ERR("creatureName=" + creatureName);
314             continue;
315         }
316 
317         creature->clearActionQueue();
318         creature->pushAction(Utils::make_unique<CreatureActionUseRoom>(*creature, *this, true));
319     }
320 }
321 
hasOpenCreatureSpot(Creature * creature)322 bool RoomPrison::hasOpenCreatureSpot(Creature* creature)
323 {
324     // We count current prisoners + prisoners on their way
325     uint32_t nbCreatures = countPrisoners();
326     nbCreatures += mPendingPrisoners.size();
327     if(nbCreatures >= mCentralActiveSpotTiles.size())
328         return false;
329 
330     return true;
331 }
332 
addCreatureUsingRoom(Creature * creature)333 bool RoomPrison::addCreatureUsingRoom(Creature* creature)
334 {
335     if(!Room::addCreatureUsingRoom(creature))
336         return false;
337 
338     creature->setInJail(this);
339 
340     return true;
341 }
342 
removeCreatureUsingRoom(Creature * creature)343 void RoomPrison::removeCreatureUsingRoom(Creature* creature)
344 {
345     Room::removeCreatureUsingRoom(creature);
346 
347     creature->setInJail(nullptr);
348 }
349 
hasCarryEntitySpot(GameEntity * carriedEntity)350 bool RoomPrison::hasCarryEntitySpot(GameEntity* carriedEntity)
351 {
352     if(carriedEntity->getObjectType() != GameEntityType::creature)
353         return false;
354 
355     Creature* creature = static_cast<Creature*>(carriedEntity);
356 
357     // Only ko to death enemy creatures should be carried to prison
358     if(creature->getKoTurnCounter() >= 0)
359        return false;
360 
361     // Only enemy creatures should be brought to prison
362     if(getSeat()->isAlliedSeat(creature->getSeat()))
363        return false;
364 
365     return hasOpenCreatureSpot(creature);
366 }
367 
askSpotForCarriedEntity(GameEntity * carriedEntity)368 Tile* RoomPrison::askSpotForCarriedEntity(GameEntity* carriedEntity)
369 {
370     if(carriedEntity->getObjectType() != GameEntityType::creature)
371     {
372         OD_LOG_ERR("room=" + getName() + ", entity=" + carriedEntity->getName());
373         return nullptr;
374     }
375 
376     Creature* creature = static_cast<Creature*>(carriedEntity);
377     mPendingPrisoners.push_back(creature);
378     return getCoveredTile(0);
379 }
380 
notifyCarryingStateChanged(Creature * carrier,GameEntity * carriedEntity)381 void RoomPrison::notifyCarryingStateChanged(Creature* carrier, GameEntity* carriedEntity)
382 {
383     // The carrier has brought the enemy creature
384     if(carriedEntity->getObjectType() != GameEntityType::creature)
385     {
386         OD_LOG_ERR("room=" + getName() + ", entity=" + carriedEntity->getName());
387         return;
388     }
389 
390     Creature* prisonerCreature = static_cast<Creature*>(carriedEntity);
391 
392     // We check if we were waiting for this creature
393     // We release the pending prisoners before pushing the action room to make sure
394     // the place is free when we add the creature
395     auto it = std::find(mPendingPrisoners.begin(), mPendingPrisoners.end(), prisonerCreature);
396     if(it == mPendingPrisoners.end())
397     {
398         OD_LOG_ERR("room=" + getName() + ", unexpected creature=" + prisonerCreature->getName());
399         return;
400     }
401 
402     mPendingPrisoners.erase(it);
403 
404     prisonerCreature->clearActionQueue();
405     prisonerCreature->pushAction(Utils::make_unique<CreatureActionUseRoom>(*prisonerCreature, *this, true));
406     prisonerCreature->resetKoTurns();
407 }
408 
countPrisoners()409 uint32_t RoomPrison::countPrisoners()
410 {
411     uint32_t nbCreatures = 0;
412     for(Tile* tile : mCoveredTiles)
413     {
414         for(GameEntity* entity : tile->getEntitiesInTile())
415         {
416             if(entity->getObjectType() != GameEntityType::creature)
417                 continue;
418 
419             Creature* creature = static_cast<Creature*>(entity);
420             if(creature->getSeatPrison() != getSeat())
421                 continue;
422 
423             ++nbCreatures;
424         }
425     }
426 
427     return nbCreatures;
428 }
429 
isInContainment(Creature & creature)430 bool RoomPrison::isInContainment(Creature& creature)
431 {
432     if(creature.getSeatPrison() != getSeat())
433         return false;
434 
435     return true;
436 }
437 
useRoom(Creature & creature,bool forced)438 bool RoomPrison::useRoom(Creature& creature, bool forced)
439 {
440     if(Random::Uint(1, 4) > 1)
441         return false;
442 
443     Tile* creatureTile = creature.getPositionTile();
444     if(creatureTile == nullptr)
445     {
446         OD_LOG_ERR("room=" + getName() + ", creatureName=" + creature.getName() + ", position=" + Helper::toString(creature.getPosition()));
447         return false;
448     }
449 
450     std::vector<Tile*> availableTiles;
451     for(Tile* tile : creatureTile->getAllNeighbors())
452     {
453         if(tile->getCoveringBuilding() != this)
454             continue;
455 
456         availableTiles.push_back(tile);
457     }
458 
459     if(availableTiles.empty())
460         return false;
461 
462     uint32_t index = Random::Uint(0, availableTiles.size() - 1);
463     Tile* tileDest = availableTiles[index];
464     Ogre::Vector3 v (static_cast<Ogre::Real>(tileDest->getX()), static_cast<Ogre::Real>(tileDest->getY()), 0.0);
465     std::vector<Ogre::Vector3> path;
466     path.push_back(v);
467     creature.setWalkPath(EntityAnimation::flee_anim, EntityAnimation::idle_anim, true, true, path);
468 
469     uint32_t nbTurns = Random::Uint(3, 6);
470     creature.setJobCooldown(nbTurns);
471 
472     return false;
473 }
474 
creatureDropped(Creature & creature)475 void RoomPrison::creatureDropped(Creature& creature)
476 {
477     // Owned and enemy creatures can be tortured
478     if((getSeat() != creature.getSeat()) && (getSeat()->isAlliedSeat(creature.getSeat())))
479         return;
480 
481     if(!hasOpenCreatureSpot(&creature))
482         return;
483 
484     // We only push the use room action. We do not want this creature to be
485     // considered as searching for a job
486     creature.clearActionQueue();
487     creature.pushAction(Utils::make_unique<CreatureActionUseRoom>(creature, *this, true));
488 }
489