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