2023-06-20 04:33:09 +00:00
|
|
|
#include "StarStatSet.hpp"
|
|
|
|
#include "StarMathCommon.hpp"
|
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
void StatSet::addStat(String statName, float baseValue) {
|
2024-02-19 15:55:19 +00:00
|
|
|
if (!m_baseStats.insert(std::move(statName), baseValue).second)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("Added duplicate stat named '{}' in StatSet", statName);
|
2023-06-20 04:33:09 +00:00
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::removeStat(String const& statName) {
|
|
|
|
if (!m_baseStats.remove(statName))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("No such base stat '{}' in StatSet", statName);
|
2023-06-20 04:33:09 +00:00
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
StringList StatSet::baseStatNames() const {
|
|
|
|
return m_baseStats.keys();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::isBaseStat(String const& statName) const {
|
|
|
|
return m_baseStats.contains(statName);
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::statBaseValue(String const& statName) const {
|
|
|
|
if (auto s = m_baseStats.ptr(statName))
|
|
|
|
return *s;
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("No such base stat '{}' in StatSet", statName);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::setStatBaseValue(String const& statName, float value) {
|
|
|
|
if (auto s = m_baseStats.ptr(statName)) {
|
|
|
|
if (*s != value) {
|
|
|
|
*s = value;
|
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
} else {
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("No such base stat '{}' in StatSet", statName);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StatModifierGroupId StatSet::addStatModifierGroup(List<StatModifier> modifiers) {
|
|
|
|
bool empty = modifiers.empty();
|
2024-02-19 15:55:19 +00:00
|
|
|
auto id = m_statModifierGroups.add(std::move(modifiers));
|
2023-06-20 04:33:09 +00:00
|
|
|
if (!empty)
|
|
|
|
update(0.0f);
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<StatModifierGroupId> StatSet::statModifierGroupIds() const {
|
|
|
|
return m_statModifierGroups.keys();
|
|
|
|
}
|
|
|
|
|
|
|
|
List<StatModifier> StatSet::statModifierGroup(StatModifierGroupId modifierGroupId) const {
|
|
|
|
return m_statModifierGroups.get(modifierGroupId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::addStatModifierGroup(StatModifierGroupId groupId, List<StatModifier> modifiers) {
|
|
|
|
bool empty = modifiers.empty();
|
2024-02-19 15:55:19 +00:00
|
|
|
m_statModifierGroups.add(groupId, std::move(modifiers));
|
2023-06-20 04:33:09 +00:00
|
|
|
if (!empty)
|
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::setStatModifierGroup(StatModifierGroupId groupId, List<StatModifier> modifiers) {
|
|
|
|
auto& list = m_statModifierGroups.get(groupId);
|
|
|
|
if (list != modifiers) {
|
2024-02-19 15:55:19 +00:00
|
|
|
list = std::move(modifiers);
|
2023-06-20 04:33:09 +00:00
|
|
|
update(0.0f);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::removeStatModifierGroup(StatModifierGroupId modifierSetId) {
|
|
|
|
if (m_statModifierGroups.remove(modifierSetId)) {
|
|
|
|
update(0.0f);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::clearStatModifiers() {
|
|
|
|
if (!m_statModifierGroups.empty()) {
|
|
|
|
m_statModifierGroups.clear();
|
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StatModifierGroupMap const& StatSet::allStatModifierGroups() const {
|
|
|
|
return m_statModifierGroups;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::setAllStatModifierGroups(StatModifierGroupMap map) {
|
|
|
|
if (m_statModifierGroups != map) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_statModifierGroups = std::move(map);
|
2023-06-20 04:33:09 +00:00
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StringList StatSet::effectiveStatNames() const {
|
|
|
|
return m_effectiveStats.keys();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::isEffectiveStat(String const& statName) const {
|
|
|
|
return m_effectiveStats.contains(statName);
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::statEffectiveValue(String const& statName) const {
|
|
|
|
// All stat values will be added to m_effectiveStats regardless of whether a
|
|
|
|
// modifier is applied for it.
|
|
|
|
if (auto modified = m_effectiveStats.ptr(statName))
|
|
|
|
return modified->effectiveModifiedValue;
|
|
|
|
else
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::addResource(String resourceName, MVariant<String, float> max, MVariant<String, float> delta) {
|
2024-02-19 15:55:19 +00:00
|
|
|
auto pair = m_resources.insert({std::move(resourceName), Resource{std::move(max), std::move(delta), false, 0.0f, {}}});
|
2023-06-20 04:33:09 +00:00
|
|
|
if (!pair.second)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("Added duplicate resource named '{}' in StatSet", resourceName);
|
2023-06-20 04:33:09 +00:00
|
|
|
update(0.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::removeResource(String const& resourceName) {
|
|
|
|
if (!m_resources.remove(resourceName))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("No such resource named '{}' in StatSet", resourceName);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
StringList StatSet::resourceNames() const {
|
|
|
|
return m_resources.keys();
|
|
|
|
}
|
|
|
|
|
|
|
|
MVariant<String, float> StatSet::resourceMax(String const& resourceName) const {
|
|
|
|
return getResource(resourceName).max;
|
|
|
|
}
|
|
|
|
|
|
|
|
MVariant<String, float> StatSet::resourceDelta(String const& resourceName) const {
|
|
|
|
return getResource(resourceName).delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::isResource(String const& resourceName) const {
|
|
|
|
return m_resources.contains(resourceName);
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::resourceValue(String const& resourceName) const {
|
|
|
|
if (auto r = m_resources.ptr(resourceName))
|
|
|
|
return r->value;
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::setResourceValue(String const& resourceName, float value) {
|
|
|
|
return getResource(resourceName).setValue(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::modifyResourceValue(String const& resourceName, float amount) {
|
|
|
|
auto& resource = getResource(resourceName);
|
|
|
|
return resource.setValue(resource.value + amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::giveResourceValue(String const& resourceName, float amount) {
|
|
|
|
if (auto r = m_resources.ptr(resourceName)) {
|
|
|
|
float previousValue = r->value;
|
|
|
|
r->setValue(r->value + amount);
|
|
|
|
return r->value - previousValue;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::consumeResourceValue(String const& resourceName, float amount) {
|
|
|
|
return consumeResourceValue(resourceName, amount, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::overConsumeResourceValue(String const& resourceName, float amount) {
|
|
|
|
return consumeResourceValue(resourceName, amount, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::resourceLocked(String const& resourceName) const {
|
|
|
|
return getResource(resourceName).locked;
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::setResourceLocked(String const& resourceName, bool locked) {
|
|
|
|
getResource(resourceName).locked = locked;
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<float> StatSet::resourceMaxValue(String const& resourceName) const {
|
|
|
|
return getResource(resourceName).maxValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Maybe<float> StatSet::resourcePercentage(String const& resourceName) const {
|
|
|
|
auto const& resource = getResource(resourceName);
|
|
|
|
if (!resource.maxValue)
|
|
|
|
return {};
|
|
|
|
return resource.value / *resource.maxValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::setResourcePercentage(String const& resourceName, float resourcePercentage) {
|
|
|
|
auto& resource = getResource(resourceName);
|
|
|
|
if (!resource.maxValue)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("setResourcePersentage called on resource '{}' which has no maximum", resourceName);
|
2023-06-20 04:33:09 +00:00
|
|
|
return resource.setValue(resourcePercentage * *resource.maxValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::modifyResourcePercentage(String const& resourceName, float resourcePercentage) {
|
|
|
|
auto& resource = getResource(resourceName);
|
|
|
|
if (!resource.maxValue)
|
|
|
|
throw StatusException::format(
|
2023-06-27 10:23:44 +00:00
|
|
|
"modifyResourcePercentage called on resource '{}' which has no maximum", resourceName);
|
2023-06-20 04:33:09 +00:00
|
|
|
return resource.setValue(resource.value + resourcePercentage * *resource.maxValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StatSet::update(float dt) {
|
|
|
|
// We use two intermediate values for calculating the effective stat value.
|
|
|
|
// The baseModifiedValue represents the application of the base percentage
|
|
|
|
// modifiers and the value modifiers, which only depend on the baseValue.
|
|
|
|
// The effectiveModifiedValue is the application of all effective percentage
|
|
|
|
// modifiers successively on the baseModifiedValue, causing them to stack with
|
|
|
|
// each other in addition to base multipliers and value modifiers
|
|
|
|
|
|
|
|
// First, clear the modified values to get rid of temporary stats applied
|
|
|
|
// from modifiers that may no longer be there
|
|
|
|
m_effectiveStats.clear();
|
|
|
|
|
|
|
|
// Then we do all the StatValueModifiers and StatBaseMultipliers and
|
|
|
|
// compute the baseModifiedValue
|
|
|
|
|
|
|
|
for (auto& p : m_baseStats) {
|
|
|
|
auto& stat = m_effectiveStats[p.first];
|
|
|
|
stat.baseValue = p.second;
|
|
|
|
stat.baseModifiedValue = stat.baseValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto const& p : m_statModifierGroups) {
|
|
|
|
for (auto const& modifier : p.second) {
|
|
|
|
if (auto baseMultiplier = modifier.ptr<StatBaseMultiplier>()) {
|
|
|
|
auto& stat = m_effectiveStats[baseMultiplier->statName];
|
|
|
|
stat.baseModifiedValue += (baseMultiplier->baseMultiplier - 1.0f) * stat.baseValue;
|
|
|
|
} else if (auto valueModifier = modifier.ptr<StatValueModifier>()) {
|
|
|
|
auto& stat = m_effectiveStats[valueModifier->statName];
|
|
|
|
stat.baseModifiedValue += valueModifier->value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then we do all the StatEffectiveMultipliers and compute the
|
|
|
|
// final effectiveModifiedValue
|
|
|
|
|
|
|
|
for (auto& p : m_effectiveStats)
|
|
|
|
p.second.effectiveModifiedValue = p.second.baseModifiedValue;
|
|
|
|
|
|
|
|
for (auto const& p : m_statModifierGroups) {
|
|
|
|
for (auto const& modifier : p.second) {
|
|
|
|
if (auto effectiveMultiplier = modifier.ptr<StatEffectiveMultiplier>()) {
|
|
|
|
auto& stat = m_effectiveStats[effectiveMultiplier->statName];
|
|
|
|
stat.effectiveModifiedValue *= effectiveMultiplier->effectiveMultiplier;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then update all the resources due to charging and percentage tracking,
|
|
|
|
// after updating the stats.
|
|
|
|
|
|
|
|
for (auto& p : m_resources) {
|
|
|
|
Maybe<float> newMaxValue;
|
|
|
|
if (p.second.max.is<String>())
|
|
|
|
newMaxValue = statEffectiveValue(p.second.max.get<String>());
|
|
|
|
else if (p.second.max.is<float>())
|
|
|
|
newMaxValue = p.second.max.get<float>();
|
|
|
|
|
|
|
|
// If the resource has a maximum value, rather than keeping the absolute
|
|
|
|
// value of the resource the same between updates, the resource value
|
|
|
|
// should instead track the percentage.
|
|
|
|
if (p.second.maxValue && newMaxValue && *p.second.maxValue > 0.0f)
|
|
|
|
p.second.value *= *newMaxValue / *p.second.maxValue;
|
|
|
|
|
|
|
|
p.second.maxValue = newMaxValue;
|
|
|
|
if (p.second.maxValue)
|
|
|
|
p.second.value = clamp(p.second.value, 0.0f, *p.second.maxValue);
|
|
|
|
|
|
|
|
if (dt != 0.0f) {
|
|
|
|
float delta = 0.0f;
|
|
|
|
if (p.second.delta.is<String>())
|
|
|
|
delta = statEffectiveValue(p.second.delta.get<String>());
|
|
|
|
else if (p.second.delta.is<float>())
|
|
|
|
delta = p.second.delta.get<float>();
|
|
|
|
p.second.setValue(p.second.value + delta * dt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float StatSet::Resource::setValue(float v) {
|
|
|
|
if (maxValue)
|
|
|
|
value = clamp(v, 0.0f, *maxValue);
|
|
|
|
else
|
|
|
|
value = Star::max(v, 0.0f);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
StatSet::Resource const& StatSet::getResource(String const& resourceName) const {
|
|
|
|
if (auto r = m_resources.ptr(resourceName))
|
|
|
|
return *r;
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("No such resource '{}' in StatSet", resourceName);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
StatSet::Resource& StatSet::getResource(String const& resourceName) {
|
|
|
|
if (auto r = m_resources.ptr(resourceName))
|
|
|
|
return *r;
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("No such resource '{}' in StatSet", resourceName);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool StatSet::consumeResourceValue(String const& resourceName, float amount, bool allowOverConsume) {
|
|
|
|
if (amount < 0.0f)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StatusException::format("StatSet, consumeResource called with negative amount '{}' {}", resourceName, amount);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
if (auto r = m_resources.ptr(resourceName)) {
|
|
|
|
if (r->locked)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (r->value >= amount) {
|
|
|
|
r->setValue(r->value - amount);
|
|
|
|
return true;
|
|
|
|
} else if (r->value > 0.0f && allowOverConsume) {
|
|
|
|
r->setValue(0.0f);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|