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