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