Support for cloning battles for AI purposes.
continuous-integration/drone/push Build is passing Details

Signed-off-by: Deukhoofd <Deukhoofd@gmail.com>
This commit is contained in:
Deukhoofd 2021-04-11 15:20:50 +02:00
parent a3b7002cd4
commit 84a14cff2b
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
19 changed files with 236 additions and 30 deletions

View File

@ -1,5 +1,5 @@
Checks: 'readability-*,clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-alpha*,performance-*,cppcoreguidelines-*, Checks: 'readability-*,clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-alpha*,performance-*,cppcoreguidelines-*,
bugprone-*,modernize-*,-modernize-use-trailing-return-type' bugprone-*,modernize-*,-modernize-use-trailing-return-type,-cppcoreguidelines-non-private-member-variables-in-classes'
HeaderFilterRegex: '' HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
CheckOptions: CheckOptions:

View File

@ -9,7 +9,9 @@ namespace CreatureLib::Battling {
protected: protected:
void Clear() override { void Clear() override {
_attack.reset(); if (_attack != nullptr) {
_attack.reset();
}
HistoryElement::Clear(); HistoryElement::Clear();
} }

View File

@ -6,18 +6,24 @@
namespace CreatureLib::Battling { namespace CreatureLib::Battling {
class HistoryElement { class HistoryElement {
friend class HistoryHolder; friend class HistoryHolder;
HistoryElement* _previous; u8* _previousOffset = nullptr;
protected: protected:
virtual void Clear() { virtual void Clear() {
if (_previous != nullptr) { if (_previousOffset != nullptr) {
_previous->Clear(); GetPrevious()->Clear();
} }
} }
public: public:
virtual HistoryElementKind GetKind() const noexcept = 0; virtual HistoryElementKind GetKind() const noexcept = 0;
ArbUt::BorrowedPtr<const HistoryElement> GetPrevious() const noexcept { return _previous; } ArbUt::BorrowedPtr<const HistoryElement> GetPrevious() const noexcept {
return reinterpret_cast<HistoryElement*>((u8*)this - _previousOffset);
}
ArbUt::BorrowedPtr<HistoryElement> GetPrevious() noexcept {
return reinterpret_cast<HistoryElement*>((u8*)this - _previousOffset);
}
}; };
} }

View File

@ -4,13 +4,14 @@
#include "../../Library/Exceptions/CreatureException.hpp" #include "../../Library/Exceptions/CreatureException.hpp"
#include "HistoryElements/HistoryElement.hpp" #include "HistoryElements/HistoryElement.hpp"
template <class T> concept HistoryElementType = std::is_base_of<CreatureLib::Battling::HistoryElement, T>::value; template <class T>
concept HistoryElementType = std::is_base_of<CreatureLib::Battling::HistoryElement, T>::value;
namespace CreatureLib::Battling { namespace CreatureLib::Battling {
class HistoryHolder { class HistoryHolder {
size_t _offset; size_t _offset;
size_t _capacity; size_t _capacity;
uint8_t* _memory = nullptr; u8* _memory = nullptr;
HistoryElement* _top = nullptr; HistoryElement* _top = nullptr;
static constexpr size_t initialSize = 2048; static constexpr size_t initialSize = 2048;
@ -22,7 +23,7 @@ namespace CreatureLib::Battling {
if (ptr == nullptr) { if (ptr == nullptr) {
THROW("Out of memory."); THROW("Out of memory.");
} }
_memory = static_cast<uint8_t*>(ptr); _memory = static_cast<u8*>(ptr);
} }
HistoryHolder(const HistoryHolder&) = delete; HistoryHolder(const HistoryHolder&) = delete;
HistoryHolder& operator=(const HistoryHolder&) = delete; HistoryHolder& operator=(const HistoryHolder&) = delete;
@ -33,19 +34,25 @@ namespace CreatureLib::Battling {
free(_memory); free(_memory);
} }
void Resize() {
_capacity += stepSize;
auto newPtr = realloc(_memory, _capacity);
if (newPtr == nullptr) {
THROW("Out of memory.");
}
_memory = static_cast<u8*>(newPtr);
}
template <HistoryElementType T, class... parameters> void Register(parameters... args) { template <HistoryElementType T, class... parameters> void Register(parameters... args) {
if (_offset + sizeof(T) >= _capacity) { if (_offset + sizeof(T) >= _capacity) {
_capacity += stepSize; Resize();
auto newPtr = realloc(_memory, _capacity);
if (newPtr == nullptr) {
THROW("Out of memory.");
}
_memory = static_cast<uint8_t*>(newPtr);
} }
uint8_t* ptr = _memory + _offset; u8* ptr = _memory + _offset;
T* element = new (ptr) T(args...); T* element = new (ptr) T(args...);
_offset += sizeof(T); _offset += sizeof(T);
element->_previous = _top; if (_top != nullptr) {
element->_previousOffset = (u8*)sizeof(T);
}
_top = element; _top = element;
} }
@ -56,10 +63,23 @@ namespace CreatureLib::Battling {
while (c != nullptr) { while (c != nullptr) {
if (c->GetKind() == HistoryElementKind::AttackUse) if (c->GetKind() == HistoryElementKind::AttackUse)
return c; return c;
c = c->_previous; c = c->GetPrevious();
} }
return {}; return {};
} }
void CloneOnto(HistoryHolder& other) {
if (other._top != nullptr) {
other._top->Clear();
}
free(other._memory);
other._offset = _offset;
other._capacity = _capacity;
other._memory = static_cast<uint8_t*>(malloc(_capacity));
if (_top != nullptr) {
other._top = reinterpret_cast<HistoryElement*>(other._memory + ((u8*)_top - _memory));
}
}
}; };
} }

