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-*,
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:

View File

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

View File

@ -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<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 "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 {
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<uint8_t*>(ptr);
_memory = static_cast<u8*>(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<u8*>(newPtr);
}
template <HistoryElementType T, class... parameters> 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<uint8_t*>(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<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); }
void Battle::RemoveVolatileScript(BattleScript* script) { _volatile.Remove(script->GetName()); }
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)
: _party(party), _responsibleIndices(responsibleIndices) {}
virtual ~BattleParty() = default;
inline const ArbUt::BorrowedPtr<CreatureParty>& GetParty() const { return _party; }
inline const ArbUt::List<CreatureIndex>& GetResponsibleIndices() const { return _responsibleIndices; }
@ -40,6 +42,8 @@ namespace CreatureLib::Battling {
}
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);
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();
bool SwapPositions(u8 a, u8 b);
BattleSide* CloneWithoutCreatures();
};
}

View File

@ -1,4 +1,5 @@
#include "Creature.hpp"
#include <utility>
#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<const BattleLibrary> library,
Creature::Creature(const ArbUt::BorrowedPtr<const BattleLibrary>& library,
const ArbUt::BorrowedPtr<const Library::CreatureSpecies>& species,
const ArbUt::BorrowedPtr<const Library::SpeciesVariant>& variant, level_int_t level,
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,
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<ChangeStatBoostEvent>(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<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;
bool _onBattleField = false;
std::string _nickname = "";
std::string _nickname;
CreatureLib::Library::TalentIndex _talentIndex;
std::unique_ptr<BattleScript> _activeTalent = nullptr;
@ -57,17 +57,17 @@ namespace CreatureLib::Battling {
std::unique_ptr<BattleScript> _status = nullptr;
ScriptSet _volatile = {};
std::vector<uint8_t> _types;
std::vector<u8> _types;
private:
void OnFaint();
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::SpeciesVariant>& variant, level_int_t level,
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,
bool allowedExperienceGain = true);
@ -186,6 +186,8 @@ namespace CreatureLib::Battling {
void RecalculateBoostedStat(Library::Statistic);
// 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 RestoreUses(uint8_t amount) 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* Clone() = 0;
virtual void Stack(){};
virtual void OnRemove(){};

View File

@ -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()) {

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;
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.") {

View File

@ -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") {

View File

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