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