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/RoomCrypt.h"
19
20 #include "entities/BuildingObject.h"
21 #include "entities/Creature.h"
22 #include "entities/GameEntityType.h"
23 #include "entities/SmallSpiderEntity.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/LogManager.h"
33 #include "utils/Random.h"
34
35 const std::string RoomCryptName = "Crypt";
36 const std::string RoomCryptNameDisplay = "Crypt room";
37 const RoomType RoomCrypt::mRoomType = RoomType::crypt;
38
39 namespace
40 {
41 class RoomCryptFactory : public RoomFactory
42 {
getRoomType() const43 RoomType getRoomType() const override
44 { return RoomCrypt::mRoomType; }
45
getName() const46 const std::string& getName() const override
47 { return RoomCryptName; }
48
getNameReadable() const49 const std::string& getNameReadable() const override
50 { return RoomCryptNameDisplay; }
51
getCostPerTile() const52 int getCostPerTile() const override
53 { return ConfigManager::getSingleton().getRoomConfigInt32("CryptCostPerTile"); }
54
checkBuildRoom(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const55 void checkBuildRoom(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
56 {
57 checkBuildRoomDefault(gameMap, RoomCrypt::mRoomType, inputManager, inputCommand);
58 }
59
buildRoom(GameMap * gameMap,Player * player,ODPacket & packet) const60 bool buildRoom(GameMap* gameMap, Player* player, ODPacket& packet) const override
61 {
62 std::vector<Tile*> tiles;
63 if(!getRoomTilesDefault(tiles, gameMap, player, packet))
64 return false;
65
66 int32_t pricePerTarget = RoomManager::costPerTile(RoomCrypt::mRoomType);
67 int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
68 if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
69 return false;
70
71 RoomCrypt* room = new RoomCrypt(gameMap);
72 return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
73 }
74
checkBuildRoomEditor(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const75 void checkBuildRoomEditor(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
76 {
77 checkBuildRoomDefaultEditor(gameMap, RoomCrypt::mRoomType, inputManager, inputCommand);
78 }
79
buildRoomEditor(GameMap * gameMap,ODPacket & packet) const80 bool buildRoomEditor(GameMap* gameMap, ODPacket& packet) const override
81 {
82 RoomCrypt* room = new RoomCrypt(gameMap);
83 return buildRoomDefaultEditor(gameMap, room, packet);
84 }
85
getRoomFromStream(GameMap * gameMap,std::istream & is) const86 Room* getRoomFromStream(GameMap* gameMap, std::istream& is) const override
87 {
88 RoomCrypt* room = new RoomCrypt(gameMap);
89 if(!Room::importRoomFromStream(*room, is))
90 {
91 OD_LOG_ERR("Error while building a room from the stream");
92 }
93 return room;
94 }
95
buildRoomOnTiles(GameMap * gameMap,Player * player,const std::vector<Tile * > & tiles) const96 bool buildRoomOnTiles(GameMap* gameMap, Player* player, const std::vector<Tile*>& tiles) const override
97 {
98 int32_t pricePerTarget = RoomManager::costPerTile(RoomCrypt::mRoomType);
99 int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
100 if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
101 return false;
102
103 RoomCrypt* room = new RoomCrypt(gameMap);
104 return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
105 }
106 };
107
108 // Register the factory
109 static RoomRegister reg(new RoomCryptFactory);
110 }
111
112 static const int32_t OFFSET_TILE_X = 0;
113 static const int32_t OFFSET_TILE_Y = -1;
114
RoomCrypt(GameMap * gameMap)115 RoomCrypt::RoomCrypt(GameMap* gameMap) :
116 Room(gameMap),
117 mRottenPoints(0)
118 {
119 setMeshName("Crypt");
120 }
121
notifyActiveSpotCreated(ActiveSpotPlace place,Tile * tile)122 BuildingObject* RoomCrypt::notifyActiveSpotCreated(ActiveSpotPlace place, Tile* tile)
123 {
124 switch(place)
125 {
126 case ActiveSpotPlace::activeSpotCenter:
127 {
128 mRottingCreatures[tile] = std::pair<Creature*,int32_t>(nullptr, -1);
129 int rnd = Random::Int(0, 100);
130 if (rnd < 33)
131 return new BuildingObject(getGameMap(), *this, "KnightCoffin", *tile, 0.0, false);
132 else if (rnd < 66)
133 return new BuildingObject(getGameMap(), *this, "CelticCross", *tile, 0.0, false);
134 else
135 return new BuildingObject(getGameMap(), *this, "StoneCoffin", *tile, 0.0, false);
136 }
137 case ActiveSpotPlace::activeSpotLeft:
138 {
139 return new BuildingObject(getGameMap(), *this, "KnightStatue", *tile, 90.0, false);
140 }
141 case ActiveSpotPlace::activeSpotRight:
142 {
143 return new BuildingObject(getGameMap(), *this, "KnightStatue", *tile, 270.0, false);
144 }
145 case ActiveSpotPlace::activeSpotTop:
146 {
147 return new BuildingObject(getGameMap(), *this, "KnightStatue2", *tile, 0.0, false);
148 }
149 case ActiveSpotPlace::activeSpotBottom:
150 {
151 return new BuildingObject(getGameMap(), *this, "KnightStatue2", *tile, 180.0, false);
152 }
153 default:
154 break;
155 }
156 return nullptr;
157 }
158
notifyActiveSpotRemoved(ActiveSpotPlace place,Tile * tile)159 void RoomCrypt::notifyActiveSpotRemoved(ActiveSpotPlace place, Tile* tile)
160 {
161 Room::notifyActiveSpotRemoved(place, tile);
162 if(place != ActiveSpotPlace::activeSpotCenter)
163 return;
164
165 std::pair<Creature*,int32_t> rottingCreature = mRottingCreatures[tile];
166 mRottingCreatures.erase(tile);
167 if(rottingCreature.first == nullptr)
168 return;
169
170 // If the dead creature is already rotting, we add it back to its tile so that it can continue
171 // its normal dead creature life ^^
172 if(rottingCreature.second == -1)
173 return;
174
175 rottingCreature.first->addEntityToPositionTile();
176 }
177
absorbRoom(Room * r)178 void RoomCrypt::absorbRoom(Room *r)
179 {
180 if(r->getType() != getType())
181 {
182 OD_LOG_ERR("Trying to merge incompatible rooms: " + getName() + ", type=" + RoomManager::getRoomNameFromRoomType(getType()) + ", with " + r->getName() + ", type=" + RoomManager::getRoomNameFromRoomType(r->getType()));
183 return;
184 }
185 RoomCrypt* rc = static_cast<RoomCrypt*>(r);
186 mRottingCreatures.insert(rc->mRottingCreatures.begin(), rc->mRottingCreatures.end());
187 rc->mRottingCreatures.clear();
188 mRottenPoints += rc->mRottenPoints;
189
190 Room::absorbRoom(r);
191 }
192
doUpkeep()193 void RoomCrypt::doUpkeep()
194 {
195 Room::doUpkeep();
196
197 if(mCoveredTiles.empty())
198 return;
199
200 // Each central active spot has a probability to spawn a spider
201 for(Tile* tile : mCentralActiveSpotTiles)
202 {
203 if(Random::Int(1, 10) > 1)
204 continue;
205
206 SmallSpiderEntity* spider = new SmallSpiderEntity(getGameMap(), getName(), 10);
207 Ogre::Vector3 pos(static_cast<Ogre::Real>(tile->getX()), static_cast<Ogre::Real>(tile->getY()), 0.0f);
208 spider->addToGameMap();
209 spider->createMesh();
210 spider->setPosition(pos);
211 }
212
213 // We increment rotting creatures counter
214 for(std::pair<Tile* const, std::pair<Creature*, int32_t> >& p : mRottingCreatures)
215 {
216 if((p.second.first == nullptr) || (p.second.second == -1))
217 continue;
218
219 ConfigManager& configManager = ConfigManager::getSingleton();
220
221 ++p.second.second;
222 if(p.second.second < configManager.getRoomConfigInt32("CryptRotNbTurns"))
223 continue;
224
225 // We add the rotten creature points to the room and release the active spot
226 double coef = 1.0 + static_cast<double>(mNumActiveSpots - mCentralActiveSpotTiles.size()) * configManager.getRoomConfigDouble("CryptBonusWallActiveSpot");
227 Creature* c = p.second.first;
228 mRottenPoints += static_cast<int32_t>(c->getMaxHp() * coef);
229
230 c->removeFromGameMap();
231 c->deleteYourself();
232 p.second.first = nullptr;
233 p.second.second = -1;
234
235 int32_t maxCreatures = configManager.getMaxCreaturesPerSeatAbsolute();
236 int32_t numCreatures = getGameMap()->getCreaturesBySeat(getSeat()).size();
237 int32_t cryptPointsForSpawn = configManager.getRoomConfigInt32("CryptPointsForSpawn");
238 if((numCreatures < maxCreatures) &&
239 (mRottenPoints >= cryptPointsForSpawn))
240 {
241 Tile* tileSpawn = p.first;
242 mRottenPoints -= cryptPointsForSpawn;
243 const std::string& className = configManager.getRoomConfigString("CryptSpawnClass");
244 const CreatureDefinition* classToSpawn = getGameMap()->getClassDescription(className);
245 if(classToSpawn == nullptr)
246 {
247 OD_LOG_ERR("className=" + className);
248 continue;
249 }
250
251 if((getSeat()->getPlayer() != nullptr) &&
252 getSeat()->getPlayer()->getIsHuman() &&
253 !getSeat()->getPlayer()->getHasLost())
254 {
255 ServerNotification *serverNotification = new ServerNotification(
256 ServerNotificationType::chatServer, getSeat()->getPlayer());
257 std::string msg = "A creature has raised in your crypt thanks to the blood of the creatures rotting there";
258 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
259 ODServer::getSingleton().queueServerNotification(serverNotification);
260 }
261
262 // Create a new creature and copy over the class-based creature parameters.
263 Creature* newCreature = new Creature(getGameMap(), classToSpawn, getSeat());
264
265 // Add the creature to the gameMap and create meshes so it is visible.
266 newCreature->addToGameMap();
267 newCreature->setPosition(Ogre::Vector3(tileSpawn->getX(), tileSpawn->getY(), 0.0f));
268 newCreature->createMesh();
269 }
270 }
271 }
272
hasCarryEntitySpot(GameEntity * carriedEntity)273 bool RoomCrypt::hasCarryEntitySpot(GameEntity* carriedEntity)
274 {
275 if(carriedEntity->getObjectType() != GameEntityType::creature)
276 return false;
277
278 Creature* creature = static_cast<Creature*>(carriedEntity);
279 // Only dead creatures can be carried to the crypt. Seat doesn't matter
280 if(creature->isAlive())
281 return false;
282
283 for(std::pair<Tile* const, std::pair<Creature*, int32_t> >& p : mRottingCreatures)
284 {
285 if(p.second.first == nullptr)
286 return true;
287 }
288 return false;
289 }
290
askSpotForCarriedEntity(GameEntity * carriedEntity)291 Tile* RoomCrypt::askSpotForCarriedEntity(GameEntity* carriedEntity)
292 {
293 if(carriedEntity->getObjectType() != GameEntityType::creature)
294 {
295 OD_LOG_ERR("room=" + getName() + ", entity=" + carriedEntity->getName());
296 return nullptr;
297 }
298
299 Creature* creature = static_cast<Creature*>(carriedEntity);
300 for(std::pair<Tile* const, std::pair<Creature*, int32_t> >& p : mRottingCreatures)
301 {
302 if(p.second.first == nullptr)
303 {
304 p.second.first = creature;
305 p.second.second = -1;
306 Tile* spot = p.first;
307 Tile* t = getGameMap()->getTile(spot->getX() + OFFSET_TILE_X,
308 spot->getY() + OFFSET_TILE_Y);
309 OD_ASSERT_TRUE_MSG(t != nullptr, "room=" + getName() + ", spot=" + Tile::displayAsString(spot));
310 return t;
311 }
312 }
313 return nullptr;
314 }
315
notifyCarryingStateChanged(Creature * carrier,GameEntity * carriedEntity)316 void RoomCrypt::notifyCarryingStateChanged(Creature* carrier, GameEntity* carriedEntity)
317 {
318 for(std::pair<Tile* const, std::pair<Creature*, int32_t> >& p : mRottingCreatures)
319 {
320 if(p.second.first == carriedEntity)
321 {
322 // We check if the carrier is at the expected destination
323 Tile* carrierTile = carrier->getPositionTile();
324 if(carrierTile == nullptr)
325 {
326 OD_LOG_ERR("carrier=" + carrier->getName());
327 p.second.first = nullptr;
328 p.second.second = -1;
329 return;
330 }
331
332 Tile* spot = p.first;
333 Tile* tileExpected = getGameMap()->getTile(spot->getX() + OFFSET_TILE_X,
334 spot->getY() + OFFSET_TILE_Y);
335 if(tileExpected != carrierTile)
336 {
337 p.second.first = nullptr;
338 p.second.second = -1;
339 return;
340 }
341
342 // The carrier has brought the dead creature
343 if(carriedEntity->getObjectType() != GameEntityType::creature)
344 {
345 OD_LOG_ERR("room=" + getName() + ", entity=" + carriedEntity->getName());
346 return;
347 }
348
349 Creature* deadCreature = static_cast<Creature*>(carriedEntity);
350 Tile* tileDeadCreature = deadCreature->getPositionTile();
351 if(tileDeadCreature == nullptr)
352 {
353 OD_LOG_ERR("deadCreature=" + deadCreature->getName());
354 return;
355 }
356 // Start rotting
357 deadCreature->removeEntityFromPositionTile();
358 p.second.second = 0;
359 return;
360 }
361 }
362
363 // We couldn't find the entity in the list. That may happen if the active spot has
364 // been erased between the time the carrier tried to come and the time it arrived.
365 // In any case, nothing to do
366 }
367
exportToStream(std::ostream & os) const368 void RoomCrypt::exportToStream(std::ostream& os) const
369 {
370 Room::exportToStream(os);
371 os << mRottenPoints << "\n";
372 // We do not save rotten creatures. They will automatically be carried again by workers
373 }
374
importFromStream(std::istream & is)375 bool RoomCrypt::importFromStream(std::istream& is)
376 {
377 if(!Room::importFromStream(is))
378 return false;
379 if(!(is >> mRottenPoints))
380 return false;
381
382 return true;
383 }
384