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/RoomTorture.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/Tile.h"
25 #include "game/Player.h"
26 #include "game/Seat.h"
27 #include "gamemap/GameMap.h"
28 #include "network/ODServer.h"
29 #include "network/ServerNotification.h"
30 #include "rooms/RoomManager.h"
31 #include "utils/ConfigManager.h"
32 #include "utils/Helper.h"
33 #include "utils/LogManager.h"
34 #include "utils/MakeUnique.h"
35 #include "utils/Random.h"
36 
37 const std::string RoomTortureName = "Torture";
38 const std::string RoomTortureNameDisplay = "Torture room";
39 const RoomType RoomTorture::mRoomType = RoomType::torture;
40 
41 namespace
42 {
43 class RoomTortureFactory : public RoomFactory
44 {
getRoomType() const45     RoomType getRoomType() const override
46     { return RoomTorture::mRoomType; }
47 
getName() const48     const std::string& getName() const override
49     { return RoomTortureName; }
50 
getNameReadable() const51     const std::string& getNameReadable() const override
52     { return RoomTortureNameDisplay; }
53 
getCostPerTile() const54     int getCostPerTile() const override
55     { return ConfigManager::getSingleton().getRoomConfigInt32("TortureCostPerTile"); }
56 
checkBuildRoom(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const57     void checkBuildRoom(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
58     {
59         checkBuildRoomDefault(gameMap, RoomTorture::mRoomType, inputManager, inputCommand);
60     }
61 
buildRoom(GameMap * gameMap,Player * player,ODPacket & packet) const62     bool buildRoom(GameMap* gameMap, Player* player, ODPacket& packet) const override
63     {
64         std::vector<Tile*> tiles;
65         if(!getRoomTilesDefault(tiles, gameMap, player, packet))
66             return false;
67 
68         int32_t pricePerTarget = RoomManager::costPerTile(RoomTorture::mRoomType);
69         int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
70         if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
71             return false;
72 
73         RoomTorture* room = new RoomTorture(gameMap);
74         return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
75     }
76 
checkBuildRoomEditor(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const77     void checkBuildRoomEditor(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
78     {
79         checkBuildRoomDefaultEditor(gameMap, RoomTorture::mRoomType, inputManager, inputCommand);
80     }
81 
buildRoomEditor(GameMap * gameMap,ODPacket & packet) const82     bool buildRoomEditor(GameMap* gameMap, ODPacket& packet) const override
83     {
84         RoomTorture* room = new RoomTorture(gameMap);
85         return buildRoomDefaultEditor(gameMap, room, packet);
86     }
87 
getRoomFromStream(GameMap * gameMap,std::istream & is) const88     Room* getRoomFromStream(GameMap* gameMap, std::istream& is) const override
89     {
90         RoomTorture* room = new RoomTorture(gameMap);
91         if(!Room::importRoomFromStream(*room, is))
92         {
93             OD_LOG_ERR("Error while building a room from the stream");
94         }
95         return room;
96     }
97 
buildRoomOnTiles(GameMap * gameMap,Player * player,const std::vector<Tile * > & tiles) const98     bool buildRoomOnTiles(GameMap* gameMap, Player* player, const std::vector<Tile*>& tiles) const override
99     {
100         int32_t pricePerTarget = RoomManager::costPerTile(RoomTorture::mRoomType);
101         int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
102         if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
103             return false;
104 
105         RoomTorture* room = new RoomTorture(gameMap);
106         return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
107     }
108 };
109 
110 // Register the factory
111 static RoomRegister reg(new RoomTortureFactory);
112 }
113 
RoomTorture(GameMap * gameMap)114 RoomTorture::RoomTorture(GameMap* gameMap) :
115     Room(gameMap)
116 {
117     setMeshName("TortureGround");
118 }
119 
notifyActiveSpotCreated(ActiveSpotPlace place,Tile * tile)120 BuildingObject* RoomTorture::notifyActiveSpotCreated(ActiveSpotPlace place, Tile* tile)
121 {
122     switch(place)
123     {
124         case ActiveSpotPlace::activeSpotCenter:
125         {
126             Ogre::Real x = static_cast<Ogre::Real>(tile->getX());
127             Ogre::Real y = static_cast<Ogre::Real>(tile->getY());
128             Ogre::Real z = 0;
129             mCreaturesSpots.emplace(std::make_pair(tile, RoomTortureCreatureInfo()));
130             return new BuildingObject(getGameMap(), *this, "TortureObject", tile, x, y, z, 0.0, false);
131         }
132         case ActiveSpotPlace::activeSpotLeft:
133         {
134             return new BuildingObject(getGameMap(), *this, "Chimney", *tile, 90.0, false);
135         }
136         case ActiveSpotPlace::activeSpotRight:
137         {
138             return new BuildingObject(getGameMap(), *this, "Chimney", *tile, 270.0, false);
139         }
140         case ActiveSpotPlace::activeSpotTop:
141         {
142             return new BuildingObject(getGameMap(), *this, "Chimney", *tile, 0.0, false);
143         }
144         case ActiveSpotPlace::activeSpotBottom:
145         {
146             return new BuildingObject(getGameMap(), *this, "Chimney", *tile, 180.0, false);
147         }
148         default:
149             break;
150     }
151     return nullptr;
152 }
153 
absorbRoom(Room * room)154 void RoomTorture::absorbRoom(Room* room)
155 {
156     if(room->getType() != getType())
157     {
158         OD_LOG_ERR("Trying to merge incompatible rooms: " + getName() + ", type=" + RoomManager::getRoomNameFromRoomType(getType()) + ", with " + room->getName() + ", type=" + RoomManager::getRoomNameFromRoomType(room->getType()));
159         return;
160     }
161 
162     Room::absorbRoom(room);
163 
164     RoomTorture* roomAbs = static_cast<RoomTorture*>(room);
165     OD_ASSERT_TRUE_MSG(roomAbs->mCreaturesSpots.empty(), "room=" + getName() + ", roomAbs=" + roomAbs->getName());
166 }
167 
notifyActiveSpotRemoved(ActiveSpotPlace place,Tile * tile)168 void RoomTorture::notifyActiveSpotRemoved(ActiveSpotPlace place, Tile* tile)
169 {
170     Room::notifyActiveSpotRemoved(place, tile);
171 
172     if(place != ActiveSpotPlace::activeSpotCenter)
173         return;
174 
175     auto it = mCreaturesSpots.find(tile);
176     if(it == mCreaturesSpots.end())
177     {
178         OD_LOG_ERR("room=" + getName() + ", tile=" + Tile::displayAsString(tile));
179         return;
180     }
181 
182     RoomTortureCreatureInfo& info = it->second;
183     if(info.mCreature != nullptr)
184     {
185         // We check if another active spot is free. If yes, use it
186         for(std::pair<Tile* const,RoomTortureCreatureInfo>& p : mCreaturesSpots)
187         {
188             if(p.second.mCreature != nullptr)
189                 continue;
190 
191             // We found a spot
192             p.second.mCreature = info.mCreature;
193             p.second.mIsReady = false;
194             p.second.mState = 0;
195             info.mCreature = nullptr;
196         }
197 
198         if(info.mCreature != nullptr)
199             info.mCreature->clearActionQueue();
200     }
201 
202     // clearActionQueue should have released mCreaturesSpots
203     OD_ASSERT_TRUE_MSG(info.mCreature == nullptr, "room=" + getName() + ", tile=" + Tile::displayAsString(tile) + ", creature=" + info.mCreature->getName());
204 
205     mCreaturesSpots.erase(it);
206 }
207 
hasOpenCreatureSpot(Creature * creature)208 bool RoomTorture::hasOpenCreatureSpot(Creature* creature)
209 {
210     for(std::pair<Tile* const,RoomTortureCreatureInfo>& p : mCreaturesSpots)
211     {
212         if(p.second.mCreature == nullptr)
213             return true;
214     }
215 
216     return false;
217 }
218 
addCreatureUsingRoom(Creature * creature)219 bool RoomTorture::addCreatureUsingRoom(Creature* creature)
220 {
221     RoomTortureCreatureInfo* infoToUse = nullptr;
222     for(std::pair<Tile* const,RoomTortureCreatureInfo>& p : mCreaturesSpots)
223     {
224         if(p.second.mCreature != nullptr)
225             continue;
226 
227         infoToUse = &p.second;
228         break;
229     }
230 
231     if(infoToUse == nullptr)
232     {
233         OD_LOG_ERR("room=" + getName() + ", creature=" + creature->getName());
234         return false;
235     }
236 
237     if(!Room::addCreatureUsingRoom(creature))
238         return false;
239 
240     creature->setInJail(this);
241 
242     infoToUse->mCreature = creature;
243     infoToUse->mIsReady = false;
244     infoToUse->mState = 0;
245     return true;
246 }
247 
removeCreatureUsingRoom(Creature * creature)248 void RoomTorture::removeCreatureUsingRoom(Creature* creature)
249 {
250     Room::removeCreatureUsingRoom(creature);
251 
252     // If the creature leaving the casino left another one playing alone, we
253     // should search for another creature alone and try to make them match
254     RoomTortureCreatureInfo* infoToUse = nullptr;
255     for(std::pair<Tile* const,RoomTortureCreatureInfo>& p : mCreaturesSpots)
256     {
257         if(p.second.mCreature != creature)
258             continue;
259 
260         infoToUse = &p.second;
261         break;
262     }
263 
264     if(infoToUse == nullptr)
265     {
266         OD_LOG_ERR("room=" + getName() + ", creature=" + creature->getName());
267         return;
268     }
269 
270     creature->setInJail(nullptr);
271 
272     infoToUse->mCreature = nullptr;
273     infoToUse->mIsReady = false;
274     infoToUse->mState = 0;
275 }
276 
doUpkeep()277 void RoomTorture::doUpkeep()
278 {
279     Room::doUpkeep();
280 
281     if (mCoveredTiles.empty())
282         return;
283 
284     ConfigManager& config = ConfigManager::getSingleton();
285     for(std::pair<Tile* const,RoomTortureCreatureInfo>& p : mCreaturesSpots)
286     {
287         if(p.second.mCreature == nullptr)
288             continue;
289         if(!p.second.mIsReady)
290             continue;
291 
292         Creature* creature = p.second.mCreature;
293         Tile* tileCreature = creature->getPositionTile();
294         if(tileCreature == nullptr)
295         {
296             OD_LOG_ERR("room=" + getName() + ", creature=" + creature->getName() + ", pos=" + Helper::toString(creature->getPosition()));
297             break;
298         }
299         creature->increaseTurnsTorture();
300         double damage = config.getRoomConfigDouble("TortureDamagePerTurn");
301         creature->takeDamage(this, damage, 0.0, 0.0, 0.0, tileCreature, false);
302         break;
303     }
304 }
305 
useRoom(Creature & creature,bool forced)306 bool RoomTorture::useRoom(Creature& creature, bool forced)
307 {
308     Tile* tileCreature = creature.getPositionTile();
309     if(tileCreature == nullptr)
310     {
311         OD_LOG_ERR("room=" + getName() + ", creature=" + creature.getName() + ", pos=" + Helper::toString(creature.getPosition()));
312         return false;
313     }
314 
315     ConfigManager& config = ConfigManager::getSingleton();
316     for(std::pair<Tile* const,RoomTortureCreatureInfo>& p : mCreaturesSpots)
317     {
318         if(p.second.mCreature != &creature)
319             continue;
320 
321         if(tileCreature != p.first)
322         {
323             creature.setDestination(p.first);
324             return false;
325         }
326 
327         // The creature is on the good tile
328         p.second.mIsReady = true;
329 
330         if((getSeat() != creature.getSeat()) &&
331            (Random::Double(0.0, 1.0) <= config.getRoomConfigDouble("TortureRallyPercent")))
332         {
333             // The creature changes side
334             creature.changeSeat(getSeat());
335             creature.clearActionQueue();
336 
337             // We return false because we don't want to choose an action before a next complete turn
338             if((getSeat()->getPlayer() != nullptr) &&
339                getSeat()->getPlayer()->getIsHuman() &&
340                !getSeat()->getPlayer()->getHasLost())
341             {
342                 ServerNotification *serverNotification = new ServerNotification(
343                     ServerNotificationType::chatServer, getSeat()->getPlayer());
344                 std::string msg = "Your tormentors have convinced another creature how sweet it is to live under your rule";
345                 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
346                 ODServer::getSingleton().queueServerNotification(serverNotification);
347             }
348             return false;
349         }
350 
351         // We start the fire effect and we set job cooldown
352         uint32_t nbTurns = Random::Uint(config.getRoomConfigUInt32("TortureSessionLengthMin"),
353             config.getRoomConfigUInt32("TortureSessionLengthMax"));
354         creature.setJobCooldown(nbTurns);
355 
356         BuildingObject* obj = getBuildingObjectFromTile(tileCreature);
357         if(obj == nullptr)
358         {
359             OD_LOG_ERR("room=" + getName() + ", tile=" + Tile::displayAsString(tileCreature));
360             return false;
361         }
362 
363         // We set the flame during half the session
364         ++p.second.mState;
365         switch(p.second.mState)
366         {
367             case 1:
368             {
369                 obj->addParticleEffect("Flame", nbTurns / 2);
370                 obj->fireRefresh();
371                 creature.setAnimationState(EntityAnimation::flee_anim);
372                 break;
373             }
374             case 2:
375             {
376                 creature.setAnimationState(EntityAnimation::idle_anim);
377                 p.second.mState = 0;
378                 break;
379             }
380             default:
381                 p.second.mState = 0;
382                 break;
383         }
384         return false;
385     }
386 
387     // We should not come here
388     OD_LOG_ERR("room=" + getName() + ", creature=" + creature.getName());
389     creature.clearActionQueue();
390     return false;
391 
392 }
393 
isInContainment(Creature & creature)394 bool RoomTorture::isInContainment(Creature& creature)
395 {
396     if(creature.getSeatPrison() != getSeat())
397         return false;
398 
399     return true;
400 }
401 
creatureDropped(Creature & creature)402 void RoomTorture::creatureDropped(Creature& creature)
403 {
404     // Owned and enemy creatures can be tortured
405     if((getSeat() != creature.getSeat()) && (getSeat()->isAlliedSeat(creature.getSeat())))
406         return;
407 
408     if(!hasOpenCreatureSpot(&creature))
409         return;
410 
411     // We only push the use room action. We do not want this creature to be
412     // considered as searching for a job
413     creature.clearActionQueue();
414     creature.pushAction(Utils::make_unique<CreatureActionUseRoom>(creature, *this, true));
415 }
exportToStream(std::ostream & os) const416 void RoomTorture::exportToStream(std::ostream& os) const
417 {
418     Room::exportToStream(os);
419 
420     std::vector<Creature*> creatures;
421     for(Tile* tile : mCoveredTiles)
422     {
423         for(GameEntity* entity : tile->getEntitiesInTile())
424         {
425             if(entity->getObjectType() != GameEntityType::creature)
426                 continue;
427 
428             Creature* creature = static_cast<Creature*>(entity);
429             if(creature->getSeatPrison() != getSeat())
430                 continue;
431 
432             creatures.push_back(creature);
433         }
434     }
435 
436     uint32_t nb = creatures.size();
437     os << nb;
438     for(Creature* creature : creatures)
439     {
440         os << "\t" << creature->getName();
441     }
442     os << "\n";
443 }
444 
importFromStream(std::istream & is)445 bool RoomTorture::importFromStream(std::istream& is)
446 {
447     if(!Room::importFromStream(is))
448         return false;
449 
450     uint32_t nb;
451     if(!(is >> nb))
452         return false;
453     while(nb > 0)
454     {
455         std::string creatureName;
456         if(!(is >> creatureName))
457             return false;
458 
459         mPrisonersLoad.push_back(creatureName);
460         nb--;
461     }
462 
463     return true;
464 }
465 
restoreInitialEntityState()466 void RoomTorture::restoreInitialEntityState()
467 {
468     for(const std::string& creatureName : mPrisonersLoad)
469     {
470         Creature* creature = getGameMap()->getCreature(creatureName);
471         if(creature == nullptr)
472         {
473             OD_LOG_ERR("creatureName=" + creatureName);
474             continue;
475         }
476 
477         creature->clearActionQueue();
478         creature->pushAction(Utils::make_unique<CreatureActionUseRoom>(*creature, *this, true));
479     }
480 }
481