View File

@ -168,3 +168,45 @@ BattleScript* Battle::AddVolatileScript(const ArbUt::StringView& key) {
BattleScript* Battle::AddVolatileScript(BattleScript* script) { return _volatile.Add(script); } BattleScript* Battle::AddVolatileScript(BattleScript* script) { return _volatile.Add(script); }
void Battle::RemoveVolatileScript(BattleScript* script) { _volatile.Remove(script->GetName()); } void Battle::RemoveVolatileScript(BattleScript* script) { _volatile.Remove(script->GetName()); }
void Battle::DisplayText(const ArbUt::StringView& text) { TriggerEventListener<DisplayTextEvent>(text); } void Battle::DisplayText(const ArbUt::StringView& text) { TriggerEventListener<DisplayTextEvent>(text); }
Battle* Battle::Clone() {
auto parties = ArbUt::List<BattleParty*>(_parties.Count());
for (auto* party : _parties) {
parties.Append(party->Clone());
}
auto* battle = new Battle(_library, parties, _canFlee, _numberOfSides, _creaturesPerSide);
for (int i = 0; i < _numberOfSides; ++i) {
battle->_sides.Set(i, _sides[i]->CloneWithoutCreatures());
// FIXME: This is horrible code to translate the creature from the old battle into the same creature in the
// new battle. This needs to be cleaned up, as it's ugly and slow.
for (int creatureIndex = 0; creatureIndex < _creaturesPerSide; ++creatureIndex) {
auto creature = _sides[i]->GetCreature(creatureIndex);
if (!creature.HasValue()) {
continue;
}
for (size_t j = 0; j < _parties.Count(); ++j) {
auto party = _parties[j];
if (!party->IsResponsibleForIndex(i, creatureIndex)) {
continue;
}
auto partyIndex = party->GetParty()->GetParty().IndexOf(creature.GetValue());
if (partyIndex != -1U) {
auto c = battle->_parties.At(j)->GetParty()->GetParty()[partyIndex];
battle->_sides.At(i)->SetCreature(c, creatureIndex);
j = _parties.Count();
break;
}
}
}
}
battle->_random = _random;
battle->_hasEnded = _hasEnded;
battle->_battleResult = _battleResult;
_historyHolder.CloneOnto(battle->_historyHolder);
battle->_currentTurn = _currentTurn;
_volatile.Clone(battle->_volatile);
return battle;
}

View File

@ -131,6 +131,8 @@ namespace CreatureLib::Battling {
} }
} }
} }
Battle* Clone();
}; };
} }

