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