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/RoomPortal.h"
19 
20 #include "entities/BuildingObject.h"
21 #include "entities/Creature.h"
22 #include "entities/CreatureDefinition.h"
23 #include "entities/PersistentObject.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/Random.h"
35 
36 #include <cmath>
37 
38 const std::string RoomPortalName = "Portal";
39 const std::string RoomPortalNameDisplay = "Portal room";
40 const RoomType RoomPortal::mRoomType = RoomType::portal;
41 
42 namespace
43 {
44 class RoomPortalFactory : public RoomFactory
45 {
getRoomType() const46     RoomType getRoomType() const override
47     { return RoomPortal::mRoomType; }
48 
getName() const49     const std::string& getName() const override
50     { return RoomPortalName; }
51 
getNameReadable() const52     const std::string& getNameReadable() const override
53     { return RoomPortalNameDisplay; }
54 
getCostPerTile() const55     int getCostPerTile() const override
56     { return 0; }
57 
checkBuildRoom(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const58     void checkBuildRoom(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
59     {
60         // Not buildable on game mode
61     }
62 
buildRoom(GameMap * gameMap,Player * player,ODPacket & packet) const63     bool buildRoom(GameMap* gameMap, Player* player, ODPacket& packet) const override
64     {
65         // Not buildable on game mode
66         return false;
67     }
68 
checkBuildRoomEditor(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const69     void checkBuildRoomEditor(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
70     {
71         return checkBuildRoomDefaultEditor(gameMap, RoomPortal::mRoomType, inputManager, inputCommand);
72     }
73 
buildRoomEditor(GameMap * gameMap,ODPacket & packet) const74     bool buildRoomEditor(GameMap* gameMap, ODPacket& packet) const override
75     {
76         RoomPortal* room = new RoomPortal(gameMap);
77         return buildRoomDefaultEditor(gameMap, room, packet);
78     }
79 
getRoomFromStream(GameMap * gameMap,std::istream & is) const80     Room* getRoomFromStream(GameMap* gameMap, std::istream& is) const override
81     {
82         RoomPortal* room = new RoomPortal(gameMap);
83         if(!Room::importRoomFromStream(*room, is))
84         {
85             OD_LOG_ERR("Error while building a room from the stream");
86         }
87         return room;
88     }
89 
buildRoomOnTiles(GameMap * gameMap,Player * player,const std::vector<Tile * > & tiles) const90     bool buildRoomOnTiles(GameMap* gameMap, Player* player, const std::vector<Tile*>& tiles) const override
91     {
92         // Not buildable in game mode
93         return false;
94     }
95 };
96 
97 // Register the factory
98 static RoomRegister reg(new RoomPortalFactory);
99 }
100 
101 static const double CLAIMED_VALUE_PER_TILE = 1.0;
102 
RoomPortal(GameMap * gameMap)103 RoomPortal::RoomPortal(GameMap* gameMap) :
104         Room(gameMap),
105         mSpawnCreatureCountdown(0),
106         mPortalObject(nullptr),
107         mClaimedValue(0),
108         mNbCreatureMaxIncrease(0)
109 {
110    setMeshName("");
111 }
112 
absorbRoom(Room * r)113 void RoomPortal::absorbRoom(Room *r)
114 {
115     if(r->getType() != getType())
116     {
117         OD_LOG_ERR("Trying to merge incompatible rooms: " + getName() + ", type=" + RoomManager::getRoomNameFromRoomType(getType()) + ", with " + r->getName() + ", type=" + RoomManager::getRoomNameFromRoomType(r->getType()));
118         return;
119     }
120     RoomPortal* oldRoom = static_cast<RoomPortal*>(r);
121     mClaimedValue += oldRoom->mClaimedValue;
122     // We keep the number of creatures increased by this portal
123     mNbCreatureMaxIncrease = oldRoom->mNbCreatureMaxIncrease;
124 
125     Room::absorbRoom(r);
126 }
127 
removeCoveredTile(Tile * t)128 bool RoomPortal::removeCoveredTile(Tile* t)
129 {
130     if(mClaimedValue > CLAIMED_VALUE_PER_TILE)
131         mClaimedValue -= CLAIMED_VALUE_PER_TILE;
132 
133     return Room::removeCoveredTile(t);
134 }
135 
isClaimable(Seat * seat) const136 bool RoomPortal::isClaimable(Seat* seat) const
137 {
138     return !getSeat()->isAlliedSeat(seat);
139 }
140 
claimForSeat(Seat * seat,Tile * tile,double danceRate)141 void RoomPortal::claimForSeat(Seat* seat, Tile* tile, double danceRate)
142 {
143     if(mClaimedValue > danceRate)
144     {
145         mClaimedValue-= danceRate;
146         return;
147     }
148 
149     mClaimedValue = static_cast<double>(numCoveredTiles());
150     setSeat(seat);
151 
152     for(Tile* tile : mCoveredTiles)
153         tile->claimTile(seat);
154 }
155 
updateActiveSpots()156 void RoomPortal::updateActiveSpots()
157 {
158     // Room::updateActiveSpots(); <<-- Disabled on purpose.
159     // We don't update the active spots the same way as only the central tile is needed.
160     if (getGameMap()->isInEditorMode())
161         updatePortalPosition();
162     else
163     {
164         if(mPortalObject == nullptr)
165         {
166             // We check if the portal already exists (that can happen if it has
167             // been restored after restoring a saved game)
168             if(mBuildingObjects.empty())
169                 updatePortalPosition();
170             else
171             {
172                 for(auto& p : mBuildingObjects)
173                 {
174                     if(p.second == nullptr)
175                         continue;
176 
177                     // We take the first BuildingObject. Note that we cannot use
178                     // the central tile because after saving a game, the central tile may
179                     // not be the same if some tiles have been destroyed
180                     mPortalObject = p.second;
181                     break;
182                 }
183             }
184         }
185     }
186 }
187 
updatePortalPosition()188 void RoomPortal::updatePortalPosition()
189 {
190     // Delete all previous rooms meshes and recreate a central one.
191     removeAllBuildingObjects();
192     mPortalObject = nullptr;
193 
194     Tile* centralTile = getCentralTile();
195     if (centralTile == nullptr)
196         return;
197 
198     mPortalObject = new PersistentObject(getGameMap(), *this, "PortalObject",
199         centralTile, 0.0, false, 1.0f, "Idle", true);
200     addBuildingObject(centralTile, mPortalObject);
201 }
202 
destroyMeshLocal()203 void RoomPortal::destroyMeshLocal()
204 {
205     Room::destroyMeshLocal();
206     mPortalObject = nullptr;
207 }
208 
doUpkeep()209 void RoomPortal::doUpkeep()
210 {
211     // Call the super class Room::doUpkeep() function to do any generic upkeep common to all rooms.
212     Room::doUpkeep();
213 
214     if(mSpawnCreatureCountdown > 0)
215     {
216         --mSpawnCreatureCountdown;
217         return;
218     }
219     mSpawnCreatureCountdown = Random::Uint(ConfigManager::getSingleton().getRoomConfigUInt32("PortalCooldownSpawnMin"),
220         ConfigManager::getSingleton().getRoomConfigUInt32("PortalCooldownSpawnMax"));
221 
222     if (mCoveredTiles.empty())
223         return;
224 
225     // Rogue seat cannot spawn creatures through normal portal (they won't spawn
226     // anyway since there is no spawning list for rogue seat but no need to compute
227     // everything in this case)
228     if(getSeat()->isRogueSeat())
229         return;
230 
231     if(getSeat()->getPlayer() == nullptr)
232         return;
233 
234     if(getSeat()->getPlayer()->getHasLost())
235         return;
236 
237     // Randomly choose to spawn a creature.
238     const double maxCreatures = getSeat()->getNumCreaturesFightersMax();
239     // Count how many creatures are controlled by this seat
240     double numCreatures = getSeat()->getNumCreaturesFighters();
241     if(numCreatures >= maxCreatures)
242         return;
243 
244     spawnCreature();
245 }
246 
spawnCreature()247 void RoomPortal::spawnCreature()
248 {
249     // We check if a creature can spawn
250     const CreatureDefinition* classToSpawn = getSeat()->getNextFighterClassToSpawn(*getGameMap(), ConfigManager::getSingleton());
251     if (classToSpawn == nullptr)
252         return;
253 
254     Tile* centralTile = getCentralTile();
255     if (centralTile == nullptr)
256         return;
257 
258     if (mPortalObject != nullptr)
259         mPortalObject->setAnimationState("Triggered", false);
260 
261     Ogre::Real xPos = static_cast<Ogre::Real>(centralTile->getX());
262     Ogre::Real yPos = static_cast<Ogre::Real>(centralTile->getY());
263 
264     // Create a new creature and copy over the class-based creature parameters.
265     Creature* newCreature = new Creature(getGameMap(), classToSpawn, getSeat(), Ogre::Vector3(xPos, yPos, 0.0f));
266 
267     OD_LOG_INF("RoomPortal name=" + getName()
268         + " spawns a creature class=" + classToSpawn->getClassName()
269         + ", name=" + newCreature->getName() + ", seatId=" + Helper::toString(getSeat()->getId()));
270 
271     newCreature->addToGameMap();
272     newCreature->createMesh();
273     newCreature->setPosition(newCreature->getPosition());
274 }
275 
setupRoom(const std::string & name,Seat * seat,const std::vector<Tile * > & tiles)276 void RoomPortal::setupRoom(const std::string& name, Seat* seat, const std::vector<Tile*>& tiles)
277 {
278     Room::setupRoom(name, seat, tiles);
279     mClaimedValue = static_cast<double>(numCoveredTiles()) * CLAIMED_VALUE_PER_TILE;
280     // By default, we allow some more creatures per portal
281     mNbCreatureMaxIncrease = 5;
282 }
283 
exportToStream(std::ostream & os) const284 void RoomPortal::exportToStream(std::ostream& os) const
285 {
286     Room::exportToStream(os);
287 
288     os << mClaimedValue << "\t" << mNbCreatureMaxIncrease << "\n";
289 }
290 
importFromStream(std::istream & is)291 bool RoomPortal::importFromStream(std::istream& is)
292 {
293     if(!Room::importFromStream(is))
294         return false;
295 
296     if(!(is >> mClaimedValue))
297         return false;
298     if(!(is >> mNbCreatureMaxIncrease))
299         return false;
300 
301     return true;
302 }
303 
restoreInitialEntityState()304 void RoomPortal::restoreInitialEntityState()
305 {
306     // We need to use seats with vision before calling Room::restoreInitialEntityState
307     // because it will empty the list
308     if(mPortalObject == nullptr)
309     {
310         OD_LOG_ERR("roomPortal=" + getName());
311         return;
312     }
313 
314     Tile* tilePortalObject = mPortalObject->getPositionTile();
315     if(tilePortalObject == nullptr)
316     {
317         OD_LOG_ERR("roomPortal=" + getName() + ", mPortalObject=" + mPortalObject->getName());
318         return;
319     }
320     TileData* tileData = mTileData[tilePortalObject];
321     if(tileData == nullptr)
322     {
323         OD_LOG_ERR("roomPortal=" + getName() + ", tile=" + Tile::displayAsString(tilePortalObject));
324         return;
325     }
326 
327     // We want all players to know where the portal is
328     mPortalObject->notifySeatsWithVision(getGameMap()->getSeats());
329 
330     // If there are no covered tile, the temple object is not working
331     if(numCoveredTiles() == 0)
332         mPortalObject->notifyRemoveAsked();
333 
334     // We want all players to know where portals are. Since portal tiles
335     // cannot be destroyed, we can safely notify all the tiles at game
336     // start since it won't change between saved games
337     std::vector<Tile*> tilesToNotify = getCoveredTiles();
338     for(Seat* seat : getGameMap()->getSeats())
339     {
340         if(seat->getPlayer() == nullptr)
341             continue;
342         if(!seat->getPlayer()->getIsHuman())
343             continue;
344 
345         uint32_t nbTiles = tilesToNotify.size();
346         ServerNotification *serverNotification = new ServerNotification(
347             ServerNotificationType::refreshTiles, seat->getPlayer());
348         serverNotification->mPacket << nbTiles;
349         for(Tile* tile : tilesToNotify)
350         {
351             getGameMap()->tileToPacket(serverNotification->mPacket, tile);
352             seat->updateTileStateForSeat(tile, true);
353             tile->exportToPacketForUpdate(serverNotification->mPacket, seat, true);
354         }
355         ODServer::getSingleton().queueServerNotification(serverNotification);
356     }
357 
358     Room::restoreInitialEntityState();
359 }
360