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 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue