Implements running from battle.
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Deukhoofd 2019-12-15 11:52:10 +01:00
parent 0fad615050
commit 6ba708ad12
Signed by: Deukhoofd
GPG Key ID: ADF2E9256009EDCE
16 changed files with 160 additions and 54 deletions

View File

@ -44,9 +44,9 @@ void TurnHandler::ExecuteChoice(BaseTurnChoice* choice) {
case TurnChoiceKind::Pass: throw NotReachableException(); case TurnChoiceKind::Pass: throw NotReachableException();
case TurnChoiceKind::Attack: return ExecuteAttackChoice(dynamic_cast<AttackTurnChoice*>(choice)); case TurnChoiceKind::Attack: return ExecuteAttackChoice(dynamic_cast<AttackTurnChoice*>(choice));
case TurnChoiceKind::Switch: return ExecuteSwitchChoice(dynamic_cast<SwitchTurnChoice*>(choice)); case TurnChoiceKind::Switch: return ExecuteSwitchChoice(dynamic_cast<SwitchTurnChoice*>(choice));
case TurnChoiceKind::Flee: return ExecuteFleeChoice(dynamic_cast<FleeTurnChoice*>(choice));
case TurnChoiceKind::Item: case TurnChoiceKind::Item: throw NotImplementedException();
case TurnChoiceKind::RunAway: throw NotImplementedException();
} }
} }
@ -138,7 +138,7 @@ void TurnHandler::HandleAttackForTarget(ExecutingAttack* attack, Creature* targe
auto hitType = hit->GetType(); auto hitType = hit->GetType();
HOOK(ChangeAttackType, targetSource, attack, target, hitIndex, hitType); HOOK(ChangeAttackType, targetSource, attack, target, hitIndex, hitType);
hit->SetEffectiveness(library->GetTypeLibrary()->GetEffectiveness(hitType, target->GetTypes())); hit->SetEffectiveness(library->GetTypeLibrary()->GetEffectiveness(hitType, target->GetTypes()));
hit->SetCritical(library->GetCriticalLibrary()->IsCritical(attack, target, hitIndex)); hit->SetCritical(library->GetMiscLibrary()->IsCritical(attack, target, hitIndex));
hit->SetBasePower(dmgLibrary->GetBasePower(attack, target, hitIndex)); hit->SetBasePower(dmgLibrary->GetBasePower(attack, target, hitIndex));
hit->SetDamage(dmgLibrary->GetDamage(attack, target, hitIndex)); hit->SetDamage(dmgLibrary->GetDamage(attack, target, hitIndex));
@ -180,4 +180,18 @@ void TurnHandler::ExecuteSwitchChoice(CreatureLib::Battling::SwitchTurnChoice* c
auto userSide = user->GetBattleSide(); auto userSide = user->GetBattleSide();
auto userIndex = userSide->GetCreatureIndex(user); auto userIndex = userSide->GetCreatureIndex(user);
userSide->SetCreature(choice->GetNewCreature(), userIndex); userSide->SetCreature(choice->GetNewCreature(), userIndex);
} }
void TurnHandler::ExecuteFleeChoice(FleeTurnChoice* choice) {
auto user = choice->GetUser();
auto battle = user->GetBattle();
if (!battle->CanFlee()) {
return;
}
// TODO: If any of the creatures on the users side has a script that prevents it from running, block.
// TODO: If any of the creatures on any other side has a script that prevents this side from running, block.
if (battle->GetLibrary()->GetMiscLibrary()->CanFlee(choice)) {
user->GetBattleSide()->MarkAsFled();
battle->ValidateBattleState();
}
}

View File

