From 84a14cff2bfcf4184c094b4d2f6c05faaebfcc3a Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 11 Apr 2021 15:20:50 +0200 Subject: [PATCH] Support for cloning battles for AI purposes. Signed-off-by: Deukhoofd --- .clang-tidy | 2 +- .../HistoryElements/AttackUseHistory.hpp | 4 +- .../HistoryElements/HistoryElement.hpp | 14 ++++-- src/Battling/History/HistoryHolder.hpp | 44 +++++++++++----- src/Battling/Models/Battle.cpp | 42 ++++++++++++++++ src/Battling/Models/Battle.hpp | 2 + src/Battling/Models/BattleParty.hpp | 4 ++ src/Battling/Models/BattleSide.cpp | 7 +++ src/Battling/Models/BattleSide.hpp | 2 + src/Battling/Models/Creature.cpp | 49 ++++++++++++++++-- src/Battling/Models/Creature.hpp | 10 ++-- src/Battling/Models/CreatureParty.hpp | 12 +++++ src/Battling/Models/LearnedAttack.hpp | 6 +++ src/Battling/ScriptHandling/BattleScript.hpp | 2 + src/Battling/ScriptHandling/ScriptSet.hpp | 6 +++ tests/BattleTests/CloneTests.cpp | 50 +++++++++++++++++++ .../ScriptTests/ScriptAggregatorTests.cpp | 3 +- .../ScriptTests/ScriptSetTests.cpp | 4 +- .../ScriptTests/ScriptSourceTest.cpp | 3 +- 19 files changed, 236 insertions(+), 30 deletions(-) create mode 100644 tests/BattleTests/CloneTests.cpp diff --git a/.clang-tidy b/.clang-tidy index 884002b..ff1f909 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ 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: '' AnalyzeTemporaryDtors: false CheckOptions: diff --git a/src/Battling/History/HistoryElements/AttackUseHistory.hpp b/src/Battling/History/HistoryElements/AttackUseHistory.hpp index 177c1d6..15ab55b 100644 --- a/src/Battling/History/HistoryElements/AttackUseHistory.hpp +++ b/src/Battling/History/HistoryElements/AttackUseHistory.hpp @@ -9,7 +9,9 @@ namespace CreatureLib::Battling { protected: void Clear() override { - _attack.reset(); + if (_attack != nullptr) { + _attack.reset(); + } HistoryElement::Clear(); } diff --git a/src/Battling/History/HistoryElements/HistoryElement.hpp b/src/Battling/History/HistoryElements/HistoryElement.hpp index 1fe2140..4ebbef4 100644 --- a/src/Battling/History/HistoryElements/HistoryElement.hpp +++ b/src/Battling/History/HistoryElements/HistoryElement.hpp @@ -6,18 +6,24 @@ namespace CreatureLib::Battling { class HistoryElement { friend class HistoryHolder; - HistoryElement* _previous; + u8* _previousOffset = nullptr; protected: virtual void Clear() { - if (_previous != nullptr) { - _previous->Clear(); + if (_previousOffset != nullptr) { + GetPrevious()->Clear(); } } public: virtual HistoryElementKind GetKind() const noexcept = 0; - ArbUt::BorrowedPtr GetPrevious() const noexcept { return _previous; } + ArbUt::BorrowedPtr GetPrevious() const noexcept { + return reinterpret_cast((u8*)this - _previousOffset); + } + + ArbUt::BorrowedPtr GetPrevious() noexcept { + return reinterpret_cast((u8*)this - _previousOffset); + } }; } diff --git a/src/Battling/History/HistoryHolder.hpp b/src/Battling/History/HistoryHolder.hpp index 2259603..1eeea9f 100644 --- a/src/Battling/History/HistoryHolder.hpp +++ b/src/Battling/History/HistoryHolder.hpp @@ -4,13 +4,14 @@ #include "../../Library/Exceptions/CreatureException.hpp" #include "HistoryElements/HistoryElement.hpp" -template concept HistoryElementType = std::is_base_of::value; +template +concept HistoryElementType = std::is_base_of::value; namespace CreatureLib::Battling { class HistoryHolder { size_t _offset; size_t _capacity; - uint8_t* _memory = nullptr; + u8* _memory = nullptr; HistoryElement* _top = nullptr; static constexpr size_t initialSize = 2048; @@ -22,7 +23,7 @@ namespace CreatureLib::Battling { if (ptr == nullptr) { THROW("Out of memory."); } - _memory = static_cast(ptr); + _memory = static_cast(ptr); } HistoryHolder(const HistoryHolder&) = delete; HistoryHolder& operator=(const HistoryHolder&) = delete; @@ -33,19 +34,25 @@ namespace CreatureLib::Battling { free(_memory); } + void Resize() { + _capacity += stepSize; + auto newPtr = realloc(_memory, _capacity); + if (newPtr == nullptr) { + THROW("Out of memory."); + } + _memory = static_cast(newPtr); + } + template void Register(parameters... args) { if (_offset + sizeof(T) >= _capacity) { - _capacity += stepSize; - auto newPtr = realloc(_memory, _capacity); - if (newPtr == nullptr) { - THROW("Out of memory."); - } - _memory = static_cast(newPtr); + Resize(); } - uint8_t* ptr = _memory + _offset; + u8* ptr = _memory + _offset; T* element = new (ptr) T(args...); _offset += sizeof(T); - element->_previous = _top; + if (_top != nullptr) { + element->_previousOffset = (u8*)sizeof(T); + } _top = element; } @@ -56,10 +63,23 @@ namespace CreatureLib::Battling { while (c != nullptr) { if (c->GetKind() == HistoryElementKind::AttackUse) return c; - c = c->_previous; + c = c->GetPrevious(); } 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(malloc(_capacity)); + if (_top != nullptr) { + other._top = reinterpret_cast(other._memory + ((u8*)_top - _memory)); + } + } }; } diff --git a/src/Battling/Models/Battle.cpp b/src/Battling/Models/Battle.cpp index 88c83f1..9b86fe0 100644 --- a/src/Battling/Models/Battle.cpp +++ b/src/Battling/Models/Battle.cpp @@ -168,3 +168,45 @@ BattleScript* Battle::AddVolatileScript(const ArbUt::StringView& key) { BattleScript* Battle::AddVolatileScript(BattleScript* script) { return _volatile.Add(script); } void Battle::RemoveVolatileScript(BattleScript* script) { _volatile.Remove(script->GetName()); } void Battle::DisplayText(const ArbUt::StringView& text) { TriggerEventListener(text); } + +Battle* Battle::Clone() { + auto parties = ArbUt::List(_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; +} diff --git a/src/Battling/Models/Battle.hpp b/src/Battling/Models/Battle.hpp index ec746cc..996867e 100644 --- a/src/Battling/Models/Battle.hpp +++ b/src/Battling/Models/Battle.hpp @@ -131,6 +131,8 @@ namespace CreatureLib::Battling { } } } + + Battle* Clone(); }; } diff --git a/src/Battling/Models/BattleParty.hpp b/src/Battling/Models/BattleParty.hpp index 7cbcdf5..2c98521 100644 --- a/src/Battling/Models/BattleParty.hpp +++ b/src/Battling/Models/BattleParty.hpp @@ -13,6 +13,8 @@ namespace CreatureLib::Battling { BattleParty(CreatureParty* party, const ArbUt::List& responsibleIndices) : _party(party), _responsibleIndices(responsibleIndices) {} + virtual ~BattleParty() = default; + inline const ArbUt::BorrowedPtr& GetParty() const { return _party; } inline const ArbUt::List& GetResponsibleIndices() const { return _responsibleIndices; } @@ -40,6 +42,8 @@ namespace CreatureLib::Battling { } return false; } + + virtual BattleParty* Clone() { return new BattleParty(_party->Clone(), _responsibleIndices); } }; } diff --git a/src/Battling/Models/BattleSide.cpp b/src/Battling/Models/BattleSide.cpp index 42b120d..1041b62 100644 --- a/src/Battling/Models/BattleSide.cpp +++ b/src/Battling/Models/BattleSide.cpp @@ -123,3 +123,10 @@ bool BattleSide::SwapPositions(u8 a, u8 b) { _battle->TriggerEventListener(_index, a, b); return true; } +BattleSide* BattleSide::CloneWithoutCreatures() { + auto* side = new BattleSide(_index, _battle, _creaturesPerSide); + side->_choicesSet = _choicesSet; + _volatile.Clone(side->_volatile); + side->_hasFled = _hasFled; + return side; +} diff --git a/src/Battling/Models/BattleSide.hpp b/src/Battling/Models/BattleSide.hpp index 0522dd6..ffae166 100644 --- a/src/Battling/Models/BattleSide.hpp +++ b/src/Battling/Models/BattleSide.hpp @@ -87,6 +87,8 @@ namespace CreatureLib::Battling { uint8_t GetRandomCreatureIndex(); bool SwapPositions(u8 a, u8 b); + + BattleSide* CloneWithoutCreatures(); }; } diff --git a/src/Battling/Models/Creature.cpp b/src/Battling/Models/Creature.cpp index 0291fbd..84961e6 100644 --- a/src/Battling/Models/Creature.cpp +++ b/src/Battling/Models/Creature.cpp @@ -1,4 +1,5 @@ #include "Creature.hpp" +#include #include "../EventHooks/EventDataClasses.hpp" #include "../Models/Battle.hpp" #include "../ScriptHandling/ScriptMacros.hpp" @@ -6,11 +7,11 @@ using namespace CreatureLib; namespace CreatureLib::Battling { - Creature::Creature(ArbUt::BorrowedPtr library, + Creature::Creature(const ArbUt::BorrowedPtr& library, const ArbUt::BorrowedPtr& species, const ArbUt::BorrowedPtr& variant, level_int_t level, uint32_t experience, uint32_t uid, Library::Gender gender, uint8_t coloring, - const ArbUt::OptionalBorrowedPtr heldItem, const std::string& nickname, + ArbUt::OptionalBorrowedPtr heldItem, std::string nickname, const Library::TalentIndex& talent, const std::vector& attacks, bool allowedExperienceGain) : _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 changed = false; auto oldValue = this->_statBoost.GetStat(stat); - if (diffAmount > 0) + if (diffAmount > 0) { changed = this->_statBoost.IncreaseStatBy(stat, diffAmount); - else + } else if (diffAmount < 0) { changed = this->_statBoost.DecreaseStatBy(stat, -diffAmount); + } if (this->GetBattle().HasValue()) { auto newValue = this->_statBoost.GetStat(stat); this->GetBattle().GetValue()->TriggerEventListener(this, stat, oldValue, newValue); @@ -123,7 +125,7 @@ namespace CreatureLib::Battling { } void Creature::RecalculateFlatStats() { - auto& statCalc = this->_library->GetStatCalculator(); + const auto& statCalc = this->_library->GetStatCalculator(); this->_flatStats = statCalc->CalculateFlatStats(this); RecalculateBoostedStats(); } @@ -329,4 +331,41 @@ namespace CreatureLib::Battling { _attacks.Set(index, attack); } + Creature* Creature::Clone() { + auto attacks = std::vector(_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(_activeTalent->Clone()); + } + c->_hasOverridenTalent = _hasOverridenTalent; + c->_overridenTalentName = _overridenTalentName; + if (_status != nullptr) { + c->_status = std::unique_ptr(_status->Clone()); + } + _volatile.Clone(c->_volatile); + c->_types = std::vector(_types); + c->RecalculateFlatStats(); + + return c; + } + } \ No newline at end of file diff --git a/src/Battling/Models/Creature.hpp b/src/Battling/Models/Creature.hpp index 1da863a..5366f70 100644 --- a/src/Battling/Models/Creature.hpp +++ b/src/Battling/Models/Creature.hpp @@ -43,7 +43,7 @@ namespace CreatureLib::Battling { ArbUt::OptionalBorrowedPtr _side = nullptr; bool _onBattleField = false; - std::string _nickname = ""; + std::string _nickname; CreatureLib::Library::TalentIndex _talentIndex; std::unique_ptr _activeTalent = nullptr; @@ -57,17 +57,17 @@ namespace CreatureLib::Battling { std::unique_ptr _status = nullptr; ScriptSet _volatile = {}; - std::vector _types; + std::vector _types; private: void OnFaint(); public: - Creature(ArbUt::BorrowedPtr library, + Creature(const ArbUt::BorrowedPtr& library, const ArbUt::BorrowedPtr& species, const ArbUt::BorrowedPtr& variant, level_int_t level, uint32_t experience, uint32_t uid, Library::Gender gender, uint8_t coloring, - const ArbUt::OptionalBorrowedPtr heldItem, const std::string& nickname, + ArbUt::OptionalBorrowedPtr heldItem, std::string nickname, const Library::TalentIndex& talent, const std::vector& attacks, bool allowedExperienceGain = true); @@ -186,6 +186,8 @@ namespace CreatureLib::Battling { void RecalculateBoostedStat(Library::Statistic); // endregion + + virtual Creature* Clone(); }; } diff --git a/src/Battling/Models/CreatureParty.hpp b/src/Battling/Models/CreatureParty.hpp index 65999cc..acf111e 100644 --- a/src/Battling/Models/CreatureParty.hpp +++ b/src/Battling/Models/CreatureParty.hpp @@ -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; + } }; } diff --git a/src/Battling/Models/LearnedAttack.hpp b/src/Battling/Models/LearnedAttack.hpp index 531abd8..e1cf674 100644 --- a/src/Battling/Models/LearnedAttack.hpp +++ b/src/Battling/Models/LearnedAttack.hpp @@ -28,6 +28,12 @@ namespace CreatureLib::Battling { virtual void DecreaseUses(uint8_t amount) noexcept; virtual void RestoreUses(uint8_t amount) noexcept; virtual void RestoreAllUses() noexcept; + + virtual LearnedAttack* Clone() { + auto* attack = new LearnedAttack(_attack, _maxUses, _learnMethod); + attack->_remainingUses = _remainingUses; + return attack; + } }; } diff --git a/src/Battling/ScriptHandling/BattleScript.hpp b/src/Battling/ScriptHandling/BattleScript.hpp index 7420fc7..32608d6 100644 --- a/src/Battling/ScriptHandling/BattleScript.hpp +++ b/src/Battling/ScriptHandling/BattleScript.hpp @@ -18,6 +18,8 @@ namespace CreatureLib::Battling { virtual ~BattleScript() = default; + virtual BattleScript* Clone() = 0; + virtual void Stack(){}; virtual void OnRemove(){}; diff --git a/src/Battling/ScriptHandling/ScriptSet.hpp b/src/Battling/ScriptHandling/ScriptSet.hpp index f4d86f8..c7ebca8 100644 --- a/src/Battling/ScriptHandling/ScriptSet.hpp +++ b/src/Battling/ScriptHandling/ScriptSet.hpp @@ -15,6 +15,12 @@ namespace CreatureLib::Battling { static constexpr size_t defaultCapacity = 8; ScriptSet() : _scripts(defaultCapacity), _lookup(defaultCapacity){}; + void Clone(ScriptSet& s) { + for (auto* script : _scripts) { + s.Add(script->Clone()); + } + } + BattleScript* Add(BattleScript* script) { auto v = _lookup.TryGet(script->GetName()); if (v.has_value()) { diff --git a/tests/BattleTests/CloneTests.cpp b/tests/BattleTests/CloneTests.cpp new file mode 100644 index 0000000..363ffea --- /dev/null +++ b/tests/BattleTests/CloneTests.cpp @@ -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(); + 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(nullptr); + clone->RegisterHistoryElement(nullptr); + + delete c; + delete clone; +} + +#endif \ No newline at end of file diff --git a/tests/BattleTests/ScriptTests/ScriptAggregatorTests.cpp b/tests/BattleTests/ScriptTests/ScriptAggregatorTests.cpp index 98e0710..bee435a 100644 --- a/tests/BattleTests/ScriptTests/ScriptAggregatorTests.cpp +++ b/tests/BattleTests/ScriptTests/ScriptAggregatorTests.cpp @@ -12,10 +12,11 @@ private: ArbUt::StringView _name; 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; } void TestMethod(int& runCount) { runCount++; } + BattleScript* Clone() override { return new TestScript(_name); } }; TEST_CASE("Script Aggregator properly iterates containing script.") { diff --git a/tests/BattleTests/ScriptTests/ScriptSetTests.cpp b/tests/BattleTests/ScriptTests/ScriptSetTests.cpp index a76f417..adafdaf 100644 --- a/tests/BattleTests/ScriptTests/ScriptSetTests.cpp +++ b/tests/BattleTests/ScriptTests/ScriptSetTests.cpp @@ -12,8 +12,10 @@ private: ArbUt::StringView _name; 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; } + + BattleScript* Clone() override { return new TestScript(_name); } }; TEST_CASE("Empty script set count == 0") { diff --git a/tests/BattleTests/ScriptTests/ScriptSourceTest.cpp b/tests/BattleTests/ScriptTests/ScriptSourceTest.cpp index 3c0a696..81b3d42 100644 --- a/tests/BattleTests/ScriptTests/ScriptSourceTest.cpp +++ b/tests/BattleTests/ScriptTests/ScriptSourceTest.cpp @@ -11,10 +11,11 @@ private: ArbUt::StringView _name; 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; } void TestMethod(int& runCount) { runCount++; } + BattleScript* Clone() override { return new TestScript(_name); } }; class ScriptSourceWithScriptPtr : public ScriptSource {