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