View File

@ -13,6 +13,8 @@ namespace CreatureLib::Battling {
BattleParty(CreatureParty* party, const ArbUt::List<CreatureIndex>& responsibleIndices) BattleParty(CreatureParty* party, const ArbUt::List<CreatureIndex>& responsibleIndices)
: _party(party), _responsibleIndices(responsibleIndices) {} : _party(party), _responsibleIndices(responsibleIndices) {}
virtual ~BattleParty() = default;
inline const ArbUt::BorrowedPtr<CreatureParty>& GetParty() const { return _party; } inline const ArbUt::BorrowedPtr<CreatureParty>& GetParty() const { return _party; }
inline const ArbUt::List<CreatureIndex>& GetResponsibleIndices() const { return _responsibleIndices; } inline const ArbUt::List<CreatureIndex>& GetResponsibleIndices() const { return _responsibleIndices; }
@ -40,6 +42,8 @@ namespace CreatureLib::Battling {
} }
return false; return false;
} }
virtual BattleParty* Clone() { return new BattleParty(_party->Clone(), _responsibleIndices); }
}; };
} }

View File

@ -123,3 +123,10 @@ bool BattleSide::SwapPositions(u8 a, u8 b) {
_battle->TriggerEventListener<SwapEvent>(_index, a, b); _battle->TriggerEventListener<SwapEvent>(_index, a, b);
return true; return true;
} }
BattleSide* BattleSide::CloneWithoutCreatures() {
auto* side = new BattleSide(_index, _battle, _creaturesPerSide);
side->_choicesSet = _choicesSet;
_volatile.Clone(side->_volatile);
side->_hasFled = _hasFled;
return side;
}

View File

@ -87,6 +87,8 @@ namespace CreatureLib::Battling {
uint8_t GetRandomCreatureIndex(); uint8_t GetRandomCreatureIndex();
bool SwapPositions(u8 a, u8 b); bool SwapPositions(u8 a, u8 b);
BattleSide* CloneWithoutCreatures();
}; };
} }

View File

