2019-11-28 11:55:22 +00:00
|
|
|
#include "Creature.hpp"
|
2019-11-05 13:31:54 +00:00
|
|
|
#include <algorithm>
|
2019-11-10 16:08:42 +00:00
|
|
|
#include <utility>
|
2019-10-24 11:37:55 +00:00
|
|
|
#include "../Models/Battle.hpp"
|
2020-02-19 14:32:43 +00:00
|
|
|
#include "../ScriptHandling/ScriptMacros.hpp"
|
2019-10-24 11:37:55 +00:00
|
|
|
|
|
|
|
using namespace CreatureLib;
|
|
|
|
|
2019-12-05 11:56:41 +00:00
|
|
|
Battling::Creature::Creature(const BattleLibrary* library, const Library::CreatureSpecies* species,
|
2019-12-27 11:19:38 +00:00
|
|
|
const Library::SpeciesVariant* variant, uint8_t level, uint32_t experience, uint32_t uid,
|
|
|
|
Library::Gender gender, uint8_t coloring, const Library::Item* heldItem,
|
2020-03-02 12:59:49 +00:00
|
|
|
std::string nickname, const TalentIndex& talent, std::vector<LearnedAttack*> attacks)
|
2020-01-12 16:04:42 +00:00
|
|
|
: _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(std::move(attacks)) {
|
2020-02-27 17:23:23 +00:00
|
|
|
|
2020-02-20 12:05:15 +00:00
|
|
|
_activeTalent = _library->LoadScript(ScriptCategory::Talent, GetActiveTalent());
|
2020-02-27 17:23:23 +00:00
|
|
|
if (_nickname.empty()) {
|
|
|
|
_nickname = species->GetName().std_str();
|
|
|
|
}
|
2019-11-18 17:41:55 +00:00
|
|
|
}
|
2019-10-24 11:37:55 +00:00
|
|
|
|
|
|
|
void Battling::Creature::ChangeLevel(int8_t amount) {
|
2020-01-12 16:04:42 +00:00
|
|
|
this->_level += amount;
|
2019-10-24 11:37:55 +00:00
|
|
|
RecalculateFlatStats();
|
|
|
|
}
|
|
|
|
|
2020-02-28 18:23:24 +00:00
|
|
|
const ConstString& Battling::Creature::GetActiveTalent() const {
|
2019-11-28 11:55:22 +00:00
|
|
|
if (_hasOverridenTalent) {
|
2019-11-18 17:41:55 +00:00
|
|
|
return _overridenTalentName;
|
|
|
|
}
|
2020-01-12 16:04:42 +00:00
|
|
|
return _variant->GetTalent(_talentIndex);
|
2019-10-24 11:37:55 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
void Battling::Creature::SetBattleData(Battling::Battle* battle, Battling::BattleSide* side) {
|
2019-10-29 10:19:25 +00:00
|
|
|
_battle = battle;
|
|
|
|
_side = side;
|
2020-02-17 09:05:32 +00:00
|
|
|
this->ResetActiveScripts();
|
2019-10-29 10:19:25 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
// region Stat APIs
|
2019-10-24 11:37:55 +00:00
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
void Battling::Creature::ChangeStatBoost(Core::Statistic stat, int8_t diffAmount) {
|
2019-10-24 11:37:55 +00:00
|
|
|
if (diffAmount > 0)
|
|
|
|
this->_statBoost.IncreaseStatBy(stat, diffAmount);
|
|
|
|
else
|
2020-01-05 13:57:17 +00:00
|
|
|
this->_statBoost.DecreaseStatBy(stat, -diffAmount);
|
2019-10-24 11:37:55 +00:00
|
|
|
this->RecalculateBoostedStat(stat);
|
|
|
|
}
|
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
uint32_t Battling::Creature::GetFlatStat(Core::Statistic stat) const { return _flatStats.GetStat(stat); }
|
2019-10-24 11:37:55 +00:00
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
uint32_t Battling::Creature::GetBoostedStat(Core::Statistic stat) const { return _boostedStats.GetStat(stat); }
|
2019-10-24 11:37:55 +00:00
|
|
|
|
2020-01-12 16:04:42 +00:00
|
|
|
uint32_t Battling::Creature::GetBaseStat(Core::Statistic stat) const { return _variant->GetStatistic(stat); }
|
2019-10-24 11:37:55 +00:00
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
int8_t Battling::Creature::GetStatBoost(Core::Statistic stat) const { return _statBoost.GetStat(stat); }
|
2019-11-05 13:31:54 +00:00
|
|
|
|
2019-10-24 11:37:55 +00:00
|
|
|
void Battling::Creature::RecalculateFlatStats() {
|
2019-12-05 11:56:41 +00:00
|
|
|
auto statCalc = this->_library->GetStatCalculator();
|
|
|
|
this->_flatStats = statCalc->CalculateFlatStats(this);
|
2019-10-24 11:37:55 +00:00
|
|
|
RecalculateBoostedStats();
|
|
|
|
}
|
|
|
|
void Battling::Creature::RecalculateBoostedStats() {
|
|
|
|
this->_boostedStats = this->_library->GetStatCalculator()->CalculateFlatStats(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Battling::Creature::RecalculateFlatStat(Core::Statistic stat) {
|
|
|
|
auto s = this->_library->GetStatCalculator()->CalculateFlatStat(this, stat);
|
|
|
|
this->_flatStats.SetStat(stat, s);
|
|
|
|
RecalculateBoostedStat(stat);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Battling::Creature::RecalculateBoostedStat(Core::Statistic stat) {
|
|
|
|
auto s = this->_library->GetStatCalculator()->CalculateBoostedStat(this, stat);
|
|
|
|
this->_boostedStats.SetStat(stat, s);
|
|
|
|
}
|
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
// endregion
|
2019-11-02 12:57:43 +00:00
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
Battling::Battle* Battling::Creature::GetBattle() const { return _battle; }
|
2019-10-29 10:19:25 +00:00
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
Battling::BattleSide* Battling::Creature::GetBattleSide() const { return _side; }
|
2019-10-29 10:19:25 +00:00
|
|
|
|
2020-01-12 16:04:42 +00:00
|
|
|
bool Battling::Creature::IsFainted() const { return this->_currentHealth <= 0; }
|
2019-11-03 12:47:50 +00:00
|
|
|
|
2019-12-14 12:28:23 +00:00
|
|
|
void Battling::Creature::OnFaint() {
|
|
|
|
// HOOK: On Faint
|
|
|
|
_library->GetExperienceLibrary()->HandleExperienceGain(this, _seenOpponents);
|
|
|
|
|
|
|
|
if (!_battle->CanSlotBeFilled(_side->GetSideIndex(), _side->GetCreatureIndex(this))) {
|
|
|
|
_side->MarkSlotAsUnfillable(this);
|
|
|
|
}
|
|
|
|
_battle->ValidateBattleState();
|
|
|
|
}
|
|
|
|
|
2019-11-03 12:47:50 +00:00
|
|
|
void Battling::Creature::Damage(uint32_t damage, Battling::DamageSource source) {
|
2020-01-12 16:04:42 +00:00
|
|
|
if (damage > _currentHealth) {
|
|
|
|
damage = _currentHealth;
|
2019-11-03 12:47:50 +00:00
|
|
|
}
|
|
|
|
// HOOK: On Damage
|
2020-01-12 16:04:42 +00:00
|
|
|
auto newHealth = _currentHealth - damage;
|
2020-02-02 11:34:02 +00:00
|
|
|
auto battle = this->GetBattle();
|
|
|
|
if (battle != nullptr) {
|
|
|
|
battle->TriggerEventListener(new DamageEvent(this, source, _currentHealth, newHealth));
|
|
|
|
}
|
2020-01-12 16:04:42 +00:00
|
|
|
_currentHealth = newHealth;
|
2019-12-07 20:56:29 +00:00
|
|
|
|
2019-12-07 21:52:43 +00:00
|
|
|
if (IsFainted() && damage > 0) {
|
2019-12-14 12:28:23 +00:00
|
|
|
OnFaint();
|
2019-12-07 20:56:29 +00:00
|
|
|
}
|
2019-11-03 12:47:50 +00:00
|
|
|
}
|
2019-11-05 07:06:12 +00:00
|
|
|
|
2020-02-13 13:48:09 +00:00
|
|
|
void Battling::Creature::Heal(uint32_t amount) {
|
|
|
|
if (amount > GetMaxHealth() - _currentHealth) {
|
|
|
|
amount = GetMaxHealth() - _currentHealth;
|
|
|
|
}
|
|
|
|
// HOOK: On Heal
|
|
|
|
auto newHealth = _currentHealth + amount;
|
|
|
|
auto battle = this->GetBattle();
|
|
|
|
if (battle != nullptr) {
|
|
|
|
battle->TriggerEventListener(new HealEvent(this, _currentHealth, newHealth));
|
|
|
|
}
|
|
|
|
_currentHealth = newHealth;
|
|
|
|
}
|
|
|
|
|
2020-02-28 18:23:24 +00:00
|
|
|
void Battling::Creature::OverrideActiveTalent(const ConstString& talent) {
|
2019-11-18 17:41:55 +00:00
|
|
|
_hasOverridenTalent = true;
|
2020-02-05 13:52:50 +00:00
|
|
|
_activeTalent->OnRemove();
|
|
|
|
delete _activeTalent;
|
2019-11-18 17:41:55 +00:00
|
|
|
_overridenTalentName = talent;
|
2020-02-20 12:05:15 +00:00
|
|
|
_activeTalent = this->_library->LoadScript(ScriptCategory::Talent, talent);
|
2019-11-18 17:41:55 +00:00
|
|
|
}
|
|
|
|
|
2019-11-05 07:06:12 +00:00
|
|
|
const std::vector<uint8_t>& Battling::Creature::GetTypes() const {
|
2019-11-28 11:55:22 +00:00
|
|
|
// HOOK: override types.
|
2020-01-12 16:04:42 +00:00
|
|
|
return this->_variant->GetTypes();
|
2019-11-05 07:06:12 +00:00
|
|
|
}
|
2019-11-05 13:31:54 +00:00
|
|
|
|
|
|
|
bool Battling::Creature::HasType(uint8_t type) const {
|
|
|
|
auto t = GetTypes();
|
|
|
|
return std::find(t.begin(), t.end(), type) != t.end();
|
|
|
|
}
|
2019-11-10 13:32:05 +00:00
|
|
|
|
2019-11-28 11:55:22 +00:00
|
|
|
void Battling::Creature::GetActiveScripts(std::vector<ScriptWrapper>& scripts) {
|
2019-11-17 10:25:52 +00:00
|
|
|
scripts.emplace_back(&_activeTalent);
|
2019-11-10 16:08:42 +00:00
|
|
|
scripts.emplace_back(&_status);
|
|
|
|
scripts.emplace_back(&_volatile);
|
2020-02-17 08:57:15 +00:00
|
|
|
if (_side != nullptr) {
|
|
|
|
_side->GetActiveScripts(scripts);
|
|
|
|
}
|
2019-11-10 13:32:05 +00:00
|
|
|
}
|
2019-12-14 11:15:30 +00:00
|
|
|
void Battling::Creature::ClearVolatileScripts() { _volatile.Clear(); }
|
2019-12-14 12:28:23 +00:00
|
|
|
void Battling::Creature::AddExperience(uint32_t amount) {
|
2020-02-01 12:30:51 +00:00
|
|
|
auto maxLevel = _library->GetSettings()->GetMaximalLevel();
|
2020-01-12 16:04:42 +00:00
|
|
|
if (_level >= maxLevel) {
|
2019-12-14 12:28:23 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-01-12 16:04:42 +00:00
|
|
|
auto exp = _experience + amount;
|
2019-12-14 12:28:23 +00:00
|
|
|
auto level = _library->GetGrowthRateLibrary()->CalculateLevel(this->GetSpecies()->GetGrowthRate(), exp);
|
|
|
|
if (level >= maxLevel) {
|
|
|
|
exp = _library->GetGrowthRateLibrary()->CalculateExperience(this->GetSpecies()->GetGrowthRate(), maxLevel);
|
|
|
|
}
|
2020-01-12 16:04:42 +00:00
|
|
|
_experience = exp;
|
|
|
|
_level = level;
|
2019-12-14 12:28:23 +00:00
|
|
|
}
|
2020-02-03 15:49:28 +00:00
|
|
|
const Library::CreatureSpecies* Battling::Creature::GetDisplaySpecies() const {
|
|
|
|
auto species = _displaySpecies;
|
|
|
|
if (species == nullptr)
|
|
|
|
species = _species;
|
2019-12-21 13:32:45 +00:00
|
|
|
return species;
|
|
|
|
}
|
2020-02-03 15:49:28 +00:00
|
|
|
const Library::SpeciesVariant* Battling::Creature::GetDisplayVariant() const {
|
|
|
|
auto variant = _displayVariant;
|
|
|
|
if (variant == nullptr)
|
|
|
|
variant = _variant;
|
2019-12-21 13:32:45 +00:00
|
|
|
return variant;
|
|
|
|
}
|
2020-02-27 17:23:23 +00:00
|
|
|
void Battling::Creature::SetHeldItem(const Arbutils::CaseInsensitiveConstString& itemName) {
|
2020-02-17 16:16:28 +00:00
|
|
|
const Library::Item* item;
|
|
|
|
if (!_library->GetItemLibrary()->TryGet(itemName, item)) {
|
|
|
|
throw CreatureException("Item not found.");
|
|
|
|
}
|
|
|
|
_heldItem = item;
|
|
|
|
}
|
2020-02-28 18:23:24 +00:00
|
|
|
void Battling::Creature::AddVolatileScript(const ConstString& name) {
|
2020-02-22 15:01:01 +00:00
|
|
|
auto script = _volatile.Get(name);
|
|
|
|
if (script != nullptr) {
|
|
|
|
script->Stack();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
script = this->_library->LoadScript(ScriptCategory::Creature, name);
|
2020-02-22 14:53:51 +00:00
|
|
|
_volatile.Add(script);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Battling::Creature::AddVolatileScript(Script* script) { _volatile.Add(script); }
|
2020-02-28 18:23:24 +00:00
|
|
|
void Battling::Creature::RemoveVolatileScript(const ConstString& name) { _volatile.Remove(name); }
|
2020-02-23 10:11:47 +00:00
|
|
|
void Battling::Creature::RemoveVolatileScript(Battling::Script* script) { _volatile.Remove(script->GetName()); }
|
2020-02-28 18:23:24 +00:00
|
|
|
void Battling::Creature::HasVolatileScript(const ConstString& name) const { _volatile.Has(name); }
|