1 /***************************************************************************
2  *   Free Heroes of Might and Magic II: https://github.com/ihhub/fheroes2  *
3  *   Copyright (C) 2021                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "monster_info.h"
22 #include "icn.h"
23 #include "m82.h"
24 #include "monster.h"
25 #include "race.h"
26 #include "speed.h"
27 #include "spell.h"
28 #include "tools.h"
29 #include "translations.h"
30 
31 #include <algorithm>
32 #include <cassert>
33 #include <map>
34 #include <set>
35 #include <sstream>
36 
37 namespace
38 {
39     std::vector<fheroes2::MonsterData> monsterData;
40 
populateMonsterData()41     void populateMonsterData()
42     {
43         const int monsterIcnIds[Monster::MONSTER_COUNT]
44             = { ICN::UNKNOWN,  ICN::PEASANT,  ICN::ARCHER,   ICN::ARCHER2,  ICN::PIKEMAN,  ICN::PIKEMAN2, ICN::SWORDSMN, ICN::SWORDSM2, ICN::CAVALRYR,
45                 ICN::CAVALRYB, ICN::PALADIN,  ICN::PALADIN2, ICN::GOBLIN,   ICN::ORC,      ICN::ORC2,     ICN::WOLF,     ICN::OGRE,     ICN::OGRE2,
46                 ICN::TROLL,    ICN::TROLL2,   ICN::CYCLOPS,  ICN::SPRITE,   ICN::DWARF,    ICN::DWARF2,   ICN::ELF,      ICN::ELF2,     ICN::DRUID,
47                 ICN::DRUID2,   ICN::UNICORN,  ICN::PHOENIX,  ICN::CENTAUR,  ICN::GARGOYLE, ICN::GRIFFIN,  ICN::MINOTAUR, ICN::MINOTAU2, ICN::HYDRA,
48                 ICN::DRAGGREE, ICN::DRAGRED,  ICN::DRAGBLAK, ICN::HALFLING, ICN::BOAR,     ICN::GOLEM,    ICN::GOLEM2,   ICN::ROC,      ICN::MAGE1,
49                 ICN::MAGE2,    ICN::TITANBLU, ICN::TITANBLA, ICN::SKELETON, ICN::ZOMBIE,   ICN::ZOMBIE2,  ICN::MUMMYW,   ICN::MUMMY2,   ICN::VAMPIRE,
50                 ICN::VAMPIRE2, ICN::LICH,     ICN::LICH2,    ICN::DRAGBONE, ICN::ROGUE,    ICN::NOMAD,    ICN::GHOST,    ICN::GENIE,    ICN::MEDUSA,
51                 ICN::EELEM,    ICN::AELEM,    ICN::FELEM,    ICN::WELEM,    ICN::UNKNOWN,  ICN::UNKNOWN,  ICN::UNKNOWN,  ICN::UNKNOWN,  ICN::UNKNOWN };
52 
53         const char * binFileName[Monster::MONSTER_COUNT]
54             = { "UNKNOWN",      "PEAS_FRM.BIN", "ARCHRFRM.BIN", "ARCHRFRM.BIN", "PIKMNFRM.BIN", "PIKMNFRM.BIN", "SWRDSFRM.BIN", "SWRDSFRM.BIN", "CVLRYFRM.BIN",
55                 "CVLR2FRM.BIN", "PALADFRM.BIN", "PALADFRM.BIN", "GOBLNFRM.BIN", "ORC__FRM.BIN", "ORC__FRM.BIN", "WOLF_FRM.BIN", "OGRE_FRM.BIN", "OGRE_FRM.BIN",
56                 "TROLLFRM.BIN", "TROLLFRM.BIN", "CYCLOFRM.BIN", "SPRITFRM.BIN", "DWARFFRM.BIN", "DWARFFRM.BIN", "ELF__FRM.BIN", "ELF__FRM.BIN", "DRUIDFRM.BIN",
57                 "DRUIDFRM.BIN", "UNICOFRM.BIN", "PHOENFRM.BIN", "CENTRFRM.BIN", "GARGLFRM.BIN", "GRIFFFRM.BIN", "MINOTFRM.BIN", "MINOTFRM.BIN", "HYDRAFRM.BIN",
58                 "DRAGGFRM.BIN", "DRAGRFRM.BIN", "DRAGBFRM.BIN", "HALFLFRM.BIN", "BOAR_FRM.BIN", "GOLEMFRM.BIN", "GOLEMFRM.BIN", "ROC__FRM.BIN", "MAGE1FRM.BIN",
59                 "MAGE1FRM.BIN", "TITANFRM.BIN", "TITA2FRM.BIN", "SKEL_FRM.BIN", "ZOMB_FRM.BIN", "ZOMB_FRM.BIN", "MUMMYFRM.BIN", "MUMMYFRM.BIN", "VAMPIFRM.BIN",
60                 "VAMPIFRM.BIN", "LICH_FRM.BIN", "LICH_FRM.BIN", "DRABNFRM.BIN", "ROGUEFRM.BIN", "NOMADFRM.BIN", "GHOSTFRM.BIN", "GENIEFRM.BIN", "MEDUSFRM.BIN",
61                 "FELEMFRM.BIN", "FELEMFRM.BIN", "FELEMFRM.BIN", "FELEMFRM.BIN", "UNKNOWN",      "UNKNOWN",      "UNKNOWN",      "UNKNOWN",      "UNKNOWN" };
62 
63         const fheroes2::MonsterSound monsterSounds[Monster::MONSTER_COUNT] = {
64             // melee attack | death | movement | wince |ranged attack
65             { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Unknown Monster
66             { M82::PSNTATTK, M82::PSNTKILL, M82::PSNTMOVE, M82::PSNTWNCE, M82::UNKNOWN }, // Peasant
67             { M82::ARCHATTK, M82::ARCHKILL, M82::ARCHMOVE, M82::ARCHWNCE, M82::ARCHSHOT }, // Archer
68             { M82::ARCHATTK, M82::ARCHKILL, M82::ARCHMOVE, M82::ARCHWNCE, M82::ARCHSHOT }, // Ranger
69             { M82::PIKEATTK, M82::PIKEKILL, M82::PIKEMOVE, M82::PIKEWNCE, M82::UNKNOWN }, // Pikeman
70             { M82::PIKEATTK, M82::PIKEKILL, M82::PIKEMOVE, M82::PIKEWNCE, M82::UNKNOWN }, // Veteran Pikeman
71             { M82::SWDMATTK, M82::SWDMKILL, M82::SWDMMOVE, M82::SWDMWNCE, M82::UNKNOWN }, // Swordsman
72             { M82::SWDMATTK, M82::SWDMKILL, M82::SWDMMOVE, M82::SWDMWNCE, M82::UNKNOWN }, // Master Swordsman
73             { M82::CAVLATTK, M82::CAVLKILL, M82::CAVLMOVE, M82::CAVLWNCE, M82::UNKNOWN }, // Cavalry
74             { M82::CAVLATTK, M82::CAVLKILL, M82::CAVLMOVE, M82::CAVLWNCE, M82::UNKNOWN }, // Champion
75             { M82::PLDNATTK, M82::PLDNKILL, M82::PLDNMOVE, M82::PLDNWNCE, M82::UNKNOWN }, // Paladin
76             { M82::PLDNATTK, M82::PLDNKILL, M82::PLDNMOVE, M82::PLDNWNCE, M82::UNKNOWN }, // Crusader
77             { M82::GBLNATTK, M82::GBLNKILL, M82::GBLNMOVE, M82::GBLNWNCE, M82::UNKNOWN }, // Goblin
78             { M82::ORC_ATTK, M82::ORC_KILL, M82::ORC_MOVE, M82::ORC_WNCE, M82::ORC_SHOT }, // Orc
79             { M82::ORC_ATTK, M82::ORC_KILL, M82::ORC_MOVE, M82::ORC_WNCE, M82::ORC_SHOT }, // Orc Chief
80             { M82::WOLFATTK, M82::WOLFKILL, M82::WOLFMOVE, M82::WOLFWNCE, M82::UNKNOWN }, // Wolf
81             { M82::OGREATTK, M82::OGREKILL, M82::OGREMOVE, M82::OGREWNCE, M82::UNKNOWN }, // Ogre
82             { M82::OGREATTK, M82::OGREKILL, M82::OGREMOVE, M82::OGREWNCE, M82::UNKNOWN }, // Ogre Lord
83             { M82::TRLLATTK, M82::TRLLKILL, M82::TRLLMOVE, M82::TRLLWNCE, M82::TRLLSHOT }, // Troll
84             { M82::TRLLATTK, M82::TRLLKILL, M82::TRLLMOVE, M82::TRLLWNCE, M82::TRLLSHOT }, // War Troll
85             { M82::CYCLATTK, M82::CYCLKILL, M82::CYCLMOVE, M82::CYCLWNCE, M82::UNKNOWN }, // Cyclops
86             { M82::SPRTATTK, M82::SPRTKILL, M82::SPRTMOVE, M82::SPRTWNCE, M82::UNKNOWN }, // Sprite
87             { M82::DWRFATTK, M82::DWRFKILL, M82::DWRFMOVE, M82::DWRFWNCE, M82::UNKNOWN }, // Dwarf
88             { M82::DWRFATTK, M82::DWRFKILL, M82::DWRFMOVE, M82::DWRFWNCE, M82::UNKNOWN }, // Battle Dwarf
89             { M82::ELF_ATTK, M82::ELF_KILL, M82::ELF_MOVE, M82::ELF_WNCE, M82::ELF_SHOT }, // Elf
90             { M82::ELF_ATTK, M82::ELF_KILL, M82::ELF_MOVE, M82::ELF_WNCE, M82::ELF_SHOT }, // Grand Elf
91             { M82::DRUIATTK, M82::DRUIKILL, M82::DRUIMOVE, M82::DRUIWNCE, M82::DRUISHOT }, // Druid
92             { M82::DRUIATTK, M82::DRUIKILL, M82::DRUIMOVE, M82::DRUIWNCE, M82::DRUISHOT }, // Greater Druid
93             { M82::UNICATTK, M82::UNICKILL, M82::UNICMOVE, M82::UNICWNCE, M82::UNKNOWN }, // Unicorn
94             { M82::PHOEATTK, M82::PHOEKILL, M82::PHOEMOVE, M82::PHOEWNCE, M82::UNKNOWN }, // Phoenix
95             { M82::CNTRATTK, M82::CNTRKILL, M82::CNTRMOVE, M82::CNTRWNCE, M82::CNTRSHOT }, // Centaur
96             { M82::GARGATTK, M82::GARGKILL, M82::GARGMOVE, M82::GARGWNCE, M82::UNKNOWN }, // Gargoyle
97             { M82::GRIFATTK, M82::GRIFKILL, M82::GRIFMOVE, M82::GRIFWNCE, M82::UNKNOWN }, // Griffin
98             { M82::MINOATTK, M82::MINOKILL, M82::MINOMOVE, M82::MINOWNCE, M82::UNKNOWN }, // Minotaur
99             { M82::MINOATTK, M82::MINOKILL, M82::MINOMOVE, M82::MINOWNCE, M82::UNKNOWN }, // Minotaur King
100             { M82::HYDRATTK, M82::HYDRKILL, M82::HYDRMOVE, M82::HYDRWNCE, M82::UNKNOWN }, // Hydra
101             { M82::DRGNATTK, M82::DRGNKILL, M82::DRGNMOVE, M82::DRGNWNCE, M82::UNKNOWN }, // Green Dragon
102             { M82::DRGNATTK, M82::DRGNKILL, M82::DRGNMOVE, M82::DRGNWNCE, M82::UNKNOWN }, // Red Dragon
103             { M82::DRGNATTK, M82::DRGNKILL, M82::DRGNMOVE, M82::DRGNWNCE, M82::UNKNOWN }, // Black Dragon
104             { M82::HALFATTK, M82::HALFKILL, M82::HALFMOVE, M82::HALFWNCE, M82::HALFSHOT }, // Halfling
105             { M82::BOARATTK, M82::BOARKILL, M82::BOARMOVE, M82::BOARWNCE, M82::UNKNOWN }, // Boar
106             { M82::GOLMATTK, M82::GOLMKILL, M82::GOLMMOVE, M82::GOLMWNCE, M82::UNKNOWN }, // Iron Golem
107             { M82::GOLMATTK, M82::GOLMKILL, M82::GOLMMOVE, M82::GOLMWNCE, M82::UNKNOWN }, // Steel Golem
108             { M82::ROC_ATTK, M82::ROC_KILL, M82::ROC_MOVE, M82::ROC_WNCE, M82::UNKNOWN }, // Roc
109             { M82::MAGEATTK, M82::MAGEKILL, M82::MAGEMOVE, M82::MAGEWNCE, M82::MAGESHOT }, // Mage
110             { M82::MAGEATTK, M82::MAGEKILL, M82::MAGEMOVE, M82::MAGEWNCE, M82::MAGESHOT }, // Archmage
111             { M82::TITNATTK, M82::TITNKILL, M82::TITNMOVE, M82::TITNWNCE, M82::UNKNOWN }, // Giant
112             { M82::TITNATTK, M82::TITNKILL, M82::TITNMOVE, M82::TITNWNCE, M82::TITNSHOT }, // Titan
113             { M82::SKELATTK, M82::SKELKILL, M82::SKELMOVE, M82::SKELWNCE, M82::UNKNOWN }, // Skeleton
114             { M82::ZOMBATTK, M82::ZOMBKILL, M82::ZOMBMOVE, M82::ZOMBWNCE, M82::UNKNOWN }, // Zombie
115             { M82::ZOMBATTK, M82::ZOMBKILL, M82::ZOMBMOVE, M82::ZOMBWNCE, M82::UNKNOWN }, // Mutant Zombie
116             { M82::MUMYATTK, M82::MUMYKILL, M82::MUMYMOVE, M82::MUMYWNCE, M82::UNKNOWN }, // Mummy
117             { M82::MUMYATTK, M82::MUMYKILL, M82::MUMYMOVE, M82::MUMYWNCE, M82::UNKNOWN }, // Royal Mummy
118             { M82::VAMPATTK, M82::VAMPKILL, M82::VAMPMOVE, M82::VAMPWNCE, M82::UNKNOWN }, // Vampire
119             { M82::VAMPATTK, M82::VAMPKILL, M82::VAMPMOVE, M82::VAMPWNCE, M82::UNKNOWN }, // Vampire Lord
120             { M82::LICHATTK, M82::LICHKILL, M82::LICHMOVE, M82::LICHWNCE, M82::LICHSHOT }, // Lich
121             { M82::LICHATTK, M82::LICHKILL, M82::LICHMOVE, M82::LICHWNCE, M82::LICHSHOT }, // Power Lich
122             { M82::BONEATTK, M82::BONEKILL, M82::BONEMOVE, M82::BONEWNCE, M82::UNKNOWN }, // Bone Dragon
123             { M82::ROGUATTK, M82::ROGUKILL, M82::ROGUMOVE, M82::ROGUWNCE, M82::UNKNOWN }, // Rogue
124             { M82::NMADATTK, M82::NMADKILL, M82::NMADMOVE, M82::NMADWNCE, M82::UNKNOWN }, // Nomad
125             { M82::GHSTATTK, M82::GHSTKILL, M82::GHSTMOVE, M82::GHSTWNCE, M82::UNKNOWN }, // Ghost
126             { M82::GENIATTK, M82::GENIKILL, M82::GENIMOVE, M82::GENIWNCE, M82::UNKNOWN }, // Genie
127             { M82::MEDSATTK, M82::MEDSKILL, M82::MEDSMOVE, M82::MEDSWNCE, M82::UNKNOWN }, // Medusa
128             { M82::EELMATTK, M82::EELMKILL, M82::EELMMOVE, M82::EELMWNCE, M82::UNKNOWN }, // Earth Elemental
129             { M82::AELMATTK, M82::AELMKILL, M82::AELMMOVE, M82::AELMWNCE, M82::UNKNOWN }, // Air Elemental
130             { M82::FELMATTK, M82::FELMKILL, M82::FELMMOVE, M82::FELMWNCE, M82::UNKNOWN }, // Fire Elemental
131             { M82::WELMATTK, M82::WELMKILL, M82::WELMMOVE, M82::WELMWNCE, M82::UNKNOWN }, // Water Elemental
132             { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster
133             { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 1
134             { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 2
135             { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 3
136             { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 4
137         };
138 
139         // Monster abilities and weaknesses will be added later.
140         const fheroes2::MonsterBattleStats monsterBattleStats[Monster::MONSTER_COUNT] = {
141             // attack | defence | damageMin | damageMax | hp | speed | shots | abilities | weaknesses
142             { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, {}, {} }, // Unknown Monster
143             { 1, 1, 1, 1, 1, Speed::VERYSLOW, 0, {}, {} }, // Peasant
144             { 5, 3, 2, 3, 10, Speed::VERYSLOW, 12, {}, {} }, // Archer
145             { 5, 3, 2, 3, 10, Speed::AVERAGE, 24, {}, {} }, // Ranger
146             { 5, 9, 3, 4, 15, Speed::AVERAGE, 0, {}, {} }, // Pikeman
147             { 5, 9, 3, 4, 20, Speed::FAST, 0, {}, {} }, // Veteran Pikeman
148             { 7, 9, 4, 6, 25, Speed::AVERAGE, 0, {}, {} }, // Swordsman
149             { 7, 9, 4, 6, 30, Speed::FAST, 0, {}, {} }, // Master Swordsman
150             { 10, 9, 5, 10, 30, Speed::VERYFAST, 0, {}, {} }, // Cavalry
151             { 10, 9, 5, 10, 40, Speed::ULTRAFAST, 0, {}, {} }, // Champion
152             { 11, 12, 10, 20, 50, Speed::FAST, 0, {}, {} }, // Paladin
153             { 11, 12, 10, 20, 65, Speed::VERYFAST, 0, {}, {} }, // Crusader
154             { 3, 1, 1, 2, 3, Speed::AVERAGE, 0, {}, {} }, // Goblin
155             { 3, 4, 2, 3, 10, Speed::VERYSLOW, 8, {}, {} }, // Orc
156             { 3, 4, 3, 4, 15, Speed::SLOW, 16, {}, {} }, // Orc Chief
157             { 6, 2, 3, 5, 20, Speed::VERYFAST, 0, {}, {} }, // Wolf
158             { 9, 5, 4, 6, 40, Speed::VERYSLOW, 0, {}, {} }, // Ogre
159             { 9, 5, 5, 7, 60, Speed::AVERAGE, 0, {}, {} }, // Ogre Lord
160             { 10, 5, 5, 7, 40, Speed::AVERAGE, 8, {}, {} }, // Troll
161             { 10, 5, 7, 9, 40, Speed::FAST, 16, {}, {} }, // War Troll
162             { 12, 9, 12, 24, 80, Speed::FAST, 0, {}, {} }, // Cyclops
163             { 4, 2, 1, 2, 2, Speed::AVERAGE, 0, {}, {} }, // Sprite
164             { 6, 5, 2, 4, 20, Speed::VERYSLOW, 0, {}, {} }, // Dwarf
165             { 6, 6, 2, 4, 20, Speed::AVERAGE, 0, {}, {} }, // Battle Dwarf
166             { 4, 3, 2, 3, 15, Speed::AVERAGE, 24, {}, {} }, // Elf
167             { 5, 5, 2, 3, 15, Speed::VERYFAST, 24, {}, {} }, // Grand Elf
168             { 7, 5, 5, 8, 25, Speed::FAST, 8, {}, {} }, // Druid
169             { 7, 7, 5, 8, 25, Speed::VERYFAST, 16, {}, {} }, // Greater Druid
170             { 10, 9, 7, 14, 40, Speed::FAST, 0, {}, {} }, // Unicorn
171             { 12, 10, 20, 40, 100, Speed::ULTRAFAST, 0, {}, {} }, // Phoenix
172             { 3, 1, 1, 2, 5, Speed::AVERAGE, 8, {}, {} }, // Centaur
173             { 4, 7, 2, 3, 15, Speed::VERYFAST, 0, {}, {} }, // Gargoyle
174             { 6, 6, 3, 5, 25, Speed::AVERAGE, 0, {}, {} }, // Griffin
175             { 9, 8, 5, 10, 35, Speed::AVERAGE, 0, {}, {} }, // Minotaur
176             { 9, 8, 5, 10, 45, Speed::VERYFAST, 0, {}, {} }, // Minotaur King
177             { 8, 9, 6, 12, 75, Speed::VERYSLOW, 0, {}, {} }, // Hydra
178             { 12, 12, 25, 50, 200, Speed::AVERAGE, 0, {}, {} }, // Green Dragon
179             { 13, 13, 25, 50, 250, Speed::FAST, 0, {}, {} }, // Red Dragon
180             { 14, 14, 25, 50, 300, Speed::VERYFAST, 0, {}, {} }, // Black Dragon
181             { 2, 1, 1, 3, 3, Speed::SLOW, 12, {}, {} }, // Halfling
182             { 5, 4, 2, 3, 15, Speed::VERYFAST, 0, {}, {} }, // Boar
183             { 5, 10, 4, 5, 30, Speed::VERYSLOW, 0, {}, {} }, // Iron Golem
184             { 7, 10, 4, 5, 35, Speed::SLOW, 0, {}, {} }, // Steel Golem
185             { 7, 7, 4, 8, 40, Speed::AVERAGE, 0, {}, {} }, // Roc
186             { 11, 7, 7, 9, 30, Speed::FAST, 12, {}, {} }, // Mage
187             { 12, 8, 7, 9, 35, Speed::VERYFAST, 24, {}, {} }, // Archmage
188             { 13, 10, 20, 30, 150, Speed::AVERAGE, 0, {}, {} }, // Giant
189             { 15, 15, 20, 30, 300, Speed::VERYFAST, 24, {}, {} }, // Titan
190             { 4, 3, 2, 3, 4, Speed::AVERAGE, 0, {}, {} }, // Skeleton
191             { 5, 2, 2, 3, 15, Speed::VERYSLOW, 0, {}, {} }, // Zombie
192             { 5, 2, 2, 3, 20, Speed::AVERAGE, 0, {}, {} }, // Mutant Zombie
193             { 6, 6, 3, 4, 25, Speed::AVERAGE, 0, {}, {} }, // Mummy
194             { 6, 6, 3, 4, 30, Speed::FAST, 0, {}, {} }, // Royal Mummy
195             { 8, 6, 5, 7, 30, Speed::AVERAGE, 0, {}, {} }, // Vampire
196             { 8, 6, 5, 7, 40, Speed::FAST, 0, {}, {} }, // Vampire Lord
197             { 7, 12, 8, 10, 25, Speed::FAST, 12, {}, {} }, // Lich
198             { 7, 13, 8, 10, 35, Speed::VERYFAST, 24, {}, {} }, // Power Lich
199             { 11, 9, 25, 45, 150, Speed::AVERAGE, 0, {}, {} }, // Bone Dragon
200             { 6, 1, 1, 2, 4, Speed::FAST, 0, {}, {} }, // Rogue
201             { 7, 6, 2, 5, 20, Speed::VERYFAST, 0, {}, {} }, // Nomad
202             { 8, 7, 4, 6, 20, Speed::FAST, 0, {}, {} }, // Ghost
203             { 10, 9, 20, 30, 50, Speed::VERYFAST, 0, {}, {} }, // Genie
204             { 8, 9, 6, 10, 35, Speed::AVERAGE, 0, {}, {} }, // Medusa
205             { 8, 8, 4, 5, 50, Speed::SLOW, 0, {}, {} }, // Earth Elemental
206             { 7, 7, 2, 8, 35, Speed::VERYFAST, 0, {}, {} }, // Air Elemental
207             { 8, 6, 4, 6, 40, Speed::FAST, 0, {}, {} }, // Fire Elemental
208             { 6, 8, 3, 7, 45, Speed::AVERAGE, 0, {}, {} }, // Water Elemental
209             { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, {}, {} }, // Random Monster
210             { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, {}, {} }, // Random Monster 1
211             { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, {}, {} }, // Random Monster 2
212             { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, {}, {} }, // Random Monster 3
213             { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, {}, {} }, // Random Monster 4
214         };
215 
216         const fheroes2::MonsterGeneralStats monsterGeneralStats[Monster::MONSTER_COUNT]
217             = { // name | plural name | growth | race | level | cost
218                 { gettext_noop( "Unknown Monster" ), gettext_noop( "Unknown Monsters" ), 0, Race::NONE, 0, { 0, 0, 0, 0, 0, 0, 0 } },
219                 { gettext_noop( "Peasant" ), gettext_noop( "Peasants" ), 12, Race::KNGT, 1, { 20, 0, 0, 0, 0, 0, 0 } },
220                 { gettext_noop( "Archer" ), gettext_noop( "Archers" ), 8, Race::KNGT, 2, { 150, 0, 0, 0, 0, 0, 0 } },
221                 { gettext_noop( "Ranger" ), gettext_noop( "Rangers" ), 8, Race::KNGT, 2, { 200, 0, 0, 0, 0, 0, 0 } },
222                 { gettext_noop( "Pikeman" ), gettext_noop( "Pikemen" ), 5, Race::KNGT, 3, { 200, 0, 0, 0, 0, 0, 0 } },
223                 { gettext_noop( "Veteran Pikeman" ), gettext_noop( "Veteran Pikemen" ), 5, Race::KNGT, 3, { 250, 0, 0, 0, 0, 0, 0 } },
224                 { gettext_noop( "Swordsman" ), gettext_noop( "Swordsmen" ), 4, Race::KNGT, 4, { 250, 0, 0, 0, 0, 0, 0 } },
225                 { gettext_noop( "Master Swordsman" ), gettext_noop( "Master Swordsmen" ), 4, Race::KNGT, 4, { 300, 0, 0, 0, 0, 0, 0 } },
226                 { gettext_noop( "Cavalry" ), gettext_noop( "Cavalries" ), 3, Race::KNGT, 5, { 300, 0, 0, 0, 0, 0, 0 } },
227                 { gettext_noop( "Champion" ), gettext_noop( "Champions" ), 3, Race::KNGT, 5, { 375, 0, 0, 0, 0, 0, 0 } },
228                 { gettext_noop( "Paladin" ), gettext_noop( "Paladins" ), 2, Race::KNGT, 6, { 600, 0, 0, 0, 0, 0, 0 } },
229                 { gettext_noop( "Crusader" ), gettext_noop( "Crusaders" ), 2, Race::KNGT, 6, { 1000, 0, 0, 0, 0, 0, 0 } },
230                 { gettext_noop( "Goblin" ), gettext_noop( "Goblins" ), 10, Race::BARB, 1, { 40, 0, 0, 0, 0, 0, 0 } },
231                 { gettext_noop( "Orc" ), gettext_noop( "Orcs" ), 8, Race::BARB, 2, { 140, 0, 0, 0, 0, 0, 0 } },
232                 { gettext_noop( "Orc Chief" ), gettext_noop( "Orc Chiefs" ), 8, Race::BARB, 2, { 175, 0, 0, 0, 0, 0, 0 } },
233                 { gettext_noop( "Wolf" ), gettext_noop( "Wolves" ), 5, Race::BARB, 3, { 200, 0, 0, 0, 0, 0, 0 } },
234                 { gettext_noop( "Ogre" ), gettext_noop( "Ogres" ), 4, Race::BARB, 4, { 300, 0, 0, 0, 0, 0, 0 } },
235                 { gettext_noop( "Ogre Lord" ), gettext_noop( "Ogre Lords" ), 4, Race::BARB, 4, { 500, 0, 0, 0, 0, 0, 0 } },
236                 { gettext_noop( "Troll" ), gettext_noop( "Trolls" ), 3, Race::BARB, 5, { 600, 0, 0, 0, 0, 0, 0 } },
237                 { gettext_noop( "War Troll" ), gettext_noop( "War Trolls" ), 3, Race::BARB, 5, { 700, 0, 0, 0, 0, 0, 0 } },
238                 { gettext_noop( "Cyclops" ), gettext_noop( "Cyclopes" ), 2, Race::BARB, 6, { 750, 0, 0, 0, 0, 1, 0 } },
239                 { gettext_noop( "Sprite" ), gettext_noop( "Sprites" ), 8, Race::SORC, 1, { 50, 0, 0, 0, 0, 0, 0 } },
240                 { gettext_noop( "Dwarf" ), gettext_noop( "Dwarves" ), 6, Race::SORC, 2, { 200, 0, 0, 0, 0, 0, 0 } },
241                 { gettext_noop( "Battle Dwarf" ), gettext_noop( "Battle Dwarves" ), 6, Race::SORC, 2, { 250, 0, 0, 0, 0, 0, 0 } },
242                 { gettext_noop( "Elf" ), gettext_noop( "Elves" ), 4, Race::SORC, 3, { 250, 0, 0, 0, 0, 0, 0 } },
243                 { gettext_noop( "Grand Elf" ), gettext_noop( "Grand Elves" ), 4, Race::SORC, 3, { 300, 0, 0, 0, 0, 0, 0 } },
244                 { gettext_noop( "Druid" ), gettext_noop( "Druids" ), 3, Race::SORC, 4, { 350, 0, 0, 0, 0, 0, 0 } },
245                 { gettext_noop( "Greater Druid" ), gettext_noop( "Greater Druids" ), 3, Race::SORC, 4, { 400, 0, 0, 0, 0, 0, 0 } },
246                 { gettext_noop( "Unicorn" ), gettext_noop( "Unicorns" ), 2, Race::SORC, 5, { 500, 0, 0, 0, 0, 0, 0 } },
247                 { gettext_noop( "Phoenix" ), gettext_noop( "Phoenixes" ), 1, Race::SORC, 6, { 1500, 0, 1, 0, 0, 0, 0 } },
248                 { gettext_noop( "Centaur" ), gettext_noop( "Centaurs" ), 8, Race::WRLK, 1, { 60, 0, 0, 0, 0, 0, 0 } },
249                 { gettext_noop( "Gargoyle" ), gettext_noop( "Gargoyles" ), 6, Race::WRLK, 2, { 200, 0, 0, 0, 0, 0, 0 } },
250                 { gettext_noop( "Griffin" ), gettext_noop( "Griffins" ), 4, Race::WRLK, 3, { 300, 0, 0, 0, 0, 0, 0 } },
251                 { gettext_noop( "Minotaur" ), gettext_noop( "Minotaurs" ), 3, Race::WRLK, 4, { 400, 0, 0, 0, 0, 0, 0 } },
252                 { gettext_noop( "Minotaur King" ), gettext_noop( "Minotaur Kings" ), 3, Race::WRLK, 4, { 500, 0, 0, 0, 0, 0, 0 } },
253                 { gettext_noop( "Hydra" ), gettext_noop( "Hydras" ), 2, Race::WRLK, 5, { 800, 0, 0, 0, 0, 0, 0 } },
254                 { gettext_noop( "Green Dragon" ), gettext_noop( "Green Dragons" ), 1, Race::WRLK, 6, { 3000, 0, 0, 0, 1, 0, 0 } },
255                 { gettext_noop( "Red Dragon" ), gettext_noop( "Red Dragons" ), 1, Race::WRLK, 6, { 3500, 0, 0, 0, 1, 0, 0 } },
256                 { gettext_noop( "Black Dragon" ), gettext_noop( "Black Dragons" ), 1, Race::WRLK, 6, { 4000, 0, 0, 0, 2, 0, 0 } },
257                 { gettext_noop( "Halfling" ), gettext_noop( "Halflings" ), 8, Race::WZRD, 1, { 50, 0, 0, 0, 0, 0, 0 } },
258                 { gettext_noop( "Boar" ), gettext_noop( "Boars" ), 6, Race::WZRD, 2, { 150, 0, 0, 0, 0, 0, 0 } },
259                 { gettext_noop( "Iron Golem" ), gettext_noop( "Iron Golems" ), 4, Race::WZRD, 3, { 300, 0, 0, 0, 0, 0, 0 } },
260                 { gettext_noop( "Steel Golem" ), gettext_noop( "Steel Golems" ), 4, Race::WZRD, 3, { 350, 0, 0, 0, 0, 0, 0 } },
261                 { gettext_noop( "Roc" ), gettext_noop( "Rocs" ), 3, Race::WZRD, 4, { 400, 0, 0, 0, 0, 0, 0 } },
262                 { gettext_noop( "Mage" ), gettext_noop( "Magi" ), 2, Race::WZRD, 5, { 600, 0, 0, 0, 0, 0, 0 } },
263                 { gettext_noop( "Archmage" ), gettext_noop( "Archmagi" ), 2, Race::WZRD, 5, { 700, 0, 0, 0, 0, 0, 0 } },
264                 { gettext_noop( "Giant" ), gettext_noop( "Giants" ), 1, Race::WZRD, 6, { 2000, 0, 0, 0, 0, 0, 1 } },
265                 { gettext_noop( "Titan" ), gettext_noop( "Titans" ), 1, Race::WZRD, 6, { 5000, 0, 0, 0, 0, 0, 2 } },
266                 { gettext_noop( "Skeleton" ), gettext_noop( "Skeletons" ), 8, Race::NECR, 1, { 75, 0, 0, 0, 0, 0, 0 } },
267                 { gettext_noop( "Zombie" ), gettext_noop( "Zombies" ), 6, Race::NECR, 2, { 150, 0, 0, 0, 0, 0, 0 } },
268                 { gettext_noop( "Mutant Zombie" ), gettext_noop( "Mutant Zombies" ), 6, Race::NECR, 2, { 200, 0, 0, 0, 0, 0, 0 } },
269                 { gettext_noop( "Mummy" ), gettext_noop( "Mummies" ), 4, Race::NECR, 3, { 250, 0, 0, 0, 0, 0, 0 } },
270                 { gettext_noop( "Royal Mummy" ), gettext_noop( "Royal Mummies" ), 4, Race::NECR, 3, { 300, 0, 0, 0, 0, 0, 0 } },
271                 { gettext_noop( "Vampire" ), gettext_noop( "Vampires" ), 3, Race::NECR, 4, { 500, 0, 0, 0, 0, 0, 0 } },
272                 { gettext_noop( "Vampire Lord" ), gettext_noop( "Vampire Lords" ), 3, Race::NECR, 4, { 650, 0, 0, 0, 0, 0, 0 } },
273                 { gettext_noop( "Lich" ), gettext_noop( "Liches" ), 2, Race::NECR, 5, { 750, 0, 0, 0, 0, 0, 0 } },
274                 { gettext_noop( "Power Lich" ), gettext_noop( "Power Liches" ), 2, Race::NECR, 5, { 900, 0, 0, 0, 0, 0, 0 } },
275                 { gettext_noop( "Bone Dragon" ), gettext_noop( "Bone Dragons" ), 1, Race::NECR, 6, { 1500, 0, 0, 0, 0, 0, 0 } },
276                 { gettext_noop( "Rogue" ), gettext_noop( "Rogues" ), 8, Race::NONE, 1, { 50, 0, 0, 0, 0, 0, 0 } },
277                 { gettext_noop( "Nomad" ), gettext_noop( "Nomads" ), 4, Race::NONE, 2, { 200, 0, 0, 0, 0, 0, 0 } },
278                 { gettext_noop( "Ghost" ), gettext_noop( "Ghosts" ), 3, Race::NONE, 3, { 1000, 0, 0, 0, 0, 0, 0 } },
279                 { gettext_noop( "Genie" ), gettext_noop( "Genies" ), 2, Race::NONE, 6, { 650, 0, 0, 0, 0, 0, 1 } },
280                 { gettext_noop( "Medusa" ), gettext_noop( "Medusas" ), 5, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } },
281                 { gettext_noop( "Earth Elemental" ), gettext_noop( "Earth Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } },
282                 { gettext_noop( "Air Elemental" ), gettext_noop( "Air Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } },
283                 { gettext_noop( "Fire Elemental" ), gettext_noop( "Fire Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } },
284                 { gettext_noop( "Water Elemental" ), gettext_noop( "Water Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } },
285                 { gettext_noop( "Random Monster" ), gettext_noop( "Random Monsters" ), 0, Race::NONE, 0, { 0, 0, 0, 0, 0, 0, 0 } },
286                 { gettext_noop( "Random Monster 1" ), gettext_noop( "Random Monsters 1" ), 0, Race::NONE, 1, { 0, 0, 0, 0, 0, 0, 0 } },
287                 { gettext_noop( "Random Monster 2" ), gettext_noop( "Random Monsters 2" ), 0, Race::NONE, 2, { 0, 0, 0, 0, 0, 0, 0 } },
288                 { gettext_noop( "Random Monster 3" ), gettext_noop( "Random Monsters 3" ), 0, Race::NONE, 3, { 0, 0, 0, 0, 0, 0, 0 } },
289                 { gettext_noop( "Random Monster 4" ), gettext_noop( "Random Monsters 4" ), 0, Race::NONE, 4, { 0, 0, 0, 0, 0, 0, 0 } } };
290 
291         monsterData.reserve( Monster::MONSTER_COUNT );
292 
293         for ( int i = 0; i < Monster::MONSTER_COUNT; ++i ) {
294             monsterData.emplace_back( monsterIcnIds[i], binFileName[i], monsterSounds[i], monsterBattleStats[i], monsterGeneralStats[i] );
295         }
296 
297         // Add monster abilities and weaknesses.
298         monsterData[Monster::RANGER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING );
299 
300         monsterData[Monster::CAVALRY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
301 
302         monsterData[Monster::CHAMPION].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
303 
304         monsterData[Monster::PALADIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK );
305 
306         monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK );
307         monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD );
308         monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::CURSE );
309         monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::MASSCURSE );
310 
311         monsterData[Monster::WOLF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
312         monsterData[Monster::WOLF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK );
313 
314         monsterData[Monster::TROLL].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::HP_REGENERATION );
315 
316         monsterData[Monster::WAR_TROLL].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::HP_REGENERATION );
317 
318         monsterData[Monster::CYCLOPS].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK );
319         monsterData[Monster::CYCLOPS].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::PARALYZE );
320 
321         monsterData[Monster::SPRITE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION );
322         monsterData[Monster::SPRITE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
323 
324         monsterData[Monster::DWARF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 25, 0 );
325 
326         monsterData[Monster::BATTLE_DWARF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 25, 0 );
327 
328         monsterData[Monster::ELF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING );
329 
330         monsterData[Monster::GRAND_ELF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING );
331 
332         monsterData[Monster::UNICORN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
333         monsterData[Monster::UNICORN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::BLIND );
334 
335         monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
336         monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK );
337         monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
338         monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY );
339 
340         monsterData[Monster::CENTAUR].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
341 
342         monsterData[Monster::GARGOYLE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
343 
344         monsterData[Monster::GRIFFIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
345         monsterData[Monster::GRIFFIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
346         monsterData[Monster::GRIFFIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ALWAYS_RETALIATE );
347 
348         monsterData[Monster::HYDRA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
349         monsterData[Monster::HYDRA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK );
350         monsterData[Monster::HYDRA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION );
351 
352         monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON );
353         monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
354         monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
355         monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 100, 0 );
356         monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK );
357 
358         monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON );
359         monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
360         monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
361         monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 100, 0 );
362         monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK );
363 
364         monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON );
365         monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
366         monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
367         monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 100, 0 );
368         monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK );
369 
370         monsterData[Monster::BOAR].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
371 
372         monsterData[Monster::IRON_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION, 50, 0 );
373 
374         monsterData[Monster::STEEL_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION, 50, 0 );
375 
376         monsterData[Monster::ROC].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
377         monsterData[Monster::ROC].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
378 
379         monsterData[Monster::MAGE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY );
380 
381         monsterData[Monster::ARCHMAGE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY );
382         monsterData[Monster::ARCHMAGE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::DISPEL );
383 
384         monsterData[Monster::GIANT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MIND_SPELL_IMMUNITY );
385 
386         monsterData[Monster::TITAN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY );
387         monsterData[Monster::TITAN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MIND_SPELL_IMMUNITY );
388 
389         monsterData[Monster::SKELETON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
390 
391         monsterData[Monster::ZOMBIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
392 
393         monsterData[Monster::MUTANT_ZOMBIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
394 
395         monsterData[Monster::MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
396         monsterData[Monster::MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::CURSE );
397 
398         monsterData[Monster::ROYAL_MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
399         monsterData[Monster::ROYAL_MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 30, Spell::CURSE );
400 
401         monsterData[Monster::VAMPIRE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
402         monsterData[Monster::VAMPIRE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
403         monsterData[Monster::VAMPIRE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION );
404 
405         monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
406         monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
407         monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION );
408         monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::HP_DRAIN );
409 
410         monsterData[Monster::LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
411         monsterData[Monster::LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::AREA_SHOT );
412 
413         monsterData[Monster::POWER_LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
414         monsterData[Monster::POWER_LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::AREA_SHOT );
415 
416         monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
417         monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON );
418         monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
419         monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
420         monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MORAL_DECREMENT, 100, 1 );
421 
422         monsterData[Monster::ROGUE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION );
423 
424         monsterData[Monster::GHOST].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SOUL_EATER );
425         monsterData[Monster::GHOST].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
426         monsterData[Monster::GHOST].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD );
427 
428         monsterData[Monster::GENIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ENEMY_HALFING, 10, 0 );
429         monsterData[Monster::GENIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING );
430 
431         monsterData[Monster::MEDUSA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
432         monsterData[Monster::MEDUSA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::STONE );
433 
434         monsterData[Monster::NOMAD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE );
435 
436         monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
437         monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::METEORSHOWER );
438         monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
439                                                                                Spell::LIGHTNINGBOLT );
440         monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
441                                                                                Spell::CHAINLIGHTNING );
442         monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
443                                                                                Spell::ELEMENTALSTORM );
444 
445         monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
446         monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT );
447         monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::CHAINLIGHTNING );
448         monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::ELEMENTALSTORM );
449         monsterData[Monster::EARTH_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100,
450                                                                                  Spell::METEORSHOWER );
451 
452         monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
453         monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FIRE_SPELL_IMMUNITY );
454         monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL );
455 
456         monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL );
457         monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::COLD_SPELL_IMMUNITY );
458         monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL );
459 
460         // TODO: verify that no duplicates of abilities and weaknesses exist.
461     }
462 
removeDuplicateSpell(std::set<int> & sortedSpellIds,const int massSpellId,const int spellId)463     void removeDuplicateSpell( std::set<int> & sortedSpellIds, const int massSpellId, const int spellId )
464     {
465         if ( sortedSpellIds.count( massSpellId ) > 0 && sortedSpellIds.count( spellId ) > 0 ) {
466             sortedSpellIds.erase( massSpellId );
467         }
468     }
469 
replaceMassSpells(const std::vector<int> & spellIds)470     std::vector<int> replaceMassSpells( const std::vector<int> & spellIds )
471     {
472         std::set<int> sortedSpellIds( spellIds.begin(), spellIds.end() );
473 
474         removeDuplicateSpell( sortedSpellIds, Spell::CHAINLIGHTNING, Spell::LIGHTNINGBOLT );
475         removeDuplicateSpell( sortedSpellIds, Spell::MASSCURE, Spell::CURE );
476         removeDuplicateSpell( sortedSpellIds, Spell::MASSCURSE, Spell::CURSE );
477         removeDuplicateSpell( sortedSpellIds, Spell::MASSBLESS, Spell::BLESS );
478         removeDuplicateSpell( sortedSpellIds, Spell::MASSHASTE, Spell::HASTE );
479         removeDuplicateSpell( sortedSpellIds, Spell::MASSSHIELD, Spell::SHIELD );
480         removeDuplicateSpell( sortedSpellIds, Spell::MASSDISPEL, Spell::DISPEL );
481         removeDuplicateSpell( sortedSpellIds, Spell::MASSSLOW, Spell::SLOW );
482 
483         return std::vector<int>( sortedSpellIds.begin(), sortedSpellIds.end() );
484     }
485 }
486 
487 namespace fheroes2
488 {
getMonsterData(const int monsterId)489     const MonsterData & getMonsterData( const int monsterId )
490     {
491         if ( monsterData.empty() ) {
492             populateMonsterData();
493         }
494 
495         assert( monsterId >= 0 && static_cast<size_t>( monsterId ) < monsterData.size() );
496         if ( monsterId < 0 || static_cast<size_t>( monsterId ) >= monsterData.size() ) {
497             return monsterData.front();
498         }
499 
500         return monsterData[monsterId];
501     }
502 
getMonsterAbilityDescription(const MonsterAbility & ability,const bool ignoreBasicAbility)503     std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbility )
504     {
505         switch ( ability.type ) {
506         case MonsterAbilityType::NONE:
507             return ignoreBasicAbility ? "" : _( "None" );
508         case MonsterAbilityType::DOUBLE_SHOOTING:
509             return _( "Double shot" );
510         case MonsterAbilityType::DOUBLE_HEX_SIZE:
511             return ignoreBasicAbility ? "" : _( "2-hex monster" );
512         case MonsterAbilityType::DOUBLE_MELEE_ATTACK:
513             return _( "Double strike" );
514         case MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD:
515             return _( "Double damage to Undead" );
516         case MonsterAbilityType::MAGIC_RESISTANCE:
517             return std::to_string( ability.percentage ) + _( "% magic resistance" );
518         case MonsterAbilityType::MIND_SPELL_IMMUNITY:
519             return _( "Immune to Mind spells" );
520         case MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY:
521             return _( "Immune to Elemental spells" );
522         case MonsterAbilityType::FIRE_SPELL_IMMUNITY:
523             return _( "Immune to Fire spells" );
524         case MonsterAbilityType::COLD_SPELL_IMMUNITY:
525             return _( "Immune to Cold spells" );
526         case MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL:
527             if ( ability.percentage == 100 ) {
528                 return std::string( _( "Immune to " ) ) + Spell( ability.value ).GetName();
529             }
530             else {
531                 std::string str = _( "% immunity to %{spell} spell" );
532                 StringReplace( str, "%{spell}", Spell( ability.value ).GetName() );
533                 return std::to_string( ability.percentage ) + str;
534             }
535         case MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION:
536             return std::to_string( ability.percentage ) + _( "% damage from Elemental spells" );
537         case MonsterAbilityType::SPELL_CASTER:
538             if ( ability.value == Spell::DISPEL ) {
539                 return std::to_string( ability.percentage ) + _( "% chance to Dispel beneficial spells" );
540             }
541             else if ( ability.value == Spell::PARALYZE ) {
542                 return std::to_string( ability.percentage ) + _( "% chance to Paralyze" );
543             }
544             else if ( ability.value == Spell::STONE ) {
545                 return std::to_string( ability.percentage ) + _( "% chance to Petrify" );
546             }
547             else if ( ability.value == Spell::BLIND ) {
548                 return std::to_string( ability.percentage ) + _( "% chance to Blind" );
549             }
550             else if ( ability.value == Spell::CURSE ) {
551                 return std::to_string( ability.percentage ) + _( "% chance to Curse" );
552             }
553             else {
554                 std::string str = _( "% chance to cast %{spell} spell" );
555                 StringReplace( str, "%{spell}", Spell( ability.value ).GetName() );
556                 return std::to_string( ability.percentage ) + str;
557             }
558         case MonsterAbilityType::HP_REGENERATION:
559             return _( "HP regeneration" );
560         case MonsterAbilityType::TWO_CELL_MELEE_ATTACK:
561             return _( "Two hexes attack" );
562         case MonsterAbilityType::FLYING:
563             return ignoreBasicAbility ? "" : _( "Flyer" );
564         case MonsterAbilityType::ALWAYS_RETALIATE:
565             return _( "Always retaliates" );
566         case MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK:
567             return _( "Attacks all adjacent enemies" );
568         case MonsterAbilityType::NO_MELEE_PENALTY:
569             return _( "No melee penalty" );
570         case MonsterAbilityType::DRAGON:
571             return ignoreBasicAbility ? "" : _( "Dragon" );
572         case MonsterAbilityType::UNDEAD:
573             return _( "Undead" );
574         case MonsterAbilityType::NO_ENEMY_RETALIATION:
575             return _( "No enemy retaliation" );
576         case MonsterAbilityType::HP_DRAIN:
577             return _( "HP drain" );
578         case MonsterAbilityType::AREA_SHOT:
579             return _( "Cloud attack" );
580         case MonsterAbilityType::MORAL_DECREMENT:
581             return _( "Decreases enemy's morale by " ) + std::to_string( ability.value );
582         case MonsterAbilityType::ENEMY_HALFING:
583             return std::to_string( ability.percentage ) + _( "% chance to halve enemy" );
584         case MonsterAbilityType::SOUL_EATER:
585             return _( "Soul Eater" );
586         case MonsterAbilityType::ELEMENTAL:
587             return ignoreBasicAbility ? _( "No Morale" ) : _( "Elemental" );
588         default:
589             break;
590         }
591 
592         assert( 0 ); // Did you add a new ability? Please add the implementation!
593         return "";
594     }
595 
getMonsterWeaknessDescription(const MonsterWeakness & weakness,const bool ignoreBasicAbility)596     std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicAbility )
597     {
598         switch ( weakness.type ) {
599         case MonsterWeaknessType::NONE:
600             return ignoreBasicAbility ? "" : _( "None" );
601         case MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL:
602             return _( "200% damage from Fire spells" );
603         case MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL:
604             return _( "200% damage from Cold spells" );
605         case MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL: {
606             std::string str = _( "% damage from %{spell} spell" );
607             StringReplace( str, "%{spell}", Spell( weakness.value ).GetName() );
608             return std::to_string( weakness.percentage + 100 ) + str;
609         }
610         default:
611             break;
612         }
613 
614         assert( 0 ); // Did you add a new weakness? Please add the implementation!
615         return "";
616     }
617 
getMonsterDescription(const int monsterId)618     std::string getMonsterDescription( const int monsterId )
619     {
620         if ( monsterData.empty() ) {
621             populateMonsterData();
622         }
623 
624         assert( monsterId >= 0 && static_cast<size_t>( monsterId ) < monsterData.size() );
625         if ( monsterId < 0 || static_cast<size_t>( monsterId ) >= monsterData.size() ) {
626             return "";
627         }
628 
629         const MonsterData & monster = monsterData[monsterId];
630 
631         std::ostringstream os;
632         os << "----------" << std::endl;
633         os << "Name: " << monster.generalStats.name << std::endl;
634         os << "Plural name: " << monster.generalStats.pluralName << std::endl;
635         os << "Base growth: " << monster.generalStats.baseGrowth << std::endl;
636         os << "Race: " << Race::String( monster.generalStats.race ) << std::endl;
637         os << "Level: " << monster.generalStats.level << std::endl;
638         os << "Cost: " << Funds( monster.generalStats.cost ).String() << std::endl;
639         os << std::endl;
640         os << "Attack: " << monster.battleStats.attack << std::endl;
641         os << "Defense: " << monster.battleStats.defense << std::endl;
642         os << "Min damage: " << monster.battleStats.damageMin << std::endl;
643         os << "Max damage: " << monster.battleStats.damageMax << std::endl;
644         os << "Hit Points: " << monster.battleStats.hp << std::endl;
645         os << "Speed: " << Speed::String( monster.battleStats.speed ) << std::endl;
646         os << "Number of shots: " << monster.battleStats.shots << std::endl;
647         if ( !monster.battleStats.abilities.empty() ) {
648             os << std::endl;
649             os << "Abilities:" << std::endl;
650             for ( const MonsterAbility & ability : monster.battleStats.abilities ) {
651                 os << "   " << getMonsterAbilityDescription( ability, false ) << std::endl;
652             }
653         }
654 
655         if ( !monster.battleStats.weaknesses.empty() ) {
656             os << std::endl;
657             os << "Weaknesses:" << std::endl;
658             for ( const MonsterWeakness & weakness : monster.battleStats.weaknesses ) {
659                 os << "   " << getMonsterWeaknessDescription( weakness, false ) << std::endl;
660             }
661         }
662 
663         return os.str();
664     }
665 
getMonsterPropertiesDescription(const int monsterId)666     std::vector<std::string> getMonsterPropertiesDescription( const int monsterId )
667     {
668         std::vector<std::string> output;
669 
670         const MonsterBattleStats & battleStats = getMonsterData( monsterId ).battleStats;
671 
672         const std::vector<MonsterAbility> & abilities = battleStats.abilities;
673 
674         std::map<uint32_t, std::vector<int>> immuneToSpells;
675         for ( const MonsterAbility & ability : abilities ) {
676             if ( ability.type == MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL ) {
677                 immuneToSpells[ability.percentage].emplace_back( ability.value );
678                 continue;
679             }
680             const std::string abilityDescription = getMonsterAbilityDescription( ability, true );
681             if ( !abilityDescription.empty() ) {
682                 output.emplace_back( abilityDescription + '.' );
683             }
684         }
685 
686         for ( auto spellInfoIter = immuneToSpells.begin(); spellInfoIter != immuneToSpells.end(); ++spellInfoIter ) {
687             assert( !spellInfoIter->second.empty() );
688 
689             std::string temp;
690 
691             if ( spellInfoIter->first == 100 ) {
692                 temp += _( "Immune to " );
693             }
694             else {
695                 temp += std::to_string( spellInfoIter->first ) + _( "% immunity to " );
696             }
697 
698             const std::vector<int> sortedSpells = replaceMassSpells( spellInfoIter->second );
699 
700             for ( size_t i = 0; i < sortedSpells.size(); ++i ) {
701                 if ( i > 0 ) {
702                     temp += ", ";
703                 }
704 
705                 if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) {
706                     temp += _( "Lightning" );
707                 }
708                 else {
709                     temp += Spell( sortedSpells[i] ).GetName();
710                 }
711             }
712 
713             temp += '.';
714             output.emplace_back( std::move( temp ) );
715         }
716 
717         std::map<uint32_t, std::vector<int>> extraDamageSpells;
718         const std::vector<MonsterWeakness> & weaknesses = battleStats.weaknesses;
719         for ( const MonsterWeakness & weakness : weaknesses ) {
720             if ( weakness.type == MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL ) {
721                 extraDamageSpells[weakness.percentage].emplace_back( weakness.value );
722                 continue;
723             }
724 
725             const std::string weaknessDescription = getMonsterWeaknessDescription( weakness, true );
726             if ( !weaknessDescription.empty() ) {
727                 output.emplace_back( weaknessDescription + '.' );
728             }
729         }
730 
731         for ( auto spellInfoIter = extraDamageSpells.begin(); spellInfoIter != extraDamageSpells.end(); ++spellInfoIter ) {
732             assert( !spellInfoIter->second.empty() );
733 
734             std::string temp;
735 
736             temp += std::to_string( spellInfoIter->first + 100 ) + _( "% damage from " );
737 
738             const std::vector<int> sortedSpells = replaceMassSpells( spellInfoIter->second );
739 
740             for ( size_t i = 0; i < sortedSpells.size(); ++i ) {
741                 if ( i > 0 ) {
742                     temp += ", ";
743                 }
744 
745                 if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) {
746                     temp += _( "Lightning" );
747                 }
748                 else {
749                     temp += Spell( sortedSpells[i] ).GetName();
750                 }
751             }
752 
753             temp += '.';
754             output.emplace_back( std::move( temp ) );
755         }
756 
757         return output;
758     }
759 
getSpellResistance(const int monsterId,const int spellId)760     uint32_t getSpellResistance( const int monsterId, const int spellId )
761     {
762         const std::vector<MonsterAbility> & abilities = getMonsterData( monsterId ).battleStats.abilities;
763 
764         Spell spell( spellId );
765 
766         std::vector<MonsterAbility>::const_iterator foundAbility;
767 
768         if ( spell.isMindInfluence() ) {
769             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::MIND_SPELL_IMMUNITY ) );
770             if ( foundAbility != abilities.end() ) {
771                 return 100;
772             }
773 
774             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) );
775             if ( foundAbility != abilities.end() ) {
776                 return 100;
777             }
778 
779             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL ) );
780             if ( foundAbility != abilities.end() ) {
781                 return 100;
782             }
783         }
784 
785         if ( spell.isALiveOnly() ) {
786             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) );
787             if ( foundAbility != abilities.end() ) {
788                 return 100;
789             }
790         }
791 
792         if ( spell.isUndeadOnly() ) {
793             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) );
794             if ( foundAbility == abilities.end() ) {
795                 return 100;
796             }
797         }
798 
799         if ( spell == Spell::RESURRECT || spell == Spell::RESURRECTTRUE || spell == Spell::ANIMATEDEAD ) {
800             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL ) );
801             if ( foundAbility != abilities.end() ) {
802                 return 100;
803             }
804         }
805 
806         if ( spell.isCold() ) {
807             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::COLD_SPELL_IMMUNITY ) );
808             if ( foundAbility != abilities.end() ) {
809                 return 100;
810             }
811         }
812 
813         if ( spell.isFire() ) {
814             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::FIRE_SPELL_IMMUNITY ) );
815             if ( foundAbility != abilities.end() ) {
816                 return 100;
817             }
818         }
819 
820         if ( spell == Spell::COLDRAY || spell == Spell::COLDRING || spell == Spell::FIREBALL || spell == Spell::FIREBLAST || spell == Spell::LIGHTNINGBOLT
821              || spell == Spell::CHAINLIGHTNING || spell == Spell::ELEMENTALSTORM ) {
822             foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY ) );
823             if ( foundAbility != abilities.end() ) {
824                 return 100;
825             }
826         }
827 
828         uint32_t spellResistance = 0;
829 
830         // Find magic immunity for every spell.
831         foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::MAGIC_RESISTANCE ) );
832         if ( foundAbility != abilities.end() ) {
833             if ( foundAbility->percentage == 100 ) {
834                 // Immune to everything.
835                 return 100;
836             }
837             if ( spell.isDamage() || spell.isApplyToEnemies() ) {
838                 spellResistance = foundAbility->percentage;
839             }
840         }
841 
842         for ( const MonsterAbility & ability : abilities ) {
843             if ( ability.type == MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL && static_cast<int>( ability.value ) == spellId ) {
844                 spellResistance = std::max( spellResistance, ability.percentage );
845             }
846         }
847 
848         return spellResistance;
849     }
850 }
851