#include "Creature.hpp" #include #include "../EventHooks/EventDataClasses.hpp" #include "../History/HistoryElements/DamageHistory.hpp" #include "../Models/Battle.hpp" #include "../ScriptHandling/ScriptMacros.hpp" using namespace CreatureLib; namespace CreatureLib::Battling { 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, ArbUt::OptionalBorrowedPtr heldItem, std::optional nickname, const Library::TalentIndex& talent, const std::vector& attacks, bool allowedExperienceGain) : _library(library), _species(species), _variant(variant), _level(level), _experience(experience), _uniqueIdentifier(uid), _gender(gender), _coloring(coloring), _heldItem(heldItem), _nickname(std::move(nickname)), _talentIndex(talent), _hasOverridenTalent(false), _attacks(attacks), _allowedExperienceGain(allowedExperienceGain) { _activeTalent = std::unique_ptr(_library->LoadScript(this, ScriptCategory::Talent, GetActiveTalent())); for (auto t : _variant->GetTypes()) { _types.push_back(t); } } void Creature::ChangeSpecies(const ArbUt::BorrowedPtr& species, const ArbUt::BorrowedPtr& variant) { _species = species; // If the creature is genderless, but it's new species is not, we want to set its gender if (_gender != CreatureLib::Library::Gender::Genderless && _species->GetGenderRate() != -1) { // If we are currently in battle, use the battle random so we can get predictable events. if (_battleData.Battle.HasValue()) { _gender = _species->GetRandomGender(_battleData.Battle.GetValue()->GetRandom()->GetRNG()); } // Else create a new random. else { ArbUt::Random rand; _gender = _species->GetRandomGender(rand); } } // Else if the new species is genderless, but the creature has a gender, make the creature genderless. else if (_species->GetGenderRate() == -1 && _gender != CreatureLib::Library::Gender::Genderless) { _gender = CreatureLib::Library::Gender::Genderless; } if (_battleData.Battle.HasValue()) { _battleData.Battle.GetValue()->TriggerEventListener(this, _species); } ChangeVariant(variant); } void Creature::ChangeVariant(const ArbUt::BorrowedPtr& variant) { _variant = variant; // Set the types to the new variant. _types.clear(); for (auto t : variant->GetTypes()) { _types.push_back(t); } // Grab the new active talent. _activeTalent = std::unique_ptr(_library->LoadScript(this, ScriptCategory::Talent, GetActiveTalent())); // We modify the health of the creature by the change in its max health. auto prevHealth = GetBoostedStat(CreatureLib::Library::Statistic::Health); RecalculateFlatStats(); int32_t diffHealth = GetBoostedStat(CreatureLib::Library::Statistic::Health) - prevHealth; if (_currentHealth < static_cast(INT32_MAX) && static_cast(_currentHealth) < -diffHealth) { _currentHealth = 0; } else { _currentHealth += diffHealth; } // TODO: consider variant specific attacks? if (_battleData.Battle.HasValue()) { _battleData.Battle.GetValue()->TriggerEventListener(this, _variant); } } void Creature::ChangeLevelBy(int8_t amount) { auto level = _level + amount; if (level > _library->GetSettings()->GetMaximalLevel()) level = _library->GetSettings()->GetMaximalLevel(); if (level < 1) level = 1; _level = level; _experience = _library->GetGrowthRateLibrary()->CalculateExperience(_species->GetGrowthRate(), _level); RecalculateFlatStats(); } const ArbUt::StringView& Creature::GetActiveTalent() const { if (_hasOverridenTalent) { return _overridenTalentName; } return _variant->GetTalent(_talentIndex); } void Creature::SetBattleData(const ArbUt::BorrowedPtr& battle, const ArbUt::BorrowedPtr& side) { _battleData.Battle = battle.GetRaw(); _battleData.Side = side.GetRaw(); this->ResetActiveScripts(); } void Creature::ClearBattleData() noexcept { _battleData = {}; _battleData.SeenOpponents = {}; ResetActiveScripts(); } bool Creature::ChangeStatBoost(Library::Statistic stat, int8_t diffAmount) { bool changed = false; auto oldValue = this->_statBoost.GetStat(stat); if (diffAmount > 0) { changed = this->_statBoost.IncreaseStatBy(stat, diffAmount); } 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); } this->RecalculateBoostedStat(stat); return changed; } void Creature::RecalculateFlatStats() { const auto& statCalc = this->_library->GetStatCalculator(); this->_flatStats = statCalc->CalculateFlatStats(this); RecalculateBoostedStats(); } void Creature::RecalculateBoostedStats() { this->_boostedStats = this->_library->GetStatCalculator()->CalculateFlatStats(this); } void Creature::RecalculateFlatStat(Library::Statistic stat) { auto s = this->_library->GetStatCalculator()->CalculateFlatStat(this, stat); this->_flatStats.SetStat(stat, s); RecalculateBoostedStat(stat); } void Creature::RecalculateBoostedStat(Library::Statistic stat) { auto s = this->_library->GetStatCalculator()->CalculateBoostedStat(this, stat); this->_boostedStats.SetStat(stat, s); } bool Creature::IsUsable() const noexcept { return !this->IsFainted(); } bool Creature::IsFainted() const noexcept { return this->_currentHealth == 0; } void Creature::OnFaint() { EnsureNotNull(_battleData.Battle) EnsureNotNull(_battleData.Side) // HOOK: On Faint if (_battleData.Battle.HasValue()) { _battleData.Battle.GetValue()->TriggerEventListener(this); } _library->GetExperienceLibrary()->HandleExperienceGain(this, _battleData.SeenOpponents); if (_battleData.Battle.HasValue() && _battleData.Side.HasValue()) { auto sideIndex = _battleData.Side.GetValue()->GetCreatureIndex(this); if (!_battleData.Battle.GetValue()->CanSlotBeFilled(_battleData.Side.GetValue()->GetSideIndex(), sideIndex)) { _battleData.Side.GetValue()->MarkSlotAsUnfillable(this); } _battleData.Battle.GetValue()->ValidateBattleState(); } } void Creature::Damage(uint32_t damage, DamageSource source) { if (damage > _currentHealth) { damage = _currentHealth; } if (damage == 0) return; // HOOK: On Damage auto newHealth = _currentHealth - damage; auto battle = this->GetBattle(); if (battle.HasValue()) { battle.GetValue()->TriggerEventListener(this, source, _currentHealth, newHealth); battle.GetValue()->RegisterHistoryElement(this, damage, source); } _currentHealth = newHealth; if (IsFainted() && damage > 0 && battle != nullptr) { OnFaint(); } } void Creature::Heal(uint32_t amount, bool canRevive) { if (_currentHealth == 0 && !canRevive) { return; } if (amount > GetMaxHealth() - _currentHealth) { amount = GetMaxHealth() - _currentHealth; } // HOOK: On Heal auto newHealth = _currentHealth + amount; auto battle = this->GetBattle(); if (battle.HasValue()) { battle.GetValue()->TriggerEventListener(this, _currentHealth, newHealth); } _currentHealth = newHealth; } void Creature::RestoreAllAttackUses() noexcept { for (auto& a : _attacks) { if (a != nullptr) { a->RestoreAllUses(); } } } void Creature::OverrideActiveTalent(const ArbUt::StringView& talent) { _hasOverridenTalent = true; if (_activeTalent != nullptr) { _activeTalent->OnRemove(); _activeTalent.reset(this->_library->LoadScript(this, ScriptCategory::Talent, talent)); } _overridenTalentName = talent; } const std::vector& Creature::GetTypes() const noexcept { return _types; } bool Creature::HasType(uint8_t type) const noexcept { return std::find(_types.begin(), _types.end(), type) != _types.end(); } size_t Creature::ScriptCount() const { auto c = 3; if (_battleData.Side.HasValue()) { c += _battleData.Side.GetValue()->ScriptCount(); } return c; } void Creature::GetActiveScripts(ArbUt::List& scripts) { GetOwnScripts(scripts); if (_battleData.Side.HasValue()) { _battleData.Side.GetValue()->GetActiveScripts(scripts); } } void Creature::GetOwnScripts(ArbUt::List& scripts) { scripts.Append(ScriptWrapper::FromScript(&_activeTalent)); scripts.Append(ScriptWrapper::FromScript(&_status)); scripts.Append(ScriptWrapper::FromSet(&_volatile)); } void Creature::ClearVolatileScripts() { _volatile.Clear(); } void Creature::AddExperience(uint32_t amount) { auto maxLevel = _library->GetSettings()->GetMaximalLevel(); if (_level >= maxLevel) { return; } auto exp = _experience + amount; auto level = _library->GetGrowthRateLibrary()->CalculateLevel(this->GetSpecies()->GetGrowthRate(), exp); if (level >= maxLevel) { exp = _library->GetGrowthRateLibrary()->CalculateExperience(this->GetSpecies()->GetGrowthRate(), maxLevel); } if (_battleData.Battle.HasValue()) { _battleData.Battle.GetValue()->TriggerEventListener(this, _experience, exp); } _experience = exp; _level = level; } ArbUt::OptionalBorrowedPtr Creature::GetDisplaySpecies() const noexcept { auto species = _displaySpecies; if (!species.HasValue()) species = _species.GetRaw(); return species; } ArbUt::OptionalBorrowedPtr Creature::GetDisplayVariant() const noexcept { auto variant = _displayVariant; if (!variant.HasValue()) variant = _variant.GetRaw(); return variant; } void Creature::SetHeldItem(const ArbUt::BasicStringView& itemName) { auto v = _library->GetItemLibrary()->TryGet(itemName.GetHash()); if (!v.has_value()) { THROW("Item not found '" << itemName.c_str() << "'."); } _heldItem = v.value(); } void Creature::SetHeldItem(uint32_t itemNameHash) { auto v = _library->GetItemLibrary()->TryGet(itemNameHash); if (!v.has_value()) { THROW("Item not found."); } _heldItem = v.value(); } BattleScript* Creature::AddVolatileScript(const ArbUt::StringView& name) { auto script = _volatile.Get(name); if (script.HasValue()) { script.GetValue()->Stack(); return script.GetValue(); } script = this->_library->LoadScript(this, ScriptCategory::Creature, name); if (!script.HasValue()) { THROW("Invalid volatile script requested for creature: '" << name.c_str() << "'."); } return _volatile.Add(script.GetValue()); } BattleScript* Creature::AddVolatileScript(BattleScript* script) { return _volatile.Add(script); } void Creature::RemoveVolatileScript(const ArbUt::BasicStringView& name) { _volatile.Remove(name); } void Creature::RemoveVolatileScript(BattleScript* script) { _volatile.Remove(script->GetName()); } bool Creature::HasVolatileScript(const ArbUt::BasicStringView& name) const { return _volatile.Has(name); } void Creature::AddAttack(LearnedAttack* attack) { for (size_t i = 0; i < _attacks.Count(); i++) { if (!_attacks[i].HasValue()) { _attacks.Set(i, attack); return; } } if (_attacks.Count() < _library->GetStaticLib()->GetSettings()->GetMaximalAttacks()) { _attacks.Append(attack); return; } THROW("Can't add attack. The creature already has the maximum amount of attacks."); } uint8_t Creature::GetAvailableAttackSlot() const noexcept { for (uint8_t i = 0; i < (uint8_t)_attacks.Count(); i++) { if (_attacks[i] == nullptr) { return i; } } if (_attacks.Count() < _library->GetStaticLib()->GetSettings()->GetMaximalAttacks()) { return _attacks.Count(); } return -1; } void Creature::ReplaceAttack(size_t index, LearnedAttack* attack) { if (_attacks.Count() <= index) { if (_attacks.Count() < _library->GetStaticLib()->GetSettings()->GetMaximalAttacks()) { _attacks.Append(attack); } THROW("Can't replace attack at index " << index << ". Number of attacks is " << _attacks.Count() << "."); } _attacks.Set(index, attack); } Creature* Creature::Clone() const { 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->_battleData.Battle = _battleData.Battle; c->_battleData.Side = _battleData.Side; c->_battleData.OnBattleField = _battleData.OnBattleField; c->_battleData.Index = _battleData.Index; if (_activeTalent != nullptr) { c->_activeTalent = std::unique_ptr(_activeTalent->Clone(c)); } c->_hasOverridenTalent = _hasOverridenTalent; c->_overridenTalentName = _overridenTalentName; if (_status != nullptr) { c->_status = std::unique_ptr(_status->Clone(c)); } _volatile.Clone(c, c->_volatile); c->_types = std::vector(_types); c->RecalculateFlatStats(); return c; } } void CreatureLib::Battling::Creature::SetStatus(const ArbUt::StringView& name) { if (_status != nullptr) { _status->OnRemove(); } _status = std::unique_ptr(_library->LoadScript(this, ScriptCategory::Status, name)); if (_battleData.Battle.HasValue()) { _battleData.Battle.GetValue()->TriggerEventListener(this, name); } } void CreatureLib::Battling::Creature::ClearStatus() { if (_status == nullptr) { return; } _status->OnRemove(); _status = nullptr; if (_battleData.Battle.HasValue()) { _battleData.Battle.GetValue()->TriggerEventListener(this, ""_cnc); } } Battling::Creature::~Creature() { if (_battleData.OnBattleField && _battleData.Side.HasValue()) { _battleData.Side.GetValue()->ForceClearCreature(_battleData.Index.GetCreatureIndex()); } }