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/RoomCasino.h"
19 
20 #include "creatureaction/CreatureActionFightFriendly.h"
21 #include "entities/BuildingObject.h"
22 #include "entities/Creature.h"
23 #include "entities/CreatureDefinition.h"
24 #include "entities/GameEntityType.h"
25 #include "entities/SkillEntity.h"
26 #include "entities/Tile.h"
27 #include "game/Player.h"
28 #include "game/Skill.h"
29 #include "game/Seat.h"
30 #include "gamemap/GameMap.h"
31 #include "gamemap/Pathfinding.h"
32 #include "rooms/RoomManager.h"
33 #include "utils/ConfigManager.h"
34 #include "utils/Helper.h"
35 #include "utils/LogManager.h"
36 #include "utils/MakeUnique.h"
37 #include "utils/Random.h"
38 
39 const std::string RoomCasinoName = "Casino";
40 const std::string RoomCasinoNameDisplay = "Casino room";
41 const RoomType RoomCasino::mRoomType = RoomType::casino;
42 
43 namespace
44 {
45 class RoomCasinoFactory : public RoomFactory
46 {
getRoomType() const47     RoomType getRoomType() const override
48     { return RoomCasino::mRoomType; }
49 
getName() const50     const std::string& getName() const override
51     { return RoomCasinoName; }
52 
getNameReadable() const53     const std::string& getNameReadable() const override
54     { return RoomCasinoNameDisplay; }
55 
getCostPerTile() const56     int getCostPerTile() const override
57     { return ConfigManager::getSingleton().getRoomConfigInt32("CasinoCostPerTile"); }
58 
checkBuildRoom(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const59     void checkBuildRoom(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
60     {
61         checkBuildRoomDefault(gameMap, RoomCasino::mRoomType, inputManager, inputCommand);
62     }
63 
buildRoom(GameMap * gameMap,Player * player,ODPacket & packet) const64     bool buildRoom(GameMap* gameMap, Player* player, ODPacket& packet) const override
65     {
66         std::vector<Tile*> tiles;
67         if(!getRoomTilesDefault(tiles, gameMap, player, packet))
68             return false;
69 
70         int32_t pricePerTarget = RoomManager::costPerTile(RoomCasino::mRoomType);
71         int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
72         if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
73             return false;
74 
75         RoomCasino* room = new RoomCasino(gameMap);
76         return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
77     }
78 
checkBuildRoomEditor(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const79     void checkBuildRoomEditor(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
80     {
81         checkBuildRoomDefaultEditor(gameMap, RoomCasino::mRoomType, inputManager, inputCommand);
82     }
83 
buildRoomEditor(GameMap * gameMap,ODPacket & packet) const84     bool buildRoomEditor(GameMap* gameMap, ODPacket& packet) const override
85     {
86         RoomCasino* room = new RoomCasino(gameMap);
87         return buildRoomDefaultEditor(gameMap, room, packet);
88     }
89 
getRoomFromStream(GameMap * gameMap,std::istream & is) const90     Room* getRoomFromStream(GameMap* gameMap, std::istream& is) const override
91     {
92         RoomCasino* room = new RoomCasino(gameMap);
93         if(!Room::importRoomFromStream(*room, is))
94         {
95             OD_LOG_ERR("Error while building a room from the stream");
96         }
97         return room;
98     }
99 
buildRoomOnTiles(GameMap * gameMap,Player * player,const std::vector<Tile * > & tiles) const100     bool buildRoomOnTiles(GameMap* gameMap, Player* player, const std::vector<Tile*>& tiles) const override
101     {
102         int32_t pricePerTarget = RoomManager::costPerTile(RoomCasino::mRoomType);
103         int32_t price = static_cast<int32_t>(tiles.size()) * pricePerTarget;
104         if(!gameMap->withdrawFromTreasuries(price, player->getSeat()))
105             return false;
106 
107         RoomCasino* room = new RoomCasino(gameMap);
108         return buildRoomDefault(gameMap, room, player->getSeat(), tiles);
109     }
110 };
111 
112 // Register the factory
113 static RoomRegister reg(new RoomCasinoFactory);
114 }
115 
116 static const Ogre::Real OFFSET_CREATURE = 0.3;
117 
RoomCasino(GameMap * gameMap)118 RoomCasino::RoomCasino(GameMap* gameMap) :
119     Room(gameMap)
120 {
121     setMeshName("Casino");
122 }
123 
notifyActiveSpotCreated(ActiveSpotPlace place,Tile * tile)124 BuildingObject* RoomCasino::notifyActiveSpotCreated(ActiveSpotPlace place, Tile* tile)
125 {
126     switch(place)
127     {
128         case ActiveSpotPlace::activeSpotCenter:
129         {
130             Ogre::Real x = static_cast<Ogre::Real>(tile->getX());
131             Ogre::Real y = static_cast<Ogre::Real>(tile->getY());
132             Ogre::Real z = 0;
133             mCreaturesSpots.emplace(std::make_pair(tile, RoomCasinoGame()));
134             if(Random::Uint(0,9) < 5)
135                 return new BuildingObject(getGameMap(), *this, "CasinoPokerTable", tile, x, y, z, 0.0, false);
136             else
137                 return new BuildingObject(getGameMap(), *this, "Roulette", tile, x, y, z, 0.0, false);
138         }
139         case ActiveSpotPlace::activeSpotLeft:
140         {
141             return new BuildingObject(getGameMap(), *this, "CasinoWallBeer", *tile, 90.0, false);
142         }
143         case ActiveSpotPlace::activeSpotRight:
144         {
145             return new BuildingObject(getGameMap(), *this, "CasinoWallBeer", *tile, 270.0, false);
146         }
147         case ActiveSpotPlace::activeSpotTop:
148         {
149             return new BuildingObject(getGameMap(), *this, "CasinoWallBeer", *tile, 0.0, false);
150         }
151         case ActiveSpotPlace::activeSpotBottom:
152         {
153             return new BuildingObject(getGameMap(), *this, "CasinoWallBeer", *tile, 180.0, false);
154         }
155         default:
156             break;
157     }
158     return nullptr;
159 }
160 
absorbRoom(Room * room)161 void RoomCasino::absorbRoom(Room* room)
162 {
163     if(room->getType() != getType())
164     {
165         OD_LOG_ERR("Trying to merge incompatible rooms: " + getName() + ", type=" + RoomManager::getRoomNameFromRoomType(getType()) + ", with " + room->getName() + ", type=" + RoomManager::getRoomNameFromRoomType(room->getType()));
166         return;
167     }
168 
169     Room::absorbRoom(room);
170 }
171 
notifyActiveSpotRemoved(ActiveSpotPlace place,Tile * tile)172 void RoomCasino::notifyActiveSpotRemoved(ActiveSpotPlace place, Tile* tile)
173 {
174     Room::notifyActiveSpotRemoved(place, tile);
175 
176     if(place != ActiveSpotPlace::activeSpotCenter)
177         return;
178 
179     auto it = mCreaturesSpots.find(tile);
180     if(it == mCreaturesSpots.end())
181     {
182         OD_LOG_ERR("room=" + getName() + ", tile=" + Tile::displayAsString(tile));
183         return;
184     }
185 
186     RoomCasinoGame& game = it->second;
187     Creature* creature;
188     creature = game.mCreature1.mCreature;
189     if(creature != nullptr)
190         creature->clearActionQueue();
191 
192     creature = game.mCreature2.mCreature;
193     if(creature != nullptr)
194         creature->clearActionQueue();
195 
196     // clearActionQueue should have released mCreaturesSpots
197     OD_ASSERT_TRUE_MSG(game.mCreature1.mCreature == nullptr, "room=" + getName() + ", tile=" + Tile::displayAsString(tile) + ", creature=" + game.mCreature1.mCreature->getName());
198     OD_ASSERT_TRUE_MSG(game.mCreature2.mCreature == nullptr, "room=" + getName() + ", tile=" + Tile::displayAsString(tile) + ", creature=" + game.mCreature2.mCreature->getName());
199 }
200 
hasOpenCreatureSpot(Creature * creature)201 bool RoomCasino::hasOpenCreatureSpot(Creature* creature)
202 {
203     // Broke creatures cannot play
204     if(creature->getGoldCarried() <= 0)
205         return false;
206 
207     for(std::pair<Tile* const,RoomCasinoGame>& p : mCreaturesSpots)
208     {
209         if(p.second.mCreature1.mCreature == nullptr)
210             return true;
211 
212         if(p.second.mCreature2.mCreature == nullptr)
213             return true;
214     }
215 
216     return false;
217 }
218 
addCreatureUsingRoom(Creature * creature)219 bool RoomCasino::addCreatureUsingRoom(Creature* creature)
220 {
221     const CreatureRoomAffinity& creatureRoomAffinity = creature->getDefinition()->getRoomAffinity(getType());
222     OD_ASSERT_TRUE_MSG(creatureRoomAffinity.getRoomType() == getType(), "name=" + getName() + ", creature=" + creature->getName()
223         + ", creatureRoomAffinityType=" + Helper::toString(static_cast<int>(creatureRoomAffinity.getRoomType())));
224 
225     OD_ASSERT_TRUE_MSG(creature->getGoldCarried() > 0, "name=" + getName() + ", creature=" + creature->getName());
226 
227     if(!Room::addCreatureUsingRoom(creature))
228         return false;
229 
230     // We try to have 2 creatures on the same spot if possible
231     RoomCasinoGameCreatureInfo* firstAvailableSpace = nullptr;
232     for(std::pair<Tile* const,RoomCasinoGame>& p : mCreaturesSpots)
233     {
234         // We don't care if already 2 creatures
235         if((p.second.mCreature1.mCreature != nullptr) && (p.second.mCreature2.mCreature != nullptr))
236             continue;
237 
238         // If spot 2 is used, we can use spot 1
239         if(p.second.mCreature2.mCreature != nullptr)
240         {
241             p.second.mCreature1.mCreature = creature;
242             p.second.mCreature1.mIsReady = false;
243             return true;
244         }
245         // If spot 1 is used, we can use spot 2
246         if(p.second.mCreature1.mCreature != nullptr)
247         {
248             p.second.mCreature2.mCreature = creature;
249             p.second.mCreature2.mIsReady = false;
250             return true;
251         }
252 
253         // No spot used. If we have not already found an available spot, we use spot 1
254         if(firstAvailableSpace == nullptr)
255             firstAvailableSpace = &p.second.mCreature1;
256     }
257 
258     // We could not found any creature waiting for another one. We use the first available
259     // we found if any
260     if(firstAvailableSpace != nullptr)
261     {
262         firstAvailableSpace->mCreature = creature;
263         firstAvailableSpace->mIsReady = false;
264         return true;
265     }
266 
267     return false;
268 }
269 
removeCreatureUsingRoom(Creature * creature)270 void RoomCasino::removeCreatureUsingRoom(Creature* creature)
271 {
272     Room::removeCreatureUsingRoom(creature);
273 
274     // If the creature leaving the casino left another one playing alone, we
275     // should search for another creature alone and try to make them match
276     RoomCasinoGameCreatureInfo* opponentAlone = nullptr;
277     bool isCreatureFound = false;
278     for(std::pair<Tile* const,RoomCasinoGame>& p : mCreaturesSpots)
279     {
280         if(p.second.mCreature1.mCreature == creature)
281         {
282             isCreatureFound = true;
283             p.second.mCreature1.mCreature = nullptr;
284             if(p.second.mCreature2.mCreature != nullptr)
285                 opponentAlone = &p.second.mCreature2;
286 
287             break;
288         }
289         if(p.second.mCreature2.mCreature == creature)
290         {
291             isCreatureFound = true;
292             p.second.mCreature2.mCreature = nullptr;
293             if(p.second.mCreature1.mCreature != nullptr)
294                 opponentAlone = &p.second.mCreature1;
295 
296             break;
297         }
298     }
299 
300     if(!isCreatureFound)
301     {
302         OD_LOG_ERR("room=" + getName() + ", creature=" + creature->getName());
303         return;
304     }
305 
306     // If no opponent alone, no need to search
307     if(opponentAlone == nullptr)
308         return;
309 
310     for(std::pair<Tile* const,RoomCasinoGame>& p : mCreaturesSpots)
311     {
312         if((&p.second.mCreature1 == opponentAlone) || (&p.second.mCreature2 == opponentAlone))
313             continue;
314 
315         // We don't care if already 2 creatures
316         if((p.second.mCreature1.mCreature != nullptr) && (p.second.mCreature2.mCreature != nullptr))
317             continue;
318 
319         // We don't care if no creatures
320         if((p.second.mCreature1.mCreature == nullptr) && (p.second.mCreature2.mCreature == nullptr))
321             continue;
322 
323         if(p.second.mCreature1.mCreature == nullptr)
324         {
325             p.second.mCreature1.mCreature = opponentAlone->mCreature;
326             opponentAlone->mCreature = nullptr;
327             p.second.mCreature1.mIsReady = false;
328             break;
329         }
330 
331         if(p.second.mCreature2.mCreature == nullptr)
332         {
333             p.second.mCreature2.mCreature = opponentAlone->mCreature;
334             opponentAlone->mCreature = nullptr;
335             p.second.mCreature2.mIsReady = false;
336             break;
337         }
338     }
339 }
340 
doUpkeep()341 void RoomCasino::doUpkeep()
342 {
343     Room::doUpkeep();
344 
345     if (mCoveredTiles.empty())
346         return;
347 
348     for(std::pair<Tile* const,RoomCasinoGame>& p : mCreaturesSpots)
349     {
350         if(p.second.mCooldown > 0)
351         {
352             --p.second.mCooldown;
353             continue;
354         }
355 
356         if(p.second.mCreature1.mCreature == nullptr)
357             continue;
358         if(!p.second.mCreature1.mIsReady)
359             continue;
360         if(p.second.mCreature2.mCreature == nullptr)
361             continue;
362         if(!p.second.mCreature2.mIsReady)
363             continue;
364 
365         Tile* tileSpot = p.first;
366         BuildingObject* ro = getBuildingObjectFromTile(tileSpot);
367         if(ro == nullptr)
368         {
369             OD_LOG_ERR("unexpected null building object");
370             return;
371         }
372 
373         ro->setAnimationState("Triggered", false);
374 
375         // TODO: we could use the wall active spots to change feePercent/bets
376 
377         // We set anim for both creatures
378         uint32_t cooldown = Random::Uint(ConfigManager::getSingleton().getRoomConfigUInt32("CasinoCooldownWorkMin"),
379             ConfigManager::getSingleton().getRoomConfigUInt32("CasinoCooldownWorkMax"));
380         double feePercent = std::min(ConfigManager::getSingleton().getRoomConfigDouble("CasinoFee"), 1.0);
381         double wakefullness = ConfigManager::getSingleton().getRoomConfigDouble("CasinoWakefulnessPerWork");
382         int32_t creatureBet = ConfigManager::getSingleton().getRoomConfigInt32("CasinoBet");
383         creatureBet = std::min(creatureBet, p.second.mCreature1.mCreature->getGoldCarried());
384         creatureBet = std::min(creatureBet, p.second.mCreature2.mCreature->getGoldCarried());
385         int32_t totalBet = 0;
386         Creature* creature;
387 
388         creature = p.second.mCreature1.mCreature;
389         totalBet += creatureBet;
390         creature->addGoldCarried(-creatureBet);
391         creature->jobDone(wakefullness);
392         creature->setJobCooldown(cooldown);
393         const CreatureRoomAffinity& creature1RoomAffinity = creature->getDefinition()->getRoomAffinity(getType());
394 
395         creature = p.second.mCreature2.mCreature;
396         totalBet += creatureBet;
397         creature->addGoldCarried(-creatureBet);
398         creature->jobDone(wakefullness);
399         creature->setJobCooldown(cooldown);
400         const CreatureRoomAffinity& creature2RoomAffinity = creature->getDefinition()->getRoomAffinity(getType());
401 
402         p.second.mCooldown = cooldown;
403 
404         // We take our fee
405         int32_t totalFee = static_cast<int32_t>(static_cast<double>(totalBet) * feePercent);
406         getGameMap()->addGoldToSeat(totalFee, getSeat()->getId());
407         totalBet -= totalFee;
408 
409         // We give the total amount to the winning creature
410         double totalWinPercent = creature1RoomAffinity.getEfficiency()
411                 + creature2RoomAffinity.getEfficiency();
412         if(Random::Double(0, totalWinPercent) <= creature1RoomAffinity.getEfficiency())
413         {
414             setCreatureWinning(*p.second.mCreature1.mCreature, ro->getPosition());
415             setCreatureLoosing(*p.second.mCreature2.mCreature, ro->getPosition());
416             p.second.mCreature1.mCreature->addGoldCarried(totalBet);
417         }
418         else
419         {
420             setCreatureWinning(*p.second.mCreature2.mCreature, ro->getPosition());
421             setCreatureLoosing(*p.second.mCreature1.mCreature, ro->getPosition());
422             p.second.mCreature2.mCreature->addGoldCarried(totalBet);
423         }
424     }
425 }
426 
useRoom(Creature & creature,bool forced)427 bool RoomCasino::useRoom(Creature& creature, bool forced)
428 {
429     bool isGamblePossible = false;
430     Tile* tileSpot = nullptr;
431     RoomCasinoGameCreatureInfo* creatureInfo = nullptr;
432     RoomCasinoGameCreatureInfo* opponentInfo = nullptr;
433     Ogre::Real creaturePositionOffset = 0;
434     for(std::pair<Tile* const,RoomCasinoGame>& p : mCreaturesSpots)
435     {
436         if(p.second.mCreature1.mCreature == &creature)
437         {
438             tileSpot = p.first;
439             creatureInfo = &p.second.mCreature1;
440             opponentInfo = &p.second.mCreature2;
441             creaturePositionOffset = OFFSET_CREATURE;
442 
443             if(p.second.mCreature1.mIsReady && (p.second.mCreature2.mCreature != nullptr))
444                 isGamblePossible = true;
445 
446             break;
447         }
448         if(p.second.mCreature2.mCreature == &creature)
449         {
450             tileSpot = p.first;
451             creatureInfo = &p.second.mCreature2;
452             opponentInfo = &p.second.mCreature1;
453             creaturePositionOffset = -OFFSET_CREATURE;
454 
455             if(p.second.mCreature2.mIsReady && (p.second.mCreature1.mCreature != nullptr))
456                 isGamblePossible = true;
457 
458             break;
459         }
460     }
461 
462     if(tileSpot == nullptr)
463     {
464         OD_LOG_ERR("room=" + getName() + ", creature=" + creature.getName());
465         return false;
466     }
467 
468     if((creatureInfo == nullptr) || (opponentInfo == nullptr))
469     {
470         OD_LOG_ERR("room=" + getName() + ", creature=" + creature.getName());
471         return false;
472     }
473 
474     // If the creature has no gold, it should stop gambling
475     if(creature.getGoldCarried() <= 0)
476     {
477         // We save the opponent here because it could be released by popAction
478         // if there is a gambler waiting
479         Creature* opponent = opponentInfo->mCreature;
480         creature.popAction();
481         // We randomly engage the creature we are playing with if any
482         if((opponent != nullptr) && (Random::Uint(0,100) <= 50))
483         {
484             // We fight for KO
485             // We notify the player that his own creatures are fighting
486             creature.pushAction(Utils::make_unique<CreatureActionFightFriendly>(creature, opponent, true, getCoveredTiles(), true));
487             opponent->pushAction(Utils::make_unique<CreatureActionFightFriendly>(*opponent, &creature, true, getCoveredTiles(), true));
488         }
489         return true;
490     }
491 
492     // If the creature can play (if it is ready and has an opponent), it should
493     // play (or at least wait for the opponent)
494     if(isGamblePossible)
495         return false;
496 
497     Tile* tileCreature = creature.getPositionTile();
498     if(tileCreature == nullptr)
499     {
500         OD_LOG_ERR("room=" + getName() + ", creature=" + creature.getName() + ", pos=" + Helper::toString(creature.getPosition()));
501         return false;
502     }
503 
504     Ogre::Real wantedX = static_cast<Ogre::Real>(tileSpot->getX());
505     Ogre::Real wantedY = static_cast<Ogre::Real>(tileSpot->getY());
506     wantedY += creaturePositionOffset;
507 
508     // We consider that the creature is in the good place if it near from where we want it to go
509     if(Pathfinding::squaredDistance(creature.getPosition().x, wantedX, creature.getPosition().y, wantedY) > 0.4)
510     {
511         // We go there
512         std::list<Tile*> pathToSpot = getGameMap()->path(&creature, tileSpot);
513         std::vector<Ogre::Vector3> path;
514         Creature::tileToVector3(pathToSpot, path, true, 0.0);
515         // We add the last step to take account of the offset
516         Ogre::Vector3 dest(wantedX, wantedY, 0.0);
517         path.push_back(dest);
518         creature.setWalkPath(EntityAnimation::walk_anim, EntityAnimation::idle_anim, true, true, path);
519         return false;
520     }
521 
522     // This creature is ready. If there is no opponent, we pick a random tile and make it wander
523     if(opponentInfo->mCreature == nullptr)
524     {
525         std::vector<Tile*> tiles = getCoveredTiles();
526         if(tiles.empty())
527         {
528             OD_LOG_ERR("room=" + getName() + ", creature=" + creature.getName());
529             return false;
530         }
531 
532         uint32_t index = Random::Uint(0, tiles.size() - 1);
533         Tile* tile = tiles[index];
534         creature.setDestination(tile);
535         creatureInfo->mIsReady = false;
536         return false;
537     }
538 
539     // We are ready. We wait
540     creatureInfo->mIsReady = true;
541     return false;
542 }
543 
setCreatureWinning(Creature & creature,const Ogre::Vector3 & gamePosition)544 void RoomCasino::setCreatureWinning(Creature& creature, const Ogre::Vector3& gamePosition)
545 {
546     Ogre::Vector3 walkDirection(gamePosition.x - creature.getPosition().x, gamePosition.y - creature.getPosition().y, static_cast<Ogre::Real>(0));
547     walkDirection.normalise();
548     creature.setAnimationState(EntityAnimation::attack_anim, false, walkDirection);
549 }
550 
setCreatureLoosing(Creature & creature,const Ogre::Vector3 & gamePosition)551 void RoomCasino::setCreatureLoosing(Creature& creature, const Ogre::Vector3& gamePosition)
552 {
553     Ogre::Vector3 walkDirection(gamePosition.x - creature.getPosition().x, gamePosition.y - creature.getPosition().y, static_cast<Ogre::Real>(0));
554     walkDirection.normalise();
555     creature.setAnimationState(EntityAnimation::idle_anim, false, walkDirection);
556 }
557