]> git.localhorst.tv Git - l2e.git/blob - src/battle/BattleState.cpp
57ff3270d170daab546e8c51c461bcb6195b5ba3
[l2e.git] / src / battle / BattleState.cpp
1 #include "BattleState.h"
2
3 #include "PartyLayout.h"
4 #include "states/SelectMoveAction.h"
5 #include "states/PerformAttacks.h"
6 #include "../app/Application.h"
7 #include "../app/Input.h"
8 #include "../common/GameState.h"
9 #include "../common/Ikari.h"
10 #include "../common/Inventory.h"
11 #include "../common/Item.h"
12 #include "../common/Spell.h"
13 #include "../graphics/Frame.h"
14 #include "../graphics/Sprite.h"
15 #include "../math/Vector.h"
16
17 #include <algorithm>
18 #include <cassert>
19 #include <cstdlib>
20 #include <stdexcept>
21
22 using app::Application;
23 using app::Input;
24 using common::Inventory;
25 using common::Item;
26 using common::Spell;
27 using common::Stats;
28 using math::Vector;
29 using graphics::Menu;
30
31 using std::rand;
32 using std::vector;
33
34 namespace battle {
35
36 void BattleState::AddMonster(const Monster &m) {
37         if (monsters.size() >= monstersLayout->NumPositions()) {
38                 throw std::overflow_error("too many monsters for layout");
39         }
40         monsters.push_back(m);
41 }
42
43 void BattleState::AddHero(const Hero &h) {
44         if (numHeroes >= 4 || numHeroes >= (int)heroesLayout->NumPositions()) {
45                 throw std::overflow_error("too many heroes for layout");
46         }
47         heroes[numHeroes] = h;
48         ++numHeroes;
49 }
50
51 void BattleState::SetCapsule(const Capsule &c) {
52         capsule = c;
53 }
54
55 void BattleState::NextHero() {
56         ++activeHero;
57         while (activeHero < numHeroes && heroes[activeHero].Health() == 0) {
58                 ++activeHero;
59         }
60 }
61
62 void BattleState::PreviousHero() {
63         --activeHero;
64         while (activeHero >= 0 && heroes[activeHero].Health() == 0) {
65                 --activeHero;
66         }
67 }
68
69 void BattleState::SwapHeroes(int lhs, int rhs) {
70         if (lhs < 0 || lhs >= numHeroes || rhs < 0 || rhs >= numHeroes || lhs == rhs) return;
71         std::swap(heroes[lhs], heroes[rhs]);
72 }
73
74
75 void BattleState::OnResize(int w, int h) {
76         offset = Vector<int>(
77                                 (w - background->w) / 2,
78                                 (h - background->h) / 2);
79 }
80
81
82 void BattleState::OnEnterState(SDL_Surface *screen) {
83         for (int i(0); i < 4; ++i) {
84                 heroes[i].Position() = heroesLayout->CalculatePosition(i, background->w, background->h);
85                 heroes[i].SpellMenu() = *res->spellMenuProperties;
86                 heroes[i].UpdateSpellMenu();
87                 heroes[i].IkariMenu() = *res->ikariMenuProperties;
88                 heroes[i].UpdateIkariMenu(res);
89                 heroTags[i] = HeroTag(this, i);
90                 smallHeroTags[i] = SmallHeroTag(this, i);
91         }
92
93         capsule.Position() = heroesLayout->CalculatePosition(4, background->w, background->h);
94
95         for (int i(0); i < int(monsters.size()); ++i) {
96                 monsters[i].Position() = monstersLayout->CalculatePosition(i, background->w, background->h);
97         }
98
99         int tagHeight(attackTypeMenu.Height());
100         int tagWidth(attackTypeMenu.Width() * 2 + attackTypeMenu.Width() / 2);
101         int xOffset((Width() - 2 * tagWidth) / 2);
102         heroTagPositions[0] = Vector<int>(xOffset, Height() - 2 * tagHeight);
103         heroTagPositions[1] = Vector<int>(xOffset + tagWidth, Height() - 2 * tagHeight);
104         heroTagPositions[2] = Vector<int>(xOffset, Height() - tagHeight);
105         heroTagPositions[3] = Vector<int>(xOffset + tagWidth, Height() - tagHeight);
106
107         tagHeight = res->normalFont->CharHeight() * 4 + res->smallHeroTagFrame->BorderHeight() * 2;
108         tagWidth = res->normalFont->CharWidth() * 6 + res->smallHeroTagFrame->BorderWidth() * 2;
109         xOffset = (Width() - 4 * tagWidth) / 2;
110         int yOffset(Height() - tagHeight);
111         smallHeroTagPositions[0] = Vector<int>(xOffset, yOffset);
112         smallHeroTagPositions[1] = Vector<int>(xOffset + 2 * tagWidth, yOffset);
113         smallHeroTagPositions[2] = Vector<int>(xOffset + tagWidth, yOffset);
114         smallHeroTagPositions[3] = Vector<int>(xOffset + 3 * tagWidth, yOffset);
115
116         OnResize(screen->w, screen->h);
117
118         itemMenu = *res->itemMenuProperties;
119         LoadInventory();
120         ClearAllAttacks();
121 }
122
123 void BattleState::LoadInventory() {
124         const Inventory &inv(game->state->inventory);
125         itemMenu.Clear();
126         itemMenu.Reserve(inv.MaxItems());
127         for (int i(0); i < inv.MaxItems(); ++i) {
128                 const Item *item(inv.ItemAt(i));
129                 if (item) {
130                         itemMenu.Add(item->Name(), item, item->CanUseInBattle(), item->MenuIcon(), inv.ItemCountAt(i));
131                 } else {
132                         itemMenu.AddEmptyEntry();
133                 }
134         }
135 }
136
137 void BattleState::OnExitState(SDL_Surface *screen) {
138
139 }
140
141 void BattleState::OnResumeState(SDL_Surface *screen) {
142         if (ranAway) {
143                 Ctrl().PopState(); // quit the battle scene
144                 return;
145         }
146         if (Victory()) {
147                 Ctrl().PopState();
148                 return;
149         }
150         if (Defeat()) {
151                 Ctrl().PopState();
152                 return;
153         }
154         // TODO: this should not push a state while quitting
155         if (AttackSelectionDone()) {
156                 Ctrl().PushState(new PerformAttacks(this));
157         } else {
158                 Ctrl().PushState(new SelectMoveAction(this));
159         }
160 }
161
162 bool BattleState::Victory() const {
163         for (int i(0); i < MaxMonsters(); ++i) {
164                 if (MonsterAt(i).Health() > 0) return false;
165         }
166         return true;
167 }
168
169 bool BattleState::Defeat() const {
170         for (int i(0); i < NumHeroes(); ++i) {
171                 if (HeroAt(i).Health() > 0) return false;
172         }
173         return true;
174 }
175
176 void BattleState::OnPauseState(SDL_Surface *screen) {
177
178 }
179
180
181 class OrderCompare {
182         public:
183                 OrderCompare(BattleState *battle) : battle(battle) { }
184                 bool operator ()(const BattleState::Order &lhs, const BattleState::Order &rhs) {
185                         return lhs.GetStats(*battle).Agility() > rhs.GetStats(*battle).Agility();
186                 }
187         private:
188                 BattleState *battle;
189 };
190
191 void BattleState::CalculateAttackOrder() {
192         attackOrder.reserve(monsters.size() + NumHeroes());
193         for (int i(0); i < NumHeroes(); ++i) {
194                 attackOrder.push_back(Order(Order::HERO, i));
195         }
196         for (vector<Monster>::size_type i(0), end(monsters.size()); i < end; ++i) {
197                 attackOrder.push_back(Order(Order::MONSTER, i));
198                 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
199         }
200         if (capsule.Active() && capsule.Health() > 0) {
201                 attackOrder.push_back(Order(Order::CAPSULE));
202         }
203         std::sort(attackOrder.begin(), attackOrder.end(), OrderCompare(this));
204 }
205
206 void BattleState::NextAttack() {
207         if (Victory() || Defeat()) {
208                 attackCursor = attackOrder.size();
209                 return;
210         }
211         ++attackCursor;
212         while (attackCursor < int(attackOrder.size())) {
213                 if (attackOrder[attackCursor].IsMonster()) {
214                         if (MonsterAt(attackOrder[attackCursor].index).Health() > 0) break;
215                 } else if (attackOrder[attackCursor].IsHero()) {
216                         if (HeroAt(attackOrder[attackCursor].index).Health() > 0) break;
217                 } else {
218                         if (capsule.Active() && capsule.Health() > 0) break;
219                 }
220                 ++attackCursor;
221         }
222 }
223
224 bool BattleState::AttacksFinished() const {
225         return attackCursor >= int(attackOrder.size())
226                         || Victory() || Defeat();
227 }
228
229 void BattleState::CalculateDamage() {
230         if (CurrentAttack().IsMonster()) {
231                 DecideMonsterAttack(MonsterAt(CurrentAttack().index));
232         } else if (CurrentAttack().IsCapsule()) {
233                 DecideCapsuleAttack();
234         }
235         AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
236         if (ac.GetType() == AttackChoice::DEFEND) return;
237         TargetSelection &ts(ac.Selection());
238
239         const Stats &attackerStats = CurrentAttack().GetStats(*this);
240         CalculateDamage(attackerStats, ts);
241 }
242
243 void BattleState::DecideMonsterAttack(Monster &m) {
244         AttackChoice &ac(m.GetAttackChoice());
245         TargetSelection &ts(ac.Selection());
246         ac.Reset();
247         int target(rand() % NumHeroes());
248         while (!HeroPositionOccupied(target)) {
249                 target = rand() % NumHeroes();
250         }
251         ac.SetType(AttackChoice::SWORD);
252         ts.SelectHeroes();
253         ts.SetSingle();
254         ts.Select(target);
255 }
256
257 void BattleState::DecideCapsuleAttack() {
258         AttackChoice &ac(capsule.GetAttackChoice());
259         TargetSelection &ts(ac.Selection());
260         ac.Reset();
261         int target(rand() % monsters.size());
262         while (!MonsterPositionOccupied(target)) {
263                 target = rand() % monsters.size();
264         }
265         ac.SetType(AttackChoice::SWORD);
266         ts.SelectMonsters();
267         ts.SetSingle();
268         ts.Select(target);
269 }
270
271 AttackChoice &BattleState::Order::GetAttackChoice(BattleState &b) const {
272         switch (by) {
273                 case HERO:
274                         return b.HeroAt(index).GetAttackChoice();
275                 case CAPSULE:
276                         return b.GetCapsule().GetAttackChoice();
277                 case MONSTER:
278                         return b.MonsterAt(index).GetAttackChoice();
279                 default:
280                         throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
281         }
282 }
283
284 Stats &BattleState::Order::GetStats(BattleState &b) const {
285         switch (by) {
286                 case HERO:
287                         return b.HeroAt(index).GetStats();
288                 case CAPSULE:
289                         return b.GetCapsule().GetStats();
290                 case MONSTER:
291                         return b.MonsterAt(index).GetStats();
292                 default:
293                         throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
294         }
295 }
296
297 void BattleState::CalculateDamage(const Stats &attackerStats, TargetSelection &ts) const {
298         bool hitSome(false);
299         if (ts.TargetsMonsters()) {
300                 for (int i(0); i < MaxMonsters(); ++i) {
301                         if (ts.IsSelected(i)) {
302                                 if (MonsterAt(i).Health() > 0) {
303                                         const Stats &defenderStats(MonsterAt(i).GetStats());
304                                         Uint16 damage(CalculateDamage(attackerStats, defenderStats));
305                                         ts.SetBad(i, damage);
306                                         hitSome = true;
307                                 } else {
308                                         ts.Unselect(i);
309                                 }
310                         }
311                 }
312                 if (hitSome) return;
313                 for (int i(0); i < MaxMonsters(); ++i) {
314                         if (MonsterAt(i).Health() > 0) {
315                                 const Stats &defenderStats(MonsterAt(i).GetStats());
316                                 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
317                                 ts.SetBad(i, damage);
318                                 break;
319                         }
320                 }
321         } else {
322                 for (int i(0); i < NumHeroes(); ++i) {
323                         if (ts.IsSelected(i)) {
324                                 if (HeroAt(i).Health() > 0) {
325                                         const Stats &defenderStats(HeroAt(i).GetStats());
326                                         Uint16 damage(CalculateDamage(attackerStats, defenderStats));
327                                         ts.SetBad(i, damage);
328                                         hitSome = true;
329                                 } else {
330                                         ts.Unselect(i);
331                                 }
332                         }
333                 }
334                 if (hitSome) return;
335                 for (int i(0); i < NumHeroes(); ++i) {
336                         if (HeroAt(i).Health() > 0) {
337                                 const Stats &defenderStats(HeroAt(i).GetStats());
338                                 Uint16 damage(CalculateDamage(attackerStats, defenderStats));
339                                 ts.SetBad(i, damage);
340                                 break;
341                         }
342                 }
343         }
344 }
345
346 Uint16 BattleState::CalculateDamage(const Stats &attacker, const Stats &defender) const {
347         return attacker.Attack() / 2 - defender.Defense() / 4;
348 }
349
350 void BattleState::ApplyDamage() {
351         if (attackCursor < 0) return;
352         AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
353         TargetSelection &ts(ac.Selection());
354         if (ts.TargetsMonsters()) {
355                 for (int i(0); i < MaxMonsters(); ++i) {
356                         Monster &monster(MonsterAt(i));
357                         if (ts.IsBad(i)) {
358                                 monster.SubtractHealth(ts.GetAmount(i));
359                                 if (monster.Health() == 0) {
360                                         expReward += monster.ExpReward();
361                                         goldReward += monster.GoldReward();
362                                 }
363                         }
364                 }
365         } else {
366                 for (int i(0); i < NumHeroes(); ++i) {
367                         Hero &hero(HeroAt(i));
368                         if (ts.IsBad(i)) {
369                                 hero.SubtractHealth(ts.GetAmount(i));
370                         }
371                 }
372         }
373 }
374
375 AttackChoice &BattleState::CurrentAttackAttackChoice() {
376         return CurrentAttack().GetAttackChoice(*this);
377 }
378
379 void BattleState::ClearAllAttacks() {
380         attackCursor = -1;
381         activeHero = -1;
382         for (int i(0); i < NumHeroes(); ++i) {
383                 HeroAt(i).GetAttackChoice() = AttackChoice(this);
384         }
385         for (int i(0); i < MaxMonsters(); ++i) {
386                 MonsterAt(i).GetAttackChoice() = AttackChoice(this);
387         }
388         capsule.GetAttackChoice() = AttackChoice(this);
389         attackOrder.clear();
390 }
391
392
393 void BattleState::HandleEvents(const Input &input) {
394
395 }
396
397 void BattleState::UpdateWorld(Uint32 deltaT) {
398
399 }
400
401 void BattleState::Render(SDL_Surface *screen) {
402         assert(screen);
403         RenderBackground(screen);
404         RenderMonsters(screen);
405 }
406
407 void BattleState::RenderBackground(SDL_Surface *screen) {
408         assert(screen);
409         // black for now
410         SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
411         SDL_Rect destRect;
412         destRect.x = offset.X();
413         destRect.y = offset.Y();
414         destRect.w = background->w;
415         destRect.h = background->h;
416         SDL_BlitSurface(background, 0, screen, &destRect);
417 }
418
419 void BattleState::RenderMonsters(SDL_Surface *screen) {
420         assert(screen);
421         for (vector<Monster>::size_type i(0), end(monsters.size()); i < end; ++i) {
422                 if (MonsterPositionOccupied(i)) {
423                         if (monsters[i].GetAnimation().Running()) {
424                                 monsters[i].GetAnimation().DrawCenter(screen, monsters[i].Position() + offset);
425                         } else {
426                                 monsters[i].Sprite()->DrawCenter(screen, monsters[i].Position() + offset);
427                         }
428                 }
429         }
430 }
431
432 void BattleState::RenderHeroes(SDL_Surface *screen) {
433         assert(screen);
434         for (int i(0); i < numHeroes; ++i) {
435                 if (heroes[i].GetAnimation().Running()) {
436                         heroes[i].GetAnimation().DrawCenter(screen, heroes[i].Position() + offset);
437                 } else {
438                         int row(heroes[i].Health() > 0 ? 0 : 2);
439                         heroes[i].Sprite()->DrawCenter(screen, heroes[i].Position() + offset, 1, row);
440                 }
441         }
442 }
443
444 void BattleState::RenderCapsule(SDL_Surface *screen) {
445         if (!capsule.Active() || capsule.Health() <= 0) return;
446         if (capsule.GetAnimation().Running()) {
447                 capsule.GetAnimation().DrawCenter(screen, capsule.Position() + offset);
448         } else {
449                 capsule.Sprite()->DrawCenter(screen, capsule.Position() + offset);
450         }
451 }
452
453 void BattleState::RenderHeroTags(SDL_Surface *screen) {
454         assert(screen);
455         int tagHeight(attackTypeMenu.Height());
456         int tagWidth(attackTypeMenu.Width() * 2 + attackTypeMenu.Width() / 2);
457
458         for (int i(0); i < numHeroes; ++i) {
459                 heroTags[i].Render(screen, tagWidth, tagHeight, heroTagPositions[i] + offset, (int)i == activeHero);
460         }
461 }
462
463 void BattleState::RenderSmallHeroTags(SDL_Surface *screen) {
464         assert(screen);
465         int tagHeight(res->normalFont->CharHeight() * 4 + res->smallHeroTagFrame->BorderHeight() * 2);
466         int tagWidth(res->normalFont->CharWidth() * 6 + res->smallHeroTagFrame->BorderWidth() * 2);
467
468         SDL_Rect rect;
469         rect.x = offset.X();
470         rect.y = offset.Y() + Height() - tagHeight;
471         rect.w = Width();
472         rect.h = tagHeight;
473         SDL_FillRect(screen, &rect, SDL_MapRGB(screen->format, 0, 0, 0));
474         rect.y += res->normalFont->CharHeight() / 8;
475         rect.h -= res->normalFont->CharHeight() / 4;
476         SDL_FillRect(screen, &rect, res->heroesBgColor.MapRGB(screen->format));
477
478         for (int i(0); i < numHeroes; ++i) {
479                 smallHeroTags[i].Render(screen, tagWidth, tagHeight, smallHeroTagPositions[i] + offset);
480         }
481 }
482
483 }