]> git.localhorst.tv Git - l2e.git/blobdiff - src/battle/Battle.cpp
extracted battle logic into a class
[l2e.git] / src / battle / Battle.cpp
diff --git a/src/battle/Battle.cpp b/src/battle/Battle.cpp
new file mode 100644 (file)
index 0000000..7bfa940
--- /dev/null
@@ -0,0 +1,385 @@
+#include "Battle.h"
+
+#include "AttackChoice.h"
+#include "PartyLayout.h"
+#include "TargetSelection.h"
+#include "../common/Stats.h"
+
+#include <cassert>
+#include <stdexcept>
+
+using common::Stats;
+
+
+namespace battle {
+
+Battle::Battle(
+               const PartyLayout *heroesLayout,
+               const PartyLayout *monstersLayout)
+: heroesLayout(heroesLayout)
+, monstersLayout(monstersLayout)
+, activeHero(-1)
+, attackCursor(-1)
+, expReward(0)
+, goldReward(0) {
+       if (!heroesLayout) {
+               throw std::invalid_argument("construct battle without heroes layout");
+       }
+       if (!monstersLayout) {
+               throw std::invalid_argument("construct battle without monsters layout");
+       }
+       heroes.reserve(4);
+       monsters.resize(monstersLayout->NumPositions());
+}
+
+
+void Battle::AddHero(const Hero &h) {
+       if (NumHeroes() == MaxHeroes()) {
+               throw std::overflow_error("max heroes breached");
+       }
+       heroes.push_back(h);
+       heroes.back().GetAttackChoice() = AttackChoice(this);
+}
+
+void Battle::AddMonster(const Monster &m) {
+       for (int i = 0; i < MaxMonsters(); ++i) {
+               if (!MonsterPositionOccupied(i)) {
+                       MonsterAt(i) = m;
+                       MonsterAt(i).GetAttackChoice() = AttackChoice(this);
+                       return;
+               }
+       }
+       throw std::overflow_error("all monster positions occupied");
+}
+
+void Battle::SetCapsule(const Capsule &c) {
+       capsule = c;
+}
+
+
+int Battle::NumHeroes() const {
+       return heroes.size();
+}
+
+int Battle::MaxHeroes() const {
+       return heroesLayout->NumPositions() - 1;
+}
+
+int Battle::NumMonsters() const {
+       return monsters.size();
+}
+
+int Battle::MaxMonsters() const {
+       return monstersLayout->NumPositions();
+}
+
+bool Battle::HasCapsule() const {
+       return capsule.Active();
+}
+
+
+Hero &Battle::HeroAt(int index) {
+       assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
+       return heroes[index];
+}
+
+const Hero &Battle::HeroAt(int index) const {
+       assert(index >= 0 && index < NumHeroes() && "hero index out of bounds");
+       return heroes[index];
+}
+
+
+Monster &Battle::MonsterAt(int index) {
+       assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
+       return monsters[index];
+}
+
+const Monster &Battle::MonsterAt(int index) const {
+       assert(index >= 0 && index < NumMonsters() && "monster index out of bounds");
+       return monsters[index];
+}
+
+bool Battle::HeroPositionOccupied(int index) const {
+       return index >= 0 && index < NumHeroes();
+}
+
+bool Battle::HeroAlive(int index) const {
+       return HeroPositionOccupied(index) && HeroAt(index).Health() > 0;
+}
+
+
+bool Battle::MonsterPositionOccupied(int index) const {
+       return MonsterAlive(index);
+}
+
+bool Battle::MonsterAlive(int index) const {
+       return index >= 0 && index < NumMonsters() && MonsterAt(index).Health() > 0;
+}
+
+bool Battle::CapsuleAlive() const {
+       return capsule.Active() && capsule.Health() > 0;
+}
+
+
+void Battle::NextHero() {
+       ++activeHero;
+       while (activeHero < NumHeroes() && HeroAt(activeHero).Health() == 0) {
+               ++activeHero;
+       }
+}
+
+void Battle::PreviousHero() {
+       --activeHero;
+       while (activeHero >= 0 && HeroAt(activeHero).Health() == 0) {
+               --activeHero;
+       }
+}
+
+void Battle::SwapHeroes(int lhs, int rhs) {
+       if (lhs < 0 || lhs >= NumHeroes() || rhs < 0 || rhs >= NumHeroes() || lhs == rhs) return;
+       std::swap(HeroAt(lhs), HeroAt(rhs));
+}
+
+bool Battle::HasChosenAttackType() const {
+       return ActiveHero().GetAttackChoice().GetType() != AttackChoice::UNDECIDED;
+}
+
+bool Battle::AttackSelectionDone() const {
+       return activeHero >= NumHeroes();
+}
+
+
+class OrderCompare {
+public:
+       OrderCompare(Battle *battle) : battle(battle) { }
+       bool operator ()(const Battle::Order &lhs, const Battle::Order &rhs) {
+               return lhs.GetStats(*battle).Agility() > rhs.GetStats(*battle).Agility();
+       }
+private:
+       Battle *battle;
+};
+
+
+void Battle::CalculateAttackOrder() {
+       attackOrder.reserve(NumMonsters() + NumHeroes() + 1);
+       for (int i(0); i < NumHeroes(); ++i) {
+               attackOrder.push_back(Order(Order::HERO, i));
+       }
+       for (std::vector<Monster>::size_type i(0), end(NumMonsters()); i < end; ++i) {
+               attackOrder.push_back(Order(Order::MONSTER, i));
+               MonsterAt(i).GetAttackChoice() = AttackChoice(this);
+       }
+       if (GetCapsule().Active() && GetCapsule().Health() > 0) {
+               attackOrder.push_back(Order(Order::CAPSULE));
+       }
+       std::sort(attackOrder.begin(), attackOrder.end(), OrderCompare(this));
+}
+
+void Battle::NextAttack() {
+       if (Victory() || Defeat()) {
+               attackCursor = attackOrder.size();
+               return;
+       }
+       ++attackCursor;
+       while (attackCursor < int(attackOrder.size())) {
+               if (attackOrder[attackCursor].IsMonster()) {
+                       if (MonsterAlive(attackOrder[attackCursor].index)) break;
+               } else if (attackOrder[attackCursor].IsHero()) {
+                       if (HeroAlive(attackOrder[attackCursor].index)) break;
+               } else {
+                       if (CapsuleAlive()) break;
+               }
+               ++attackCursor;
+       }
+}
+
+bool Battle::AttacksFinished() const {
+       return attackCursor >= int(attackOrder.size())
+                       || Victory() || Defeat();
+}
+
+void Battle::CalculateDamage() {
+       if (CurrentAttack().IsMonster()) {
+               DecideMonsterAttack(MonsterAt(CurrentAttack().index));
+       } else if (CurrentAttack().IsCapsule()) {
+               DecideCapsuleAttack();
+       }
+       AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
+       if (ac.GetType() == AttackChoice::DEFEND) return;
+       TargetSelection &ts(ac.Selection());
+
+       const Stats &attackerStats = CurrentAttack().GetStats(*this);
+       CalculateDamage(attackerStats, ts);
+}
+
+AttackChoice &Battle::Order::GetAttackChoice(Battle &b) const {
+       switch (by) {
+               case HERO:
+                       return b.HeroAt(index).GetAttackChoice();
+               case CAPSULE:
+                       return b.GetCapsule().GetAttackChoice();
+               case MONSTER:
+                       return b.MonsterAt(index).GetAttackChoice();
+               default:
+                       throw std::runtime_error("invalid case in Battle::Order::GetAttackChoice()");
+       }
+}
+
+Stats &Battle::Order::GetStats(Battle &b) const {
+       switch (by) {
+               case HERO:
+                       return b.HeroAt(index).GetStats();
+               case CAPSULE:
+                       return b.GetCapsule().GetStats();
+               case MONSTER:
+                       return b.MonsterAt(index).GetStats();
+               default:
+                       throw std::runtime_error("invalid case in BttleStats::Order::GetAttackChoice()");
+       }
+}
+
+void Battle::ApplyDamage() {
+       if (attackCursor < 0) return;
+       AttackChoice &ac = CurrentAttack().GetAttackChoice(*this);
+       TargetSelection &ts(ac.Selection());
+       if (ts.TargetsMonsters()) {
+               for (int i(0); i < NumMonsters(); ++i) {
+                       Monster &monster(MonsterAt(i));
+                       if (ts.IsBad(i)) {
+                               monster.SubtractHealth(ts.GetAmount(i));
+                               if (monster.Health() == 0) {
+                                       expReward += monster.ExpReward();
+                                       goldReward += monster.GoldReward();
+                               }
+                       }
+               }
+       } else {
+               for (int i(0); i < NumHeroes(); ++i) {
+                       Hero &hero(HeroAt(i));
+                       if (ts.IsBad(i)) {
+                               hero.SubtractHealth(ts.GetAmount(i));
+                       }
+               }
+       }
+}
+
+const Battle::Order &Battle::CurrentAttack() const {
+       assert(attackCursor >= 0 && attackCursor < int(attackOrder.size()));
+       return attackOrder[attackCursor];
+}
+
+AttackChoice &Battle::CurrentAttackAttackChoice() {
+       return CurrentAttack().GetAttackChoice(*this);
+}
+
+void Battle::ClearAllAttacks() {
+       attackCursor = -1;
+       activeHero = -1;
+       for (int i(0); i < NumHeroes(); ++i) {
+               HeroAt(i).GetAttackChoice() = AttackChoice(this);
+       }
+       for (int i(0); i < NumMonsters(); ++i) {
+               MonsterAt(i).GetAttackChoice() = AttackChoice(this);
+       }
+       GetCapsule().GetAttackChoice() = AttackChoice(this);
+       attackOrder.clear();
+}
+
+
+void Battle::DecideMonsterAttack(Monster &m) {
+       AttackChoice &ac(m.GetAttackChoice());
+       TargetSelection &ts(ac.Selection());
+       ac.Reset();
+       int target(rand() % NumHeroes());
+       while (!HeroAlive(target)) {
+               target = rand() % NumHeroes();
+       }
+       ac.SetType(AttackChoice::SWORD);
+       ts.SelectHeroes();
+       ts.SetSingle();
+       ts.Select(target);
+}
+
+void Battle::DecideCapsuleAttack() {
+       AttackChoice &ac(GetCapsule().GetAttackChoice());
+       TargetSelection &ts(ac.Selection());
+       ac.Reset();
+       int target(rand() % NumMonsters());
+       while (!MonsterAlive(target)) {
+               target = rand() % NumMonsters();
+       }
+       ac.SetType(AttackChoice::SWORD);
+       ts.SelectMonsters();
+       ts.SetSingle();
+       ts.Select(target);
+}
+
+void Battle::CalculateDamage(const Stats &attackerStats, TargetSelection &ts) const {
+       bool hitSome(false);
+       if (ts.TargetsMonsters()) {
+               for (int i(0); i < MaxMonsters(); ++i) {
+                       if (ts.IsSelected(i)) {
+                               if (MonsterAt(i).Health() > 0) {
+                                       const Stats &defenderStats(MonsterAt(i).GetStats());
+                                       Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+                                       ts.SetBad(i, damage);
+                                       hitSome = true;
+                               } else {
+                                       ts.Unselect(i);
+                               }
+                       }
+               }
+               if (hitSome) return;
+               for (int i(0); i < MaxMonsters(); ++i) {
+                       if (MonsterAt(i).Health() > 0) {
+                               const Stats &defenderStats(MonsterAt(i).GetStats());
+                               Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+                               ts.SetBad(i, damage);
+                               break;
+                       }
+               }
+       } else {
+               for (int i(0); i < NumHeroes(); ++i) {
+                       if (ts.IsSelected(i)) {
+                               if (HeroAt(i).Health() > 0) {
+                                       const Stats &defenderStats(HeroAt(i).GetStats());
+                                       Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+                                       ts.SetBad(i, damage);
+                                       hitSome = true;
+                               } else {
+                                       ts.Unselect(i);
+                               }
+                       }
+               }
+               if (hitSome) return;
+               for (int i(0); i < NumHeroes(); ++i) {
+                       if (HeroAt(i).Health() > 0) {
+                               const Stats &defenderStats(HeroAt(i).GetStats());
+                               Uint16 damage(CalculateDamage(attackerStats, defenderStats));
+                               ts.SetBad(i, damage);
+                               break;
+                       }
+               }
+       }
+}
+
+Uint16 Battle::CalculateDamage(const Stats &attacker, const Stats &defender) const {
+       return attacker.Attack() / 2 - defender.Defense() / 4;
+}
+
+
+bool Battle::Victory() const {
+       for (int i(0); i < NumMonsters(); ++i) {
+               if (MonsterAlive(i)) return false;
+       }
+       return true;
+}
+
+bool Battle::Defeat() const {
+       for (int i(0); i < NumHeroes(); ++i) {
+               if (HeroAlive(i)) return false;
+       }
+       return true;
+}
+
+}