]> git.localhorst.tv Git - l2e.git/blob - src/battle/Battle.cpp
extracted battle logic into a class
[l2e.git] / src / battle / Battle.cpp
1 #include "Battle.h"
2
3 #include "AttackChoice.h"
4 #include "PartyLayout.h"
5 #include "TargetSelection.h"
6 #include "../common/Stats.h"
7
8 #include <cassert>
9 #include <stdexcept>
10
11 using common::Stats;
12
13
14 namespace battle {
15
16 Battle::Battle(
17                 const PartyLayout *heroesLayout,
18                 const PartyLayout *monstersLayout)
19 : heroesLayout(heroesLayout)
20 , monstersLayout(monstersLayout)
21 , activeHero(-1)
22 , attackCursor(-1)
23 , expReward(0)
24 , goldReward(0) {
25         if (!heroesLayout) {
26                 throw std::invalid_argument("construct battle without heroes layout");
27         }
28         if (!monstersLayout) {
29                 throw std::invalid_argument("construct battle without monsters layout");
30         }
31         heroes.reserve(4);
32         monsters.resize(monstersLayout->NumPositions());
33 }
34
35
36 void Battle::AddHero(const Hero &h) {
37         if (NumHeroes() == MaxHeroes()) {
38                 throw std::overflow_error("max heroes breached");
39         }
40         heroes.push_back(h);
41         heroes.back().GetAttackChoice() = AttackChoice(this);
42 }
43
44 void Battle::AddMonster(const Monster &m) {
45         for (int i = 0; i < MaxMonsters(); ++i) {
46                 if (!MonsterPositionOccupied(i)) {
47                         MonsterAt(i) = m;
48                         MonsterAt(i).GetAttackChoice() = AttackChoice(this);
49                         return;
50                 }
51         }
52         throw std::overflow_error("all monster positions occupied");
53 }
54
55 void Battle::SetCapsule(const Capsule &c) {
56         capsule = c;
57 }
58
59
60 int Battle::NumHeroes() const {
61         return heroes.size();
62 }
63
64 int Battle::MaxHeroes() const {
65         return heroesLayout->NumPositions() - 1;
66 }
67
68 int Battle::NumMonsters() const {
69         return monsters.size();
70 }
71
72 int Battle::MaxMonsters() const {
73         return monstersLayout->NumPositions();
74 }
75
76 bool Battle::HasCapsule() const {
77         return capsule.Active();
78 }
79
80
81 Hero &Battle::HeroAt(int index) {
82         assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
83         return heroes[index];
84 }
85
86 const Hero &Battle::HeroAt(int index) const {
87         assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
88         return heroes[index];
89 }
90
91
92 Monster &Battle::MonsterAt(int index) {
93         assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
94         return monsters[index];
95 }
96
97 const Monster &Battle::MonsterAt(int index) const {
98         assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
99         return monsters[index];
100 }
101
102 bool Battle::HeroPositionOccupied(int index) const {
103         return index >= 0 && index < NumHeroes();
104 }
105
106 bool Battle::HeroAlive(int index) const {
107         return HeroPositionOccupied(index) && HeroAt(index).Health() > 0;
108 }
109
110
111 bool Battle::MonsterPositionOccupied(int index) const {
112         return MonsterAlive(index);
113 }
114
115 bool Battle::MonsterAlive(int index) const {
116         return index >= 0 && index < NumMonsters() && MonsterAt(index).Health() > 0;
117 }
118
119 bool Battle::CapsuleAlive() const {
120         return capsule.Active() && capsule.Health() > 0;
121 }
122
123
124 void Battle::NextHero() {
125         ++activeHero;
126         while (activeHero < NumHeroes() && HeroAt(activeHero).Health() == 0) {
127                 ++activeHero;
128         }
129 }
130
131 void Battle::PreviousHero() {
132         --activeHero;
133         while (activeHero >= 0 && HeroAt(activeHero).Health() == 0) {
134                 --activeHero;
135         }
136 }
137
138 void Battle::SwapHeroes(int lhs, int rhs) {
139         if (lhs < 0 || lhs >= NumHeroes() || rhs < 0 || rhs >= NumHeroes() || lhs == rhs) return;
140         std::swap(HeroAt(lhs), HeroAt(rhs));
141 }
142
143 bool Battle::HasChosenAttackType() const {
144         return ActiveHero().GetAttackChoice().GetType() != AttackChoice::UNDECIDED;
145 }
146
147 bool Battle::AttackSelectionDone() const {
148         return activeHero >= NumHeroes();
149 }
150
151
152 class OrderCompare {
153 public:
154         OrderCompare(Battle *battle) : battle(battle) { }
155         bool operator ()(const Battle::Order &lhs, const Battle::Order &rhs) {
156                 return lhs.GetStats(*battle).Agility() > rhs.GetStats(*battle).Agility();
157         }
158 private:
159         Battle *battle;
160 };
161
162
163 void Battle::CalculateAttackOrder() {
164         attackOrder.reserve(NumMonsters() + NumHeroes() + 1);
165         for (int i(0); i < NumHeroes(); ++i) {
166                 attackOrder.push_back(Order(Order::HERO, i));
167         }
168         for (std::vector<Monster>::size_type i(0), end(NumMonsters()); i < end; ++i) {
169                 attackOrder.push_back(Order(Order::MONSTER, i));
170                 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
171         }
172         if (GetCapsule().Active() && GetCapsule().Health() > 0) {
173                 attackOrder.push_back(Order(Order::CAPSULE));
174         }
175         std::sort(attackOrder.begin(), attackOrder.end(), OrderCompare(this));
176 }
177
178 void Battle::NextAttack() {
179         if (Victory() || Defeat()) {
180                 attackCursor = attackOrder.size();
181                 return;
182         }
183         ++attackCursor;
184         while (attackCursor < int(attackOrder.size())) {
185                 if (attackOrder[attackCursor].IsMonster()) {
186                         if (MonsterAlive(attackOrder[attackCursor].index)) break;
187                 } else if (attackOrder[attackCursor].IsHero()) {
188                         if (HeroAlive(attackOrder[attackCursor].index)) break;
189                 } else {
190                         if (CapsuleAlive()) break;
191                 }
192                 ++attackCursor;
193         }
194 }
195
196 bool Battle::AttacksFinished() const {
197         return attackCursor >= int(attackOrder.size())
198                         || Victory() || Defeat();
199 }
200
201 void Battle::CalculateDamage() {
202         if (CurrentAttack().IsMonster()) {
203                 DecideMonsterAttack(MonsterAt(CurrentAttack().index));
204         } else if (CurrentAttack().IsCapsule()) {
205                 DecideCapsuleAttack();
206         }
207         AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
208         if (ac.GetType() == AttackChoice::DEFEND) return;
209         TargetSelection &ts(ac.Selection());
210
211         const Stats &attackerStats = CurrentAttack().GetStats(*this);
212         CalculateDamage(attackerStats, ts);
213 }
214
215 AttackChoice &Battle::Order::GetAttackChoice(Battle &b) const {
216         switch (by) {
217                 case HERO:
218                         return b.HeroAt(index).GetAttackChoice();
219                 case CAPSULE:
220                         return b.GetCapsule().GetAttackChoice();
221                 case MONSTER:
222                         return b.MonsterAt(index).GetAttackChoice();
223                 default:
224                         throw std::runtime_error("invalid case in Battle::Order::GetAttackChoice()");
225         }
226 }
227
228 Stats &Battle::Order::GetStats(Battle &b) const {
229         switch (by) {
230                 case HERO:
231                         return b.HeroAt(index).GetStats();
232                 case CAPSULE:
233                         return b.GetCapsule().GetStats();
234                 case MONSTER:
235                         return b.MonsterAt(index).GetStats();
236                 default:
237                         throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
238         }
239 }
240
241 void Battle::ApplyDamage() {
242         if (attackCursor < 0) return;
243         AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
244         TargetSelection &ts(ac.Selection());
245         if (ts.TargetsMonsters()) {
246                 for (int i(0); i < NumMonsters(); ++i) {
247                         Monster &monster(MonsterAt(i));
248                         if (ts.IsBad(i)) {
249                                 monster.SubtractHealth(ts.GetAmount(i));
250                                 if (monster.Health() == 0) {
251                                         expReward += monster.ExpReward();
252                                         goldReward += monster.GoldReward();
253                                 }
254                         }
255                 }
256         } else {
257                 for (int i(0); i < NumHeroes(); ++i) {
258                         Hero &hero(HeroAt(i));
259                         if (ts.IsBad(i)) {
260                                 hero.SubtractHealth(ts.GetAmount(i));
261                         }
262                 }
263         }
264 }
265
266 const Battle::Order &Battle::CurrentAttack() const {
267         assert(attackCursor >= 0 && attackCursor < int(attackOrder.size()));
268         return attackOrder[attackCursor];
269 }
270
271 AttackChoice &Battle::CurrentAttackAttackChoice() {
272         return CurrentAttack().GetAttackChoice(*this);
273 }
274
275 void Battle::ClearAllAttacks() {
276         attackCursor = -1;
277         activeHero = -1;
278         for (int i(0); i < NumHeroes(); ++i) {
279                 HeroAt(i).GetAttackChoice() = AttackChoice(this);
280         }
281         for (int i(0); i < NumMonsters(); ++i) {
282                 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
283         }
284         GetCapsule().GetAttackChoice() = AttackChoice(this);
285         attackOrder.clear();
286 }
287
288
289 void Battle::DecideMonsterAttack(Monster &m) {
290         AttackChoice &ac(m.GetAttackChoice());
291         TargetSelection &ts(ac.Selection());
292         ac.Reset();
293         int target(rand() % NumHeroes());
294         while (!HeroAlive(target)) {
295                 target = rand() % NumHeroes();
296         }
297         ac.SetType(AttackChoice::SWORD);
298         ts.SelectHeroes();
299         ts.SetSingle();
300         ts.Select(target);
301 }
302
303 void Battle::DecideCapsuleAttack() {
304         AttackChoice &ac(GetCapsule().GetAttackChoice());
305         TargetSelection &ts(ac.Selection());
306         ac.Reset();
307         int target(rand() % NumMonsters());
308         while (!MonsterAlive(target)) {
309                 target = rand() % NumMonsters();
310         }
311         ac.SetType(AttackChoice::SWORD);
312         ts.SelectMonsters();
313         ts.SetSingle();
314         ts.Select(target);
315 }
316
317 void Battle::CalculateDamage(const Stats &attackerStats, TargetSelection &ts) const {
318         bool hitSome(false);
319         if (ts.TargetsMonsters()) {
320                 for (int i(0); i < MaxMonsters(); ++i) {
321                         if (ts.IsSelected(i)) {
322                                 if (MonsterAt(i).Health() > 0) {
323                                         const Stats &defenderStats(MonsterAt(i).GetStats());
324                                         Uint16 damage(CalculateDamage(attackerStats, defenderStats));
325                                         ts.SetBad(i, damage);
326                                         hitSome = true;
327                                 } else {
328                                         ts.Unselect(i);
329                                 }
330                         }
331                 }
332                 if (hitSome) return;
333                 for (int i(0); i < MaxMonsters(); ++i) {
334                         if (MonsterAt(i).Health() > 0) {
335                                 const Stats &defenderStats(MonsterAt(i).GetStats());
336                                 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
337                                 ts.SetBad(i, damage);
338                                 break;
339                         }
340                 }
341         } else {
342                 for (int i(0); i < NumHeroes(); ++i) {
343                         if (ts.IsSelected(i)) {
344                                 if (HeroAt(i).Health() > 0) {
345                                         const Stats &defenderStats(HeroAt(i).GetStats());
346                                         Uint16 damage(CalculateDamage(attackerStats, defenderStats));
347                                         ts.SetBad(i, damage);
348                                         hitSome = true;
349                                 } else {
350                                         ts.Unselect(i);
351                                 }
352                         }
353                 }
354                 if (hitSome) return;
355                 for (int i(0); i < NumHeroes(); ++i) {
356                         if (HeroAt(i).Health() > 0) {
357                                 const Stats &defenderStats(HeroAt(i).GetStats());
358                                 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
359                                 ts.SetBad(i, damage);
360                                 break;
361                         }
362                 }
363         }
364 }
365
366 Uint16 Battle::CalculateDamage(const Stats &attacker, const Stats &defender) const {
367         return attacker.Attack() / 2 - defender.Defense() / 4;
368 }
369
370
371 bool Battle::Victory() const {
372         for (int i(0); i < NumMonsters(); ++i) {
373                 if (MonsterAlive(i)) return false;
374         }
375         return true;
376 }
377
378 bool Battle::Defeat() const {
379         for (int i(0); i < NumHeroes(); ++i) {
380                 if (HeroAlive(i)) return false;
381         }
382         return true;
383 }
384
385 }