Support for ending battles when only one side has creatures that are available for battle.
This commit is contained in:
parent
0483e635ea
commit
262279bd2c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue