X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fbattle%2FBattle.cpp;fp=src%2Fbattle%2FBattle.cpp;h=7bfa940461e12cff44f90400d94327b58fc00e00;hb=087783315ac5955c17bb3b051c9351f321653df6;hp=0000000000000000000000000000000000000000;hpb=eb2ad5ffd08128d31af32f3929a3295fcfa251e9;p=l2e.git diff --git a/src/battle/Battle.cpp b/src/battle/Battle.cpp new file mode 100644 index 0000000..7bfa940 --- /dev/null +++ b/src/battle/Battle.cpp @@ -0,0 +1,385 @@ +#include "Battle.h" + +#include "AttackChoice.h" +#include "PartyLayout.h" +#include "TargetSelection.h" +#include "../common/Stats.h" + +#include +#include + +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::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; +} + +}