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