@ -1,4 +1,5 @@
#include "Creature.hpp" #include "Creature.hpp"
#include <utility>
#include "../EventHooks/EventDataClasses.hpp" #include "../EventHooks/EventDataClasses.hpp"
#include "../Models/Battle.hpp" #include "../Models/Battle.hpp"
#include "../ScriptHandling/ScriptMacros.hpp" #include "../ScriptHandling/ScriptMacros.hpp"
@ -6,11 +7,11 @@
using namespace CreatureLib; using namespace CreatureLib;
namespace CreatureLib::Battling { namespace CreatureLib::Battling {
Creature::Creature(ArbUt::BorrowedPtr<const BattleLibrary> library, Creature::Creature(const ArbUt::BorrowedPtr<const BattleLibrary>& library,
const ArbUt::BorrowedPtr<const Library::CreatureSpecies>& species, const ArbUt::BorrowedPtr<const Library::CreatureSpecies>& species,
const ArbUt::BorrowedPtr<const Library::SpeciesVariant>& variant, level_int_t level, const ArbUt::BorrowedPtr<const Library::SpeciesVariant>& variant, level_int_t level,
uint32_t experience, uint32_t uid, Library::Gender gender, uint8_t coloring, uint32_t experience, uint32_t uid, Library::Gender gender, uint8_t coloring,
const ArbUt::OptionalBorrowedPtr<const Library::Item> heldItem, const std::string& nickname, ArbUt::OptionalBorrowedPtr<const Library::Item> heldItem, std::string nickname,
const Library::TalentIndex& talent, const std::vector<LearnedAttack*>& attacks, const Library::TalentIndex& talent, const std::vector<LearnedAttack*>& attacks,
bool allowedExperienceGain) bool allowedExperienceGain)
: _library(library), _species(species), _variant(variant), _level(level), _experience(experience), : _library(library), _species(species), _variant(variant), _level(level), _experience(experience),
@ -110,10 +111,11 @@ namespace CreatureLib::Battling {
bool Creature::ChangeStatBoost(Library::Statistic stat, int8_t diffAmount) { bool Creature::ChangeStatBoost(Library::Statistic stat, int8_t diffAmount) {
bool changed = false; bool changed = false;
auto oldValue = this->_statBoost.GetStat(stat); auto oldValue = this->_statBoost.GetStat(stat);
if (diffAmount > 0) if (diffAmount > 0) {
changed = this->_statBoost.IncreaseStatBy(stat, diffAmount); changed = this->_statBoost.IncreaseStatBy(stat, diffAmount);
else } else if (diffAmount < 0) {
changed = this->_statBoost.DecreaseStatBy(stat, -diffAmount); changed = this->_statBoost.DecreaseStatBy(stat, -diffAmount);
}
if (this->GetBattle().HasValue()) { if (this->GetBattle().HasValue()) {
auto newValue = this->_statBoost.GetStat(stat); auto newValue = this->_statBoost.GetStat(stat);
this->GetBattle().GetValue()->TriggerEventListener<ChangeStatBoostEvent>(this, stat, oldValue, newValue); this->GetBattle().GetValue()->TriggerEventListener<ChangeStatBoostEvent>(this, stat, oldValue, newValue);
@ -123,7 +125,7 @@ namespace CreatureLib::Battling {
} }
void Creature::RecalculateFlatStats() { void Creature::RecalculateFlatStats() {
auto& statCalc = this->_library->GetStatCalculator(); const auto& statCalc = this->_library->GetStatCalculator();
this->_flatStats = statCalc->CalculateFlatStats(this); this->_flatStats = statCalc->CalculateFlatStats(this);
RecalculateBoostedStats(); RecalculateBoostedStats();
} }
@ -329,4 +331,41 @@ namespace CreatureLib::Battling {
_attacks.Set(index, attack); _attacks.Set(index, attack);
} }
Creature* Creature::Clone() {
auto attacks = std::vector<LearnedAttack*>(_attacks.Count());
auto i = 0;
for (auto* attack : _attacks) {
if (attack == nullptr) {
attacks[i++] = nullptr;
} else {
attacks[i++] = attack->Clone();
}
}
auto* c = new Creature(_library, _species, _variant, _level, _experience, _uniqueIdentifier, _gender, _coloring,
_heldItem, _nickname, _talentIndex, attacks, _allowedExperienceGain);
c->_displaySpecies = _displaySpecies;
c->_displayVariant = _displayVariant;
c->_currentHealth = _currentHealth;
c->_statBoost = _statBoost;
c->_flatStats = _flatStats;
c->_boostedStats = _boostedStats;
c->_battle = _battle;
c->_side = _side;
c->_onBattleField = _onBattleField;
if (_activeTalent != nullptr) {
c->_activeTalent = std::unique_ptr<BattleScript>(_activeTalent->Clone());
}
c->_hasOverridenTalent = _hasOverridenTalent;
c->_overridenTalentName = _overridenTalentName;
if (_status != nullptr) {
c->_status = std::unique_ptr<BattleScript>(_status->Clone());
}
_volatile.Clone(c->_volatile);
c->_types = std::vector<u8>(_types);
c->RecalculateFlatStats();
return c;
}
} }

View File

@ -43,7 +43,7 @@ namespace CreatureLib::Battling {
ArbUt::OptionalBorrowedPtr<BattleSide> _side = nullptr; ArbUt::OptionalBorrowedPtr<BattleSide> _side = nullptr;
bool _onBattleField = false; bool _onBattleField = false;
std::string _nickname = ""; std::string _nickname;
CreatureLib::Library::TalentIndex _talentIndex; CreatureLib::Library::TalentIndex _talentIndex;
std::unique_ptr<BattleScript> _activeTalent = nullptr; std::unique_ptr<BattleScript> _activeTalent = nullptr;
@ -57,17 +57,17 @@ namespace CreatureLib::Battling {
std::unique_ptr<BattleScript> _status = nullptr; std::unique_ptr<BattleScript> _status = nullptr;
ScriptSet _volatile = {}; ScriptSet _volatile = {};
std::vector<uint8_t> _types; std::vector<u8> _types;
private: private:
void OnFaint(); void OnFaint();
public: public:
Creature(ArbUt::BorrowedPtr<const BattleLibrary> library, Creature(const ArbUt::BorrowedPtr<const BattleLibrary>& library,
const ArbUt::BorrowedPtr<const Library::CreatureSpecies>& species, const ArbUt::BorrowedPtr<const Library::CreatureSpecies>& species,
const ArbUt::BorrowedPtr<const Library::SpeciesVariant>& variant, level_int_t level, const ArbUt::BorrowedPtr<const Library::SpeciesVariant>& variant, level_int_t level,
uint32_t experience, uint32_t uid, Library::Gender gender, uint8_t coloring, uint32_t experience, uint32_t uid, Library::Gender gender, uint8_t coloring,
const ArbUt::OptionalBorrowedPtr<const Library::Item> heldItem, const std::string& nickname, ArbUt::OptionalBorrowedPtr<const Library::Item> heldItem, std::string nickname,
const Library::TalentIndex& talent, const std::vector<LearnedAttack*>& attacks, const Library::TalentIndex& talent, const std::vector<LearnedAttack*>& attacks,
bool allowedExperienceGain = true); bool allowedExperienceGain = true);
@ -186,6 +186,8 @@ namespace CreatureLib::Battling {
void RecalculateBoostedStat(Library::Statistic); void RecalculateBoostedStat(Library::Statistic);
// endregion // endregion
virtual Creature* Clone();
}; };
} }

View File

@ -63,6 +63,18 @@ namespace CreatureLib::Battling {
} }
} }
} }
virtual CreatureParty* Clone() {
auto party = new CreatureParty(_party.Count());
auto i = 0;
for (auto c : _party) {
if (c != nullptr) {
party->SwapInto(i, c->Clone());
i++;
}
}
return party;
}
}; };
} }

