From 262279bd2c893ea3065843a4c888acb5ad3e921c Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 7 Dec 2019 21:56:29 +0100 Subject: [PATCH] Support for ending battles when only one side has creatures that are available for battle. --- src/Battling/Library/BattleStatCalculator.cpp | 4 +- src/Battling/Models/Battle.cpp | 17 ++++++ src/Battling/Models/Battle.hpp | 6 ++ src/Battling/Models/BattleSide.cpp | 5 ++ src/Battling/Models/BattleSide.hpp | 19 ++++++ src/Battling/Models/Creature.cpp | 5 ++ src/Battling/Models/Creature.hpp | 1 + tests/Integration/BattleIntegrations.cpp | 58 +++++++++++++++++++ tests/TestLibrary/TestLibrary.cpp | 8 +-- 9 files changed, 117 insertions(+), 6 deletions(-) diff --git a/src/Battling/Library/BattleStatCalculator.cpp b/src/Battling/Library/BattleStatCalculator.cpp index 57456c6..fe29e52 100644 --- a/src/Battling/Library/BattleStatCalculator.cpp +++ b/src/Battling/Library/BattleStatCalculator.cpp @@ -26,14 +26,14 @@ uint32_t CalculateHealthStat(Battling::Creature* creature) { auto level = creature->GetLevel(); auto a = (creature->GetBaseStat(Core::Statistic::Health) + creature->GetStatPotential(Core::Statistic::Health)) * 2 + - floor(sqrt(creature->GetStatExperience(Core::Statistic::Health) / 4)) * level; + floor(sqrt(creature->GetStatExperience(Core::Statistic::Health) / 4.0)) * level; return floor(a / 100) + level + 10; } uint32_t CalculateOtherStat(Battling::Creature* creature, Core::Statistic stat) { auto level = creature->GetLevel(); auto a = (creature->GetBaseStat(stat) + creature->GetStatPotential(stat)) * 2 + - floor(sqrt(creature->GetStatExperience(stat) / 4)) * level; + floor(sqrt(creature->GetStatExperience(stat) / 4.0)) * level; return floor(a / 100) + 10; } diff --git a/src/Battling/Models/Battle.cpp b/src/Battling/Models/Battle.cpp index a80772f..0120c36 100644 --- a/src/Battling/Models/Battle.cpp +++ b/src/Battling/Models/Battle.cpp @@ -97,3 +97,20 @@ bool Battle::CanSlotBeFilled(uint8_t side, uint8_t index) const { } return false; } + +void Battle::ValidateBattleState() { + bool survivingSideExists = false; + uint8_t result = 0; + for (uint8_t i = 0; i < _sides.size(); i++){ + auto side = _sides[i]; + if (!side->IsDefeated()){ + if (survivingSideExists){ + return; + } + survivingSideExists = true; + result = i; + } + } + this->_battleResult = result; + this->_hasEnded = true; +} diff --git a/src/Battling/Models/Battle.hpp b/src/Battling/Models/Battle.hpp index aa1d3a1..5fd7ac1 100644 --- a/src/Battling/Models/Battle.hpp +++ b/src/Battling/Models/Battle.hpp @@ -18,6 +18,8 @@ namespace CreatureLib::Battling { std::vector _sides; Core::Random _random; ChoiceQueue* _currentTurnQueue = nullptr; + bool _hasEnded = false; + uint8_t _battleResult = 0; ScriptSet _volatile; @@ -58,6 +60,10 @@ namespace CreatureLib::Battling { bool CanSlotBeFilled(uint8_t side, uint8_t index) const; void GetActiveScripts(std::vector& scripts) override; + + void ValidateBattleState(); + inline bool HasEnded() const { return _hasEnded; } + inline uint8_t GetResult() const { return _battleResult; } }; } diff --git a/src/Battling/Models/BattleSide.cpp b/src/Battling/Models/BattleSide.cpp index 1a6881a..b983acf 100644 --- a/src/Battling/Models/BattleSide.cpp +++ b/src/Battling/Models/BattleSide.cpp @@ -37,8 +37,13 @@ void BattleSide::SetChoice(BaseTurnChoice* choice) { } void BattleSide::SetCreature(Creature* creature, uint8_t index) { + auto old = _creatures[index]; + if (old != nullptr){ + old->SetOnBattleField(false); + } _creatures[index] = creature; creature->SetBattleData(_battle, this); + creature->SetOnBattleField(true); } bool BattleSide::CreatureOnSide(const Creature* creature) const { diff --git a/src/Battling/Models/BattleSide.hpp b/src/Battling/Models/BattleSide.hpp index 9cca52f..6f02c51 100644 --- a/src/Battling/Models/BattleSide.hpp +++ b/src/Battling/Models/BattleSide.hpp @@ -11,6 +11,7 @@ namespace CreatureLib::Battling { uint8_t _creaturesPerSide; std::vector _creatures; std::vector _choices; + std::vector _fillableSlots; uint8_t _choicesSet = 0; ScriptSet _volatile; Battle* _battle; @@ -20,9 +21,11 @@ namespace CreatureLib::Battling { : _index(index), _creaturesPerSide(creaturesPerSide), _battle(battle) { _creatures = std::vector(creaturesPerSide); _choices = std::vector(creaturesPerSide); + _fillableSlots = std::vector(creaturesPerSide); for (size_t i = 0; i < creaturesPerSide; i++) { _creatures[i] = nullptr; _choices[i] = nullptr; + _fillableSlots[i] = true; } ResetChoices(); } @@ -43,6 +46,22 @@ namespace CreatureLib::Battling { bool CreatureOnSide(const Creature* creature) const; void GetActiveScripts(std::vector& scripts) final; + + void MarkSlotAsUnfillable(Creature* creature){ + for (uint8_t i = 0; i < _creaturesPerSide; i++){ + if (_creatures[i] == creature){ + _fillableSlots[i] = false; + return; + } + } + } + + bool IsDefeated(){ + for (auto b: _fillableSlots){ + if (b) return false; + } + return true; + } }; } diff --git a/src/Battling/Models/Creature.cpp b/src/Battling/Models/Creature.cpp index 2db3a29..aeadebc 100644 --- a/src/Battling/Models/Creature.cpp +++ b/src/Battling/Models/Creature.cpp @@ -98,6 +98,11 @@ void Battling::Creature::Damage(uint32_t damage, Battling::DamageSource source) } // HOOK: On Damage __CurrentHealth -= damage; + + if (IsFainted()){ + _side->MarkSlotAsUnfillable(this); + _battle->ValidateBattleState(); + } } void Battling::Creature::OverrideActiveTalent(const std::string& talent) { diff --git a/src/Battling/Models/Creature.hpp b/src/Battling/Models/Creature.hpp index 6fa0615..4631cb9 100644 --- a/src/Battling/Models/Creature.hpp +++ b/src/Battling/Models/Creature.hpp @@ -69,6 +69,7 @@ namespace CreatureLib::Battling { void SetBattleData(Battle* battle, BattleSide* side); Battle* GetBattle() const; BattleSide* GetBattleSide() const; + void SetOnBattleField(bool value) {_onBattleField = value;} bool IsOnBattleField() const { return _onBattleField; } const std::string& GetNickname() const; diff --git a/tests/Integration/BattleIntegrations.cpp b/tests/Integration/BattleIntegrations.cpp index 124cc79..b3d2597 100644 --- a/tests/Integration/BattleIntegrations.cpp +++ b/tests/Integration/BattleIntegrations.cpp @@ -50,4 +50,62 @@ TEST_CASE("Use damaging move", "[Integrations]") { REQUIRE(c2->GetCurrentHealth() < c2->GetBoostedStat(Statistic::Health)); } +TEST_CASE("Finish battle when all battle of one side have fainted", "[Integrations]") { + auto library = TestLibrary::Get(); + auto c1 = CreateCreature(library, "testSpecies1", 50).WithAttack("standard", AttackLearnMethod::Unknown)->Create(); + CreatureParty party1{c1}; + auto battleParty1 = BattleParty(&party1, {CreatureIndex(0, 0)}); + auto c2 = CreateCreature(library, "testSpecies1", 50).WithAttack("standard", AttackLearnMethod::Unknown)->Create(); + CreatureParty party2{c2}; + auto battleParty2 = BattleParty(&party2, {CreatureIndex(1, 0)}); + + auto battle = Battle(library, {battleParty1, battleParty2}); + + REQUIRE_FALSE(battle.HasEnded()); + + battle.SwitchCreature(0, 0, c1); + battle.SwitchCreature(1, 0, c2); + + REQUIRE_FALSE(battle.HasEnded()); + + battle.TrySetChoice(new AttackTurnChoice(c1, c1->GetAttacks()[0], CreatureIndex(1, 0))); + battle.TrySetChoice(new PassTurnChoice(c2)); + + REQUIRE_FALSE(battle.HasEnded()); + + REQUIRE(c2->GetCurrentHealth() < c2->GetBoostedStat(Statistic::Health)); + + c2->Damage(c2->GetCurrentHealth(), DamageSource::AttackDamage); + + REQUIRE(battle.HasEnded()); + REQUIRE(battle.GetResult() == 0); +} + +TEST_CASE("When creature is dealt enough damage, faint it and mark battle as ended", "[Integrations]") { + auto library = TestLibrary::Get(); + auto c1 = CreateCreature(library, "testSpecies1", 100).WithAttack("standard", AttackLearnMethod::Unknown)->Create(); + CreatureParty party1{c1}; + auto battleParty1 = BattleParty(&party1, {CreatureIndex(0, 0)}); + auto c2 = CreateCreature(library, "testSpecies1", 1).WithAttack("standard", AttackLearnMethod::Unknown)->Create(); + CreatureParty party2{c2}; + auto battleParty2 = BattleParty(&party2, {CreatureIndex(1, 0)}); + + auto battle = Battle(library, {battleParty1, battleParty2}); + + REQUIRE_FALSE(battle.HasEnded()); + + battle.SwitchCreature(0, 0, c1); + battle.SwitchCreature(1, 0, c2); + + REQUIRE_FALSE(battle.HasEnded()); + + battle.TrySetChoice(new AttackTurnChoice(c1, c1->GetAttacks()[0], CreatureIndex(1, 0))); + battle.TrySetChoice(new PassTurnChoice(c2)); + + REQUIRE(battle.HasEnded()); + REQUIRE(battle.GetResult() == 0); +} + + + #endif \ No newline at end of file diff --git a/tests/TestLibrary/TestLibrary.cpp b/tests/TestLibrary/TestLibrary.cpp index 9217541..9e57f55 100644 --- a/tests/TestLibrary/TestLibrary.cpp +++ b/tests/TestLibrary/TestLibrary.cpp @@ -31,13 +31,13 @@ SpeciesLibrary *TestLibrary::BuildSpeciesLibrary() { AttackLibrary *TestLibrary::BuildAttackLibrary() { auto l = new AttackLibrary(); - l->LoadAttack("standard", new AttackData("standard", "normal", AttackCategory::Physical, 20, 100, 30, + l->LoadAttack("standard", new AttackData("standard", "normal", AttackCategory::Physical, 50, 100, 30, AttackTarget::AdjacentOpponent, 0, {})); - l->LoadAttack("highPriority", new AttackData("highPriority", "normal", AttackCategory::Physical, 20, 100, 30, + l->LoadAttack("highPriority", new AttackData("highPriority", "normal", AttackCategory::Physical, 50, 100, 30, AttackTarget::AdjacentOpponent, 1, {})); - l->LoadAttack("higherPriority", new AttackData("higherPriority", "normal", AttackCategory::Physical, 20, 100, 30, + l->LoadAttack("higherPriority", new AttackData("higherPriority", "normal", AttackCategory::Physical, 50, 100, 30, AttackTarget::AdjacentOpponent, 2, {})); - l->LoadAttack("lowPriority", new AttackData("lowPriority", "normal", AttackCategory::Physical, 20, 100, 30, + l->LoadAttack("lowPriority", new AttackData("lowPriority", "normal", AttackCategory::Physical, 50, 100, 30, AttackTarget::AdjacentOpponent, -1, {})); return l; }