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 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;
}

View File

@ -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;
}

View File

@ -18,6 +18,8 @@ namespace CreatureLib::Battling {
std::vector<BattleSide*> _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<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) {
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 {

View File

@ -11,6 +11,7 @@ namespace CreatureLib::Battling {
uint8_t _creaturesPerSide;
std::vector<Creature*> _creatures;
std::vector<BaseTurnChoice*> _choices;
std::vector<bool> _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<Creature*>(creaturesPerSide);
_choices = std::vector<BaseTurnChoice*>(creaturesPerSide);
_fillableSlots = std::vector<bool>(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<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
__CurrentHealth -= damage;
if (IsFainted()){
_side->MarkSlotAsUnfillable(this);
_battle->ValidateBattleState();
}
}
void Battling::Creature::OverrideActiveTalent(const std::string& talent) {

View File

@ -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;

View File

@ -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

View File

@ -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;
}