@ -3,6 +3,7 @@
#include "../Models/ExecutingAttack.hpp" #include "../Models/ExecutingAttack.hpp"
#include "../TurnChoices/AttackTurnChoice.hpp" #include "../TurnChoices/AttackTurnChoice.hpp"
#include "../TurnChoices/FleeTurnChoice.hpp"
#include "../TurnChoices/SwitchTurnChoice.hpp" #include "../TurnChoices/SwitchTurnChoice.hpp"
#include "ChoiceQueue.hpp" #include "ChoiceQueue.hpp"
@ -17,6 +18,7 @@ namespace CreatureLib::Battling {
ExecutingAttack::TargetData* targetData); ExecutingAttack::TargetData* targetData);
static void ExecuteSwitchChoice(SwitchTurnChoice* choice); static void ExecuteSwitchChoice(SwitchTurnChoice* choice);
static void ExecuteFleeChoice(FleeTurnChoice* choice);
public: public:
static void RunTurn(Battle* battle, ChoiceQueue* queue); static void RunTurn(Battle* battle, ChoiceQueue* queue);

View File

@ -4,18 +4,18 @@
using namespace CreatureLib::Battling; using namespace CreatureLib::Battling;
BattleLibrary::BattleLibrary(CreatureLib::Library::DataLibrary* staticLib, BattleStatCalculator* statCalculator, BattleLibrary::BattleLibrary(CreatureLib::Library::DataLibrary* staticLib, BattleStatCalculator* statCalculator,
DamageLibrary* damageLibrary, CriticalLibrary* criticalLibrary, DamageLibrary* damageLibrary, ExperienceLibrary* experienceLibrary,
ExperienceLibrary* experienceLibrary, ScriptResolver* scriptResolver) ScriptResolver* scriptResolver, MiscLibrary* miscLibrary)
: _staticLib(staticLib), _statCalculator(statCalculator), _damageLibrary(damageLibrary), : _staticLib(staticLib), _statCalculator(statCalculator), _damageLibrary(damageLibrary),
_criticalLibrary(criticalLibrary), _experienceLibrary(experienceLibrary), _scriptResolver(scriptResolver) {} _experienceLibrary(experienceLibrary), _scriptResolver(scriptResolver), _miscLibrary(miscLibrary) {}
BattleLibrary::~BattleLibrary() { BattleLibrary::~BattleLibrary() {
delete _staticLib; delete _staticLib;
delete _statCalculator; delete _statCalculator;
delete _damageLibrary; delete _damageLibrary;
delete _criticalLibrary;
delete _experienceLibrary; delete _experienceLibrary;
delete _scriptResolver; delete _scriptResolver;
delete _miscLibrary;
} }
const CreatureLib::Library::LibrarySettings& BattleLibrary::GetSettings() const { return _staticLib->GetSettings(); } const CreatureLib::Library::LibrarySettings& BattleLibrary::GetSettings() const { return _staticLib->GetSettings(); }
@ -36,7 +36,7 @@ const CreatureLib::Library::TypeLibrary* BattleLibrary::GetTypeLibrary() const {
const DamageLibrary* BattleLibrary::GetDamageLibrary() const { return _damageLibrary; } const DamageLibrary* BattleLibrary::GetDamageLibrary() const { return _damageLibrary; }
const CriticalLibrary* BattleLibrary::GetCriticalLibrary() const { return _criticalLibrary; } const MiscLibrary* BattleLibrary::GetMiscLibrary() const { return _miscLibrary; }
Script* BattleLibrary::LoadScript(ScriptResolver::ScriptCategory category, const std::string& scriptName) const { Script* BattleLibrary::LoadScript(ScriptResolver::ScriptCategory category, const std::string& scriptName) const {
return _scriptResolver->LoadScript(category, scriptName); return _scriptResolver->LoadScript(category, scriptName);

View File

@ -4,23 +4,23 @@
#include "../../Library/DataLibrary.hpp" #include "../../Library/DataLibrary.hpp"
#include "../ScriptHandling/ScriptResolver.hpp" #include "../ScriptHandling/ScriptResolver.hpp"
#include "BattleStatCalculator.hpp" #include "BattleStatCalculator.hpp"
#include "CriticalLibrary.hpp"
#include "DamageLibrary.hpp" #include "DamageLibrary.hpp"
#include "ExperienceLibrary.hpp" #include "ExperienceLibrary.hpp"
#include "MiscLibrary.hpp"
namespace CreatureLib::Battling { namespace CreatureLib::Battling {
class BattleLibrary { class BattleLibrary {
const Library::DataLibrary* _staticLib = nullptr; const Library::DataLibrary* _staticLib = nullptr;
BattleStatCalculator* _statCalculator = nullptr; BattleStatCalculator* _statCalculator = nullptr;
DamageLibrary* _damageLibrary = nullptr; DamageLibrary* _damageLibrary = nullptr;
CriticalLibrary* _criticalLibrary = nullptr;
ExperienceLibrary* _experienceLibrary = nullptr; ExperienceLibrary* _experienceLibrary = nullptr;
ScriptResolver* _scriptResolver = nullptr; ScriptResolver* _scriptResolver = nullptr;
MiscLibrary* _miscLibrary = nullptr;
public: public:
BattleLibrary(Library::DataLibrary* staticLib, BattleStatCalculator* statCalculator, BattleLibrary(Library::DataLibrary* staticLib, BattleStatCalculator* statCalculator,
DamageLibrary* damageLibrary, CriticalLibrary* criticalLibrary, DamageLibrary* damageLibrary, ExperienceLibrary* experienceLibrary,
ExperienceLibrary* experienceLibrary, ScriptResolver* scriptResolver); ScriptResolver* scriptResolver, MiscLibrary* miscLibrary);
~BattleLibrary(); ~BattleLibrary();
[[nodiscard]] const Library::LibrarySettings& GetSettings() const; [[nodiscard]] const Library::LibrarySettings& GetSettings() const;
@ -34,7 +34,7 @@ namespace CreatureLib::Battling {
[[nodiscard]] const BattleStatCalculator* GetStatCalculator() const; [[nodiscard]] const BattleStatCalculator* GetStatCalculator() const;
[[nodiscard]] const DamageLibrary* GetDamageLibrary() const; [[nodiscard]] const DamageLibrary* GetDamageLibrary() const;
[[nodiscard]] const CriticalLibrary* GetCriticalLibrary() const; [[nodiscard]] const MiscLibrary* GetMiscLibrary() const;
[[nodiscard]] const ExperienceLibrary* GetExperienceLibrary() const { return _experienceLibrary; } [[nodiscard]] const ExperienceLibrary* GetExperienceLibrary() const { return _experienceLibrary; }
[[nodiscard]] Script* LoadScript(ScriptResolver::ScriptCategory category, const std::string& scriptName) const; [[nodiscard]] Script* LoadScript(ScriptResolver::ScriptCategory category, const std::string& scriptName) const;

View File

@ -1,9 +0,0 @@
#include "CriticalLibrary.hpp"
#include "../Models/Battle.hpp"
bool CreatureLib::Battling::CriticalLibrary::IsCritical(CreatureLib::Battling::ExecutingAttack* attack,
CreatureLib::Battling::Creature* target, uint8_t hit) const {
auto rand = target->GetBattle()->GetRandom();
// HOOK: Increase chance for critical hits.
return rand.Get(10) <= 0;
}

View File

@ -1,14 +0,0 @@
#ifndef CREATURELIB_CRITICALLIBRARY_HPP
#define CREATURELIB_CRITICALLIBRARY_HPP
#include "../Models/ExecutingAttack.hpp"
namespace CreatureLib::Battling {
class CriticalLibrary {
public:
virtual ~CriticalLibrary() = default;
virtual bool IsCritical(ExecutingAttack* attack, Creature* target, uint8_t hit) const;
};
}
#endif // CREATURELIB_CRITICALLIBRARY_HPP

View File

@ -0,0 +1,10 @@
#include "MiscLibrary.hpp"
#include "../Models/Battle.hpp"
bool CreatureLib::Battling::MiscLibrary::IsCritical(CreatureLib::Battling::ExecutingAttack* attack,
CreatureLib::Battling::Creature* target, uint8_t hit) const {
auto rand = target->GetBattle()->GetRandom();
// HOOK: Increase chance for critical hits.
return rand.Get(10) <= 0;
}
bool CreatureLib::Battling::MiscLibrary::CanFlee(FleeTurnChoice* switchChoice) const { return true; }

View File

@ -0,0 +1,16 @@
#ifndef CREATURELIB_MISCLIBRARY_HPP
#define CREATURELIB_MISCLIBRARY_HPP
#include "../Models/ExecutingAttack.hpp"
#include "../TurnChoices/FleeTurnChoice.hpp"
namespace CreatureLib::Battling {
class MiscLibrary {
public:
virtual ~MiscLibrary() = default;
virtual bool IsCritical(ExecutingAttack* attack, Creature* target, uint8_t hit) const;
virtual bool CanFlee(FleeTurnChoice* switchChoice) const;
};
}
#endif // CREATURELIB_MISCLIBRARY_HPP

View File

@ -100,17 +100,22 @@ bool Battle::CanSlotBeFilled(uint8_t side, uint8_t index) const {
void Battle::ValidateBattleState() { void Battle::ValidateBattleState() {
bool survivingSideExists = false; bool survivingSideExists = false;
uint8_t result = 0; uint8_t winningSide = 0;
for (uint8_t i = 0; i < _sides.size(); i++){ for (uint8_t i = 0; i < _sides.size(); i++) {
auto side = _sides[i]; auto side = _sides[i];
if (!side->IsDefeated()){ if (side->HasFled()) {
if (survivingSideExists){ this->_battleResult = BattleResult::Inconclusive();
this->_hasEnded = true;
return;
}
if (!side->IsDefeated()) {
if (survivingSideExists) {
return; return;
} }
survivingSideExists = true; survivingSideExists = true;
result = i; winningSide = i;
} }
} }
this->_battleResult = result; this->_battleResult = BattleResult::Conclusive(winningSide);
this->_hasEnded = true; this->_hasEnded = true;
} }

View File

@ -6,6 +6,7 @@
#include "../Library/BattleLibrary.hpp" #include "../Library/BattleLibrary.hpp"
#include "../TurnChoices/BaseTurnChoice.hpp" #include "../TurnChoices/BaseTurnChoice.hpp"
#include "BattleParty.hpp" #include "BattleParty.hpp"
#include "BattleResult.hpp"
#include "BattleSide.hpp" #include "BattleSide.hpp"
#include "CreatureIndex.hpp" #include "CreatureIndex.hpp"
@ -13,20 +14,22 @@ namespace CreatureLib::Battling {
class Battle : public ScriptSource { class Battle : public ScriptSource {
const BattleLibrary* _library; const BattleLibrary* _library;
std::vector<BattleParty> _parties; std::vector<BattleParty> _parties;
bool _canFlee;
uint8_t _numberOfSides; uint8_t _numberOfSides;
uint8_t _creaturesPerSide; uint8_t _creaturesPerSide;
std::vector<BattleSide*> _sides; std::vector<BattleSide*> _sides;
Core::Random _random; Core::Random _random;
ChoiceQueue* _currentTurnQueue = nullptr; ChoiceQueue* _currentTurnQueue = nullptr;
bool _hasEnded = false; bool _hasEnded = false;
uint8_t _battleResult = 0; BattleResult _battleResult = BattleResult::Empty();
ScriptSet _volatile; ScriptSet _volatile;
public: public:
Battle(const BattleLibrary* library, std::vector<BattleParty> parties, uint8_t numberOfSides = 2, Battle(const BattleLibrary* library, std::vector<BattleParty> parties, bool canFlee = true,
uint8_t creaturesPerSide = 1) uint8_t numberOfSides = 2, uint8_t creaturesPerSide = 1)
: _library(library), _parties(parties), _numberOfSides(numberOfSides), _creaturesPerSide(creaturesPerSide) { : _library(library), _parties(parties), _canFlee(canFlee), _numberOfSides(numberOfSides),
_creaturesPerSide(creaturesPerSide) {
_sides = std::vector<BattleSide*>(numberOfSides); _sides = std::vector<BattleSide*>(numberOfSides);
for (size_t i = 0; i < numberOfSides; i++) { for (size_t i = 0; i < numberOfSides; i++) {
_sides[i] = new BattleSide(i, this, creaturesPerSide); _sides[i] = new BattleSide(i, this, creaturesPerSide);
@ -44,6 +47,8 @@ namespace CreatureLib::Battling {
virtual bool CanUse(const BaseTurnChoice* choice); virtual bool CanUse(const BaseTurnChoice* choice);
virtual bool TrySetChoice(BaseTurnChoice* choice); virtual bool TrySetChoice(BaseTurnChoice* choice);
bool CanFlee() const { return _canFlee; }
void CheckChoicesSetAndRun(); void CheckChoicesSetAndRun();
[[nodiscard]] ChoiceQueue* GetCurrentTurnQueue() const; [[nodiscard]] ChoiceQueue* GetCurrentTurnQueue() const;
@ -63,7 +68,7 @@ namespace CreatureLib::Battling {
void ValidateBattleState(); void ValidateBattleState();
inline bool HasEnded() const { return _hasEnded; } inline bool HasEnded() const { return _hasEnded; }
inline uint8_t GetResult() const { return _battleResult; } inline const BattleResult& GetResult() const { return _battleResult; }
const std::vector<BattleSide*>& GetSides() const { return _sides; } const std::vector<BattleSide*>& GetSides() const { return _sides; }
}; };

View File

@ -0,0 +1,25 @@
#ifndef CREATURELIB_BATTLERESULT_HPP
#define CREATURELIB_BATTLERESULT_HPP
#include <cstdint>
namespace CreatureLib::Battling {
class BattleResult {
bool _conclusiveResult;
uint8_t _winningSide;
BattleResult(bool conclusiveResult, uint8_t winningSide)
: _conclusiveResult(conclusiveResult), _winningSide(winningSide) {}
public:
static BattleResult Inconclusive() { return BattleResult(false, 0); }
static BattleResult Conclusive(uint8_t winner) { return BattleResult(true, winner); }
static BattleResult Empty() { return BattleResult(false, 0); }
/// Whether or not the battle has ended with a conclusive result.
bool IsConclusiveResult() const { return _conclusiveResult; }
/// Get the index of the side that has won the battle. Only valid if the battle has a conclusive result.
uint8_t GetWinningSide() const { return _winningSide; }
};
}
#endif // CREATURELIB_BATTLERESULT_HPP

View File

@ -15,6 +15,7 @@ namespace CreatureLib::Battling {
uint8_t _choicesSet = 0; uint8_t _choicesSet = 0;
ScriptSet _volatile; ScriptSet _volatile;
Battle* _battle; Battle* _battle;
bool _hasFled = false;
public: public:
explicit BattleSide(uint8_t index, Battle* battle, uint8_t creaturesPerSide) explicit BattleSide(uint8_t index, Battle* battle, uint8_t creaturesPerSide)
@ -74,6 +75,10 @@ namespace CreatureLib::Battling {
} }
return true; return true;
} }
bool HasFled() { return _hasFled; }
void MarkAsFled() { _hasFled = true; }
}; };
} }

View File

@ -0,0 +1,19 @@
#ifndef CREATURELIB_FLEETURNCHOICE_HPP
#define CREATURELIB_FLEETURNCHOICE_HPP
#include "../Models/Creature.hpp"
#include "BaseTurnChoice.hpp"
namespace CreatureLib::Battling {
class FleeTurnChoice : public BaseTurnChoice {
public:
FleeTurnChoice(Creature* user) : BaseTurnChoice(user) {}
TurnChoiceKind GetKind() const override { return TurnChoiceKind ::Flee; }
protected:
void GetActiveScripts(std::vector<ScriptWrapper>& scripts) override { GetUser()->GetActiveScripts(scripts); }
};
}
#endif // CREATURELIB_FLEETURNCHOICE_HPP

View File

@ -4,6 +4,6 @@
#include <cstdint> #include <cstdint>
namespace CreatureLib::Battling { namespace CreatureLib::Battling {
enum class TurnChoiceKind : uint8_t { Pass, Attack, Item, Switch, RunAway }; enum class TurnChoiceKind : uint8_t { Pass, Attack, Item, Switch, Flee };
} }
#endif // CREATURELIB_TURNCHOICEKIND_HPP #endif // CREATURELIB_TURNCHOICEKIND_HPP

View File

@ -79,7 +79,9 @@ TEST_CASE("Finish battle when all battle of one side have fainted", "[Integratio
c2->Damage(c2->GetCurrentHealth(), DamageSource::AttackDamage); c2->Damage(c2->GetCurrentHealth(), DamageSource::AttackDamage);
REQUIRE(battle.HasEnded()); REQUIRE(battle.HasEnded());
REQUIRE(battle.GetResult() == 0); auto result = battle.GetResult();
REQUIRE(result.IsConclusiveResult());
REQUIRE(result.GetWinningSide() == 0);
} }
TEST_CASE("When creature is dealt enough damage, faint it and mark battle as ended", "[Integrations]") { TEST_CASE("When creature is dealt enough damage, faint it and mark battle as ended", "[Integrations]") {
@ -104,7 +106,9 @@ TEST_CASE("When creature is dealt enough damage, faint it and mark battle as end
battle.TrySetChoice(new PassTurnChoice(c2)); battle.TrySetChoice(new PassTurnChoice(c2));
REQUIRE(battle.HasEnded()); REQUIRE(battle.HasEnded());
REQUIRE(battle.GetResult() == 0); auto result = battle.GetResult();
REQUIRE(result.IsConclusiveResult());
REQUIRE(result.GetWinningSide() == 0);
} }
TEST_CASE("When another creature is available on faint, make sure the battle hasn't ended", "[Integrations]") { TEST_CASE("When another creature is available on faint, make sure the battle hasn't ended", "[Integrations]") {
@ -137,7 +141,9 @@ TEST_CASE("When another creature is available on faint, make sure the battle has
battle.TrySetChoice(new PassTurnChoice(c3)); battle.TrySetChoice(new PassTurnChoice(c3));
REQUIRE(battle.HasEnded()); REQUIRE(battle.HasEnded());
REQUIRE(battle.GetResult() == 0); auto result = battle.GetResult();
REQUIRE(result.IsConclusiveResult());
REQUIRE(result.GetWinningSide() == 0);
} }
TEST_CASE("Switch Creature in", "[Integrations]") { TEST_CASE("Switch Creature in", "[Integrations]") {
@ -221,4 +227,26 @@ TEST_CASE("Switch Creature in, mark as seen opponent for opponent", "[Integratio
REQUIRE(seen.find(c2) != seen.end()); REQUIRE(seen.find(c2) != seen.end());
} }
TEST_CASE("Flee Battle", "[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});
battle.SwitchCreature(0, 0, c1);
battle.SwitchCreature(1, 0, c2);
battle.TrySetChoice(new FleeTurnChoice(c1));
battle.TrySetChoice(new PassTurnChoice(c2));
REQUIRE(battle.HasEnded());
auto result = battle.GetResult();
REQUIRE_FALSE(result.IsConclusiveResult());
}
#endif #endif

View File

@ -13,8 +13,8 @@ BattleLibrary* TestLibrary::Get() {
auto l = new DataLibrary(LibrarySettings(100, 4), BuildSpeciesLibrary(), BuildAttackLibrary(), auto l = new DataLibrary(LibrarySettings(100, 4), BuildSpeciesLibrary(), BuildAttackLibrary(),
BuildItemLibrary(), BuildGrowthRateLibrary(), BuildTypeLibrary()); BuildItemLibrary(), BuildGrowthRateLibrary(), BuildTypeLibrary());
auto statCalc = new BattleStatCalculator(); auto statCalc = new BattleStatCalculator();
auto battleLib = new BattleLibrary(l, statCalc, new DamageLibrary(), new CriticalLibrary(), auto battleLib = new BattleLibrary(l, statCalc, new DamageLibrary(), new ExperienceLibrary(),
new ExperienceLibrary(), new ScriptResolver()); new ScriptResolver(), new MiscLibrary());
TestLibrary::_library = battleLib; TestLibrary::_library = battleLib;
} }
return TestLibrary::_library; return TestLibrary::_library;