Support for ending battles when only one side has creatures that are available for battle.

This commit is contained in:
Deukhoofd 2019-12-07 21:56:29 +01:00
parent 0483e635ea
commit 262279bd2c
9 changed files with 117 additions and 6 deletions

View File

@ -26,14 +26,14 @@ uint32_t CalculateHealthStat(Battling::Creature* creature) {
auto level = creature->GetLevel(); auto level = creature->GetLevel();
auto a = auto a =
(creature->GetBaseStat(Core::Statistic::Health) + creature->GetStatPotential(Core::Statistic::Health)) * 2 + (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; return floor(a / 100) + level + 10;
} }
uint32_t CalculateOtherStat(Battling::Creature* creature, Core::Statistic stat) { uint32_t CalculateOtherStat(Battling::Creature* creature, Core::Statistic stat) {
auto level = creature->GetLevel(); auto level = creature->GetLevel();
auto a = (creature->GetBaseStat(stat) + creature->GetStatPotential(stat)) * 2 + 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; return floor(a / 100) + 10;
} }

View File

@ -97,3 +97,20 @@ bool Battle::CanSlotBeFilled(uint8_t side, uint8_t index) const {
} }
return false; 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;
}

View File

@ -18,6 +18,8 @@ namespace CreatureLib::Battling {
std::vector<BattleSide*> _sides; std::vector<BattleSide*> _sides;
Core::Random _random; Core::Random _random;
ChoiceQueue* _currentTurnQueue = nullptr; ChoiceQueue* _currentTurnQueue = nullptr;
bool _hasEnded = false;
uint8_t _battleResult = 0;
ScriptSet _volatile; ScriptSet _volatile;
@ -58,6 +60,10 @@ namespace CreatureLib::Battling {
bool CanSlotBeFilled(uint8_t side, uint8_t index) const; bool CanSlotBeFilled(uint8_t side, uint8_t index) const;
void GetActiveScripts(std::vector<ScriptWrapper>& scripts) override; void GetActiveScripts(std::vector<ScriptWrapper>& scripts) override;
void ValidateBattleState();
inline bool HasEnded() const { return _hasEnded; }
inline uint8_t GetResult() const { return _battleResult; }
}; };
} }

View File

@ -37,8 +37,13 @@ void BattleSide::SetChoice(BaseTurnChoice* choice) {
} }
void BattleSide::SetCreature(Creature* creature, uint8_t index) { void BattleSide::SetCreature(Creature* creature, uint8_t index) {
auto old = _creatures[index];
if (old != nullptr){
old->SetOnBattleField(false);
}
_creatures[index] = creature; _creatures[index] = creature;
creature->SetBattleData(_battle, this); creature->SetBattleData(_battle, this);
creature->SetOnBattleField(true);
} }
bool BattleSide::CreatureOnSide(const Creature* creature) const { bool BattleSide::CreatureOnSide(const Creature* creature) const {

View File

@ -11,6 +11,7 @@ namespace CreatureLib::Battling {
uint8_t _creaturesPerSide; uint8_t _creaturesPerSide;
std::vector<Creature*> _creatures; std::vector<Creature*> _creatures;
std::vector<BaseTurnChoice*> _choices; std::vector<BaseTurnChoice*> _choices;
std::vector<bool> _fillableSlots;
uint8_t _choicesSet = 0; uint8_t _choicesSet = 0;
ScriptSet _volatile; ScriptSet _volatile;
Battle* _battle; Battle* _battle;
@ -20,9 +21,11 @@ namespace CreatureLib::Battling {
: _index(index), _creaturesPerSide(creaturesPerSide), _battle(battle) { : _index(index), _creaturesPerSide(creaturesPerSide), _battle(battle) {
_creatures = std::vector<Creature*>(creaturesPerSide); _creatures = std::vector<Creature*>(creaturesPerSide);
_choices = std::vector<BaseTurnChoice*>(creaturesPerSide); _choices = std::vector<BaseTurnChoice*>(creaturesPerSide);
_fillableSlots = std::vector<bool>(creaturesPerSide);
for (size_t i = 0; i < creaturesPerSide; i++) { for (size_t i = 0; i < creaturesPerSide; i++) {
_creatures[i] = nullptr; _creatures[i] = nullptr;
_choices[i] = nullptr; _choices[i] = nullptr;
_fillableSlots[i] = true;
} }
ResetChoices(); ResetChoices();
} }
@ -43,6 +46,22 @@ namespace CreatureLib::Battling {
bool CreatureOnSide(const Creature* creature) const; bool CreatureOnSide(const Creature* creature) const;
void GetActiveScripts(std::vector<ScriptWrapper>& scripts) final; void GetActiveScripts(std::vector<ScriptWrapper>& 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;
}
}; };
} }

View File

@ -98,6 +98,11 @@ void Battling::Creature::Damage(uint32_t damage, Battling::DamageSource source)
} }
// HOOK: On Damage // HOOK: On Damage
__CurrentHealth -= damage; __CurrentHealth -= damage;
if (IsFainted()){
_side->MarkSlotAsUnfillable(this);
_battle->ValidateBattleState();
}
} }
void Battling::Creature::OverrideActiveTalent(const std::string& talent) { void Battling::Creature::OverrideActiveTalent(const std::string& talent) {

View File

@ -69,6 +69,7 @@ namespace CreatureLib::Battling {
void SetBattleData(Battle* battle, BattleSide* side); void SetBattleData(Battle* battle, BattleSide* side);
Battle* GetBattle() const; Battle* GetBattle() const;
BattleSide* GetBattleSide() const; BattleSide* GetBattleSide() const;
void SetOnBattleField(bool value) {_onBattleField = value;}
bool IsOnBattleField() const { return _onBattleField; } bool IsOnBattleField() const { return _onBattleField; }
const std::string& GetNickname() const; const std::string& GetNickname() const;

View File

@ -50,4 +50,62 @@ TEST_CASE("Use damaging move", "[Integrations]") {
REQUIRE(c2->GetCurrentHealth() < c2->GetBoostedStat(Statistic::Health)); 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 #endif

View File

@ -31,13 +31,13 @@ SpeciesLibrary *TestLibrary::BuildSpeciesLibrary() {
AttackLibrary *TestLibrary::BuildAttackLibrary() { AttackLibrary *TestLibrary::BuildAttackLibrary() {
auto l = new AttackLibrary(); 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, {})); 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, {})); 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, {})); 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, {})); AttackTarget::AdjacentOpponent, -1, {}));
return l; return l;
} }