View File

@ -28,6 +28,12 @@ namespace CreatureLib::Battling {
virtual void DecreaseUses(uint8_t amount) noexcept; virtual void DecreaseUses(uint8_t amount) noexcept;
virtual void RestoreUses(uint8_t amount) noexcept; virtual void RestoreUses(uint8_t amount) noexcept;
virtual void RestoreAllUses() noexcept; virtual void RestoreAllUses() noexcept;
virtual LearnedAttack* Clone() {
auto* attack = new LearnedAttack(_attack, _maxUses, _learnMethod);
attack->_remainingUses = _remainingUses;
return attack;
}
}; };
} }

View File

@ -18,6 +18,8 @@ namespace CreatureLib::Battling {
virtual ~BattleScript() = default; virtual ~BattleScript() = default;
virtual BattleScript* Clone() = 0;
virtual void Stack(){}; virtual void Stack(){};
virtual void OnRemove(){}; virtual void OnRemove(){};

View File

@ -15,6 +15,12 @@ namespace CreatureLib::Battling {
static constexpr size_t defaultCapacity = 8; static constexpr size_t defaultCapacity = 8;
ScriptSet() : _scripts(defaultCapacity), _lookup(defaultCapacity){}; ScriptSet() : _scripts(defaultCapacity), _lookup(defaultCapacity){};
void Clone(ScriptSet& s) {
for (auto* script : _scripts) {
s.Add(script->Clone());
}
}
BattleScript* Add(BattleScript* script) { BattleScript* Add(BattleScript* script) {
auto v = _lookup.TryGet(script->GetName()); auto v = _lookup.TryGet(script->GetName());
if (v.has_value()) { if (v.has_value()) {

View File

@ -0,0 +1,50 @@
#ifdef TESTS_BUILD
#include "../../extern/doctest.hpp"
#include "../../src/Battling/History/HistoryElements/AttackUseHistory.hpp"
#include "../../src/Battling/Models/Battle.hpp"
#include "../../src/Battling/Models/BattleSide.hpp"
#include "../../src/Battling/Models/CreateCreature.hpp"
#include "../../src/Battling/TurnChoices/PassTurnChoice.hpp"
#include "../TestLibrary/TestLibrary.hpp"
using namespace CreatureLib::Battling;
TEST_CASE("Clone battle, test basic functionality") {
auto lib = TestLibrary::Get();
auto creature = CreateCreature(lib, "testSpecies1"_cnc, 1).Create();
auto party = CreatureParty(6);
party.SwapInto(0, creature);
auto bp = ArbUt::List<BattleParty*>();
bp.Append(new BattleParty(&party, {CreatureIndex(0, 0)}));
auto battle = Battle(lib, bp);
battle.SwitchCreature(0, 0, creature);
auto clone = battle.Clone();
REQUIRE_NE(battle.GetCreature(0, 0), clone->GetCreature(0, 0));
REQUIRE(battle.GetCreature(0, 0).GetValue()->GetSpecies() == clone->GetCreature(0, 0).GetValue()->GetSpecies());
auto clonedParty = clone->GetParties().At(0)->GetParty().GetRaw();
delete clone;
delete clonedParty;
}
TEST_CASE("Clone battle, test history history holder") {
auto lib = TestLibrary::Get();
auto battle = Battle(lib, {});
auto side = BattleSide(0, &battle, 1);
auto c = CreateCreature(TestLibrary::Get(), "testSpecies1"_cnc, 5).Create();
side.SetCreature(c, 0);
auto clone = battle.Clone();
clone->RegisterHistoryElement<AttackUseHistory>(nullptr);
clone->RegisterHistoryElement<AttackUseHistory>(nullptr);
delete c;
delete clone;
}
#endif

View File

@ -12,10 +12,11 @@ private:
ArbUt::StringView _name; ArbUt::StringView _name;
public: public:
explicit TestScript(const std::string& name) : _name(name.c_str(), name.length()){}; explicit TestScript(const ArbUt::StringView& name) : _name(name){};
const ArbUt::StringView& GetName() const noexcept override { return _name; } const ArbUt::StringView& GetName() const noexcept override { return _name; }
void TestMethod(int& runCount) { runCount++; } void TestMethod(int& runCount) { runCount++; }
BattleScript* Clone() override { return new TestScript(_name); }
}; };
TEST_CASE("Script Aggregator properly iterates containing script.") { TEST_CASE("Script Aggregator properly iterates containing script.") {

View File

@ -12,8 +12,10 @@ private:
ArbUt::StringView _name; ArbUt::StringView _name;
public: public:
explicit TestScript(const std::string& name) : _name(name.c_str(), name.length()){}; explicit TestScript(const ArbUt::StringView& name) : _name(name){};
const ArbUt::StringView& GetName() const noexcept override { return _name; } const ArbUt::StringView& GetName() const noexcept override { return _name; }
BattleScript* Clone() override { return new TestScript(_name); }
}; };
TEST_CASE("Empty script set count == 0") { TEST_CASE("Empty script set count == 0") {

View File

@ -11,10 +11,11 @@ private:
ArbUt::StringView _name; ArbUt::StringView _name;
public: public:
explicit TestScript(const std::string& name) : _name(name.c_str(), name.length()){}; explicit TestScript(const ArbUt::StringView& name) : _name(name){};
const ArbUt::StringView& GetName() const noexcept override { return _name; } const ArbUt::StringView& GetName() const noexcept override { return _name; }
void TestMethod(int& runCount) { runCount++; } void TestMethod(int& runCount) { runCount++; }
BattleScript* Clone() override { return new TestScript(_name); }
}; };
class ScriptSourceWithScriptPtr : public ScriptSource { class ScriptSourceWithScriptPtr : public ScriptSource {