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 "spells/SpellCreatureExplosion.h"
19 
20 #include "creatureeffect/CreatureEffectExplosion.h"
21 #include "entities/Creature.h"
22 #include "entities/GameEntityType.h"
23 #include "entities/Tile.h"
24 #include "game/Player.h"
25 #include "game/Seat.h"
26 #include "gamemap/GameMap.h"
27 #include "modes/InputCommand.h"
28 #include "modes/InputManager.h"
29 #include "network/ODClient.h"
30 #include "spells/SpellType.h"
31 #include "spells/SpellManager.h"
32 #include "utils/ConfigManager.h"
33 #include "utils/Helper.h"
34 #include "utils/LogManager.h"
35 
36 const std::string SpellCreatureExplosionName = "creatureExplosion";
37 const std::string SpellCreatureExplosionNameDisplay = "Creature explosion";
38 const std::string SpellCreatureExplosionCooldownKey = "CreatureExplosionCooldown";
39 const SpellType SpellCreatureExplosion::mSpellType = SpellType::creatureExplosion;
40 
41 namespace
42 {
43 class SpellCreatureExplosionFactory : public SpellFactory
44 {
getSpellType() const45     SpellType getSpellType() const override
46     { return SpellCreatureExplosion::mSpellType; }
47 
getName() const48     const std::string& getName() const override
49     { return SpellCreatureExplosionName; }
50 
getCooldownKey() const51     const std::string& getCooldownKey() const override
52     { return SpellCreatureExplosionCooldownKey; }
53 
getNameReadable() const54     const std::string& getNameReadable() const override
55     { return SpellCreatureExplosionNameDisplay; }
56 
checkSpellCast(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand) const57     virtual void checkSpellCast(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand) const override
58     { SpellCreatureExplosion::checkSpellCast(gameMap, inputManager, inputCommand); }
59 
castSpell(GameMap * gameMap,Player * player,ODPacket & packet) const60     virtual bool castSpell(GameMap* gameMap, Player* player, ODPacket& packet) const override
61     { return SpellCreatureExplosion::castSpell(gameMap, player, packet); }
62 
getSpellFromStream(GameMap * gameMap,std::istream & is) const63     Spell* getSpellFromStream(GameMap* gameMap, std::istream &is) const override
64     { return SpellCreatureExplosion::getSpellFromStream(gameMap, is); }
65 
getSpellFromPacket(GameMap * gameMap,ODPacket & is) const66     Spell* getSpellFromPacket(GameMap* gameMap, ODPacket &is) const override
67     { return SpellCreatureExplosion::getSpellFromPacket(gameMap, is); }
68 };
69 
70 // Register the factory
71 static SpellRegister reg(new SpellCreatureExplosionFactory);
72 }
73 
checkSpellCast(GameMap * gameMap,const InputManager & inputManager,InputCommand & inputCommand)74 void SpellCreatureExplosion::checkSpellCast(GameMap* gameMap, const InputManager& inputManager, InputCommand& inputCommand)
75 {
76     Player* player = gameMap->getLocalPlayer();
77     int32_t priceTotal = 0;
78     int32_t pricePerTarget = ConfigManager::getSingleton().getSpellConfigInt32("CreatureExplosionPrice");
79     int32_t playerMana = static_cast<int32_t>(player->getSeat()->getMana());
80     if(inputManager.mCommandState == InputCommandState::infoOnly)
81     {
82         if(playerMana < pricePerTarget)
83         {
84             std::string txt = formatCastSpell(SpellType::creatureExplosion, pricePerTarget);
85             inputCommand.displayText(Ogre::ColourValue::Red, txt);
86         }
87         else
88         {
89             std::string txt = formatCastSpell(SpellType::creatureExplosion, pricePerTarget);
90             inputCommand.displayText(Ogre::ColourValue::White, txt);
91         }
92         inputCommand.selectSquaredTiles(inputManager.mXPos, inputManager.mYPos, inputManager.mXPos,
93             inputManager.mYPos);
94         return;
95     }
96 
97     if(inputManager.mCommandState == InputCommandState::building)
98     {
99         inputCommand.selectSquaredTiles(inputManager.mXPos, inputManager.mYPos, inputManager.mLStartDragX,
100             inputManager.mLStartDragY);
101     }
102 
103     std::vector<GameEntity*> targets;
104     gameMap->playerSelects(targets, inputManager.mXPos, inputManager.mYPos, inputManager.mLStartDragX, inputManager.mLStartDragY,
105         SelectionTileAllowed::groundClaimedAllied, SelectionEntityWanted::creatureAliveEnemy, player);
106 
107     if(targets.empty())
108     {
109         std::string txt = formatCastSpell(SpellType::creatureExplosion, 0);
110         inputCommand.displayText(Ogre::ColourValue::White, txt);
111         return;
112     }
113 
114     std::random_shuffle(targets.begin(), targets.end());
115     std::vector<Creature*> creatures;
116     for(GameEntity* target : targets)
117     {
118         if(playerMana < pricePerTarget)
119             break;
120 
121         if(target->getObjectType() != GameEntityType::creature)
122         {
123             static bool logMsg = false;
124             if(!logMsg)
125             {
126                 logMsg = true;
127                 OD_LOG_ERR("Wrong target name=" + target->getName() + ", type=" + Helper::toString(static_cast<int32_t>(target->getObjectType())));
128             }
129             continue;
130         }
131 
132         Creature* creature = static_cast<Creature*>(target);
133         creatures.push_back(creature);
134 
135         priceTotal += pricePerTarget;
136         playerMana -= pricePerTarget;
137     }
138 
139     std::string txt = formatCastSpell(SpellType::creatureExplosion, priceTotal);
140     inputCommand.displayText(Ogre::ColourValue::White, txt);
141 
142     if(inputManager.mCommandState != InputCommandState::validated)
143         return;
144 
145     inputCommand.unselectAllTiles();
146 
147     ClientNotification *clientNotification = SpellManager::createSpellClientNotification(SpellType::creatureExplosion);
148     uint32_t nbCreatures = creatures.size();
149     clientNotification->mPacket << nbCreatures;
150     for(Creature* creature : creatures)
151         clientNotification->mPacket << creature->getName();
152 
153     ODClient::getSingleton().queueClientNotification(clientNotification);
154 }
155 
castSpell(GameMap * gameMap,Player * player,ODPacket & packet)156 bool SpellCreatureExplosion::castSpell(GameMap* gameMap, Player* player, ODPacket& packet)
157 {
158     uint32_t nbCreatures;
159     OD_ASSERT_TRUE(packet >> nbCreatures);
160     std::vector<Creature*> creatures;
161     while(nbCreatures > 0)
162     {
163         --nbCreatures;
164         std::string creatureName;
165         OD_ASSERT_TRUE(packet >> creatureName);
166 
167         // We check that the creatures are valid targets
168         Creature* creature = gameMap->getCreature(creatureName);
169         if(creature == nullptr)
170         {
171             OD_LOG_ERR("creatureName=" + creatureName);
172             continue;
173         }
174 
175         if(creature->getSeat()->isAlliedSeat(player->getSeat()))
176         {
177             OD_LOG_WRN("creatureName=" + creatureName);
178             continue;
179         }
180 
181         Tile* pos = creature->getPositionTile();
182         if(pos == nullptr)
183         {
184             OD_LOG_WRN("creatureName=" + creatureName);
185             continue;
186         }
187 
188         if(!creature->isAlive())
189         {
190             // This can happen if the creature was alive on client side but is not since we received the message
191             OD_LOG_WRN("creatureName=" + creatureName);
192             continue;
193         }
194 
195         // That can happen if the creature is not in perfect synchronization and is not on a claimed tile on the server gamemap
196         if(!pos->isClaimedForSeat(player->getSeat()))
197         {
198             OD_LOG_INF("WARNING : " + creatureName + ", tile=" + Tile::displayAsString(pos));
199             continue;
200         }
201 
202         creatures.push_back(creature);
203     }
204 
205     if(creatures.empty())
206         return false;
207 
208     int32_t pricePerTarget = ConfigManager::getSingleton().getSpellConfigInt32("CreatureExplosionPrice");
209     int32_t playerMana = static_cast<int32_t>(player->getSeat()->getMana());
210     uint32_t nbTargets = std::min(static_cast<uint32_t>(playerMana / pricePerTarget), static_cast<uint32_t>(creatures.size()));
211     int32_t priceTotal = nbTargets * pricePerTarget;
212 
213     if(creatures.size() > nbTargets)
214         creatures.resize(nbTargets);
215 
216     if(!player->getSeat()->takeMana(priceTotal))
217         return false;
218 
219     uint32_t duration = ConfigManager::getSingleton().getSpellConfigUInt32("CreatureExplosionDuration");
220     double value = ConfigManager::getSingleton().getSpellConfigDouble("CreatureExplosionValue");
221     for(Creature* creature : creatures)
222     {
223         CreatureEffectExplosion* effect = new CreatureEffectExplosion(duration, value, "SpellCreatureExplosion");
224         creature->addCreatureEffect(effect);
225     }
226 
227     return true;
228 }
229 
getSpellFromStream(GameMap * gameMap,std::istream & is)230 Spell* SpellCreatureExplosion::getSpellFromStream(GameMap* gameMap, std::istream &is)
231 {
232     OD_LOG_ERR("SpellCreatureExplosion cannot be read from stream");
233     return nullptr;
234 }
235 
getSpellFromPacket(GameMap * gameMap,ODPacket & is)236 Spell* SpellCreatureExplosion::getSpellFromPacket(GameMap* gameMap, ODPacket &is)
237 {
238     OD_LOG_ERR("SpellCreatureExplosion cannot be read from packet");
239     return nullptr;
240 }
241