Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarStatusController.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/StarStatusController.cpp')
-rw-r--r--source/game/StarStatusController.cpp823
1 files changed, 823 insertions, 0 deletions
diff --git a/source/game/StarStatusController.cpp b/source/game/StarStatusController.cpp
new file mode 100644
index 0000000..3b794f0
--- /dev/null
+++ b/source/game/StarStatusController.cpp
@@ -0,0 +1,823 @@
+#include "StarStatusController.hpp"
+#include "StarDataStreamExtra.hpp"
+#include "StarJsonExtra.hpp"
+#include "StarLuaGameConverters.hpp"
+#include "StarWorld.hpp"
+#include "StarWorldLuaBindings.hpp"
+#include "StarStatusControllerLuaBindings.hpp"
+#include "StarNetworkedAnimatorLuaBindings.hpp"
+#include "StarConfigLuaBindings.hpp"
+#include "StarEntityLuaBindings.hpp"
+#include "StarStatusEffectDatabase.hpp"
+#include "StarStatusEffectEntity.hpp"
+#include "StarLiquidsDatabase.hpp"
+
+namespace Star {
+
+StatusController::StatusController(Json const& config) : m_statCollection(config) {
+ m_parentEntity = nullptr;
+ m_movementController = nullptr;
+
+ m_statusProperties.set(config.getObject("statusProperties", {}));
+ m_minimumLiquidStatusEffectPercentage = config.getFloat("minimumLiquidStatusEffectPercentage");
+ m_appliesEnvironmentStatusEffects = config.getBool("appliesEnvironmentStatusEffects");
+ m_appliesWeatherStatusEffects = config.getBool("appliesWeatherStatusEffects");
+ m_environmentStatusEffectUpdateTimer = GameTimer(config.getFloat("environmentStatusEffectUpdateTimer", 0.15f));
+
+ m_primaryAnimationConfig = config.optString("primaryAnimationConfig");
+ m_primaryScript.setScripts(jsonToStringList(config.get("primaryScriptSources", JsonArray())));
+ m_primaryScript.setUpdateDelta(config.getUInt("primaryScriptDelta", 1));
+
+ m_primaryScript.setUpdateDelta(config.getUInt("primaryScriptDelta", 1));
+
+ uint64_t keepDamageSteps = config.getUInt("keepDamageNotificationSteps", 120);
+ m_recentHitsGiven.setHistoryLimit(keepDamageSteps);
+ m_recentDamageGiven.setHistoryLimit(keepDamageSteps);
+ m_recentDamageTaken.setHistoryLimit(keepDamageSteps);
+
+ m_netGroup.addNetElement(&m_statCollection);
+ m_netGroup.addNetElement(&m_statusProperties);
+ m_netGroup.addNetElement(&m_parentDirectives);
+ m_netGroup.addNetElement(&m_uniqueEffectMetadata);
+ m_netGroup.addNetElement(&m_effectAnimators);
+
+ if (m_primaryAnimationConfig)
+ m_primaryAnimatorId = m_effectAnimators.addNetElement(make_shared<EffectAnimator>(*m_primaryAnimationConfig));
+ else
+ m_primaryAnimatorId = EffectAnimatorGroup::NullElementId;
+}
+
+Json StatusController::diskStore() const {
+ JsonObject resourceValues;
+ JsonObject resourcesLocked;
+ for (auto const& resourceName : resourceNames()) {
+ resourceValues[resourceName] = resource(resourceName);
+ resourcesLocked[resourceName] = resourceLocked(resourceName);
+ }
+
+ JsonObject persistentEffectCategories;
+ for (auto const& pair : m_persistentEffects) {
+ List<PersistentStatusEffect> persistentEffects;
+ persistentEffects.appendAll(pair.second.statModifiers.transformed(construct<PersistentStatusEffect>()));
+ persistentEffects.appendAll(pair.second.uniqueEffects.values().transformed(construct<PersistentStatusEffect>()));
+ persistentEffectCategories[pair.first] = persistentEffects.transformed(jsonFromPersistentStatusEffect);
+ }
+
+ JsonArray ephemeralEffects;
+ for (auto const& pair : m_uniqueEffects) {
+ // Store ephemeral effects in the disk store based on remaining duration.
+ // TODO: Need to store maximum duration as well in the store, otherwise the
+ // effect will always appear "full" on reload (but just last less time)
+ auto metadata = m_uniqueEffectMetadata.getNetElement(pair.second.metadataId);
+ if (metadata->duration)
+ ephemeralEffects.append(jsonFromEphemeralStatusEffect(EphemeralStatusEffect{pair.first, *metadata->duration}));
+ }
+
+ return JsonObject{
+ {"statusProperties", m_statusProperties.get()},
+ {"persistentEffectCategories", move(persistentEffectCategories)},
+ {"ephemeralEffects", move(ephemeralEffects)},
+ {"resourceValues", move(resourceValues)},
+ {"resourcesLocked", move(resourcesLocked)},
+ };
+}
+
+void StatusController::diskLoad(Json const& store) {
+ clearAllPersistentEffects();
+ clearEphemeralEffects();
+
+ m_statusProperties.set(store.getObject("statusProperties"));
+
+ for (auto const& p : store.getObject("persistentEffectCategories", {}))
+ addPersistentEffects(p.first, p.second.toArray().transformed(jsonToPersistentStatusEffect));
+
+ addEphemeralEffects(store.getArray("ephemeralEffects").transformed(jsonToEphemeralStatusEffect));
+
+ for (auto const& p : store.getObject("resourceValues", {})) {
+ if (isResource(p.first))
+ setResource(p.first, p.second.toFloat());
+ }
+
+ for (auto const& p : store.getObject("resourcesLocked", {})) {
+ if (isResource(p.first))
+ setResourceLocked(p.first, p.second.toBool());
+ }
+}
+
+Json StatusController::statusProperty(String const& name, Json const& def) const {
+ return m_statusProperties.get().value(name, def);
+}
+
+void StatusController::setStatusProperty(String const& name, Json value) {
+ m_statusProperties.update([&](JsonObject& statusProperties) {
+ if (statusProperties[name] != value) {
+ statusProperties[name] = move(value);
+ return true;
+ }
+ return false;
+ });
+}
+
+StringList StatusController::statNames() const {
+ return m_statCollection.statNames();
+}
+
+float StatusController::stat(String const& statName) const {
+ return m_statCollection.stat(statName);
+}
+
+bool StatusController::statPositive(String const& statName) const {
+ return m_statCollection.statPositive(statName);
+}
+
+StringList StatusController::resourceNames() const {
+ return m_statCollection.resourceNames();
+}
+
+bool StatusController::isResource(String const& resourceName) const {
+ return m_statCollection.isResource(resourceName);
+}
+
+float StatusController::resource(String const& resourceName) const {
+ return m_statCollection.resource(resourceName);
+}
+
+bool StatusController::resourcePositive(String const& resourceName) const {
+ return m_statCollection.resourcePositive(resourceName);
+}
+
+void StatusController::setResource(String const& resourceName, float value) {
+ m_statCollection.setResource(resourceName, value);
+}
+
+void StatusController::modifyResource(String const& resourceName, float amount) {
+ m_statCollection.modifyResource(resourceName, amount);
+}
+
+float StatusController::giveResource(String const& resourceName, float amount) {
+ return m_statCollection.giveResource(resourceName, amount);
+}
+
+bool StatusController::consumeResource(String const& resourceName, float amount) {
+ if (m_statCollection.consumeResource(resourceName, amount)) {
+ m_primaryScript.invoke("notifyResourceConsumed", resourceName, amount);
+ return true;
+ }
+ return false;
+}
+
+bool StatusController::overConsumeResource(String const& resourceName, float amount) {
+ if (m_statCollection.overConsumeResource(resourceName, amount)) {
+ m_primaryScript.invoke("notifyResourceConsumed", resourceName, amount);
+ return true;
+ }
+ return false;
+}
+
+bool StatusController::resourceLocked(String const& resourceName) const {
+ return m_statCollection.resourceLocked(resourceName);
+}
+
+void StatusController::setResourceLocked(String const& resourceName, bool locked) {
+ m_statCollection.setResourceLocked(resourceName, locked);
+}
+
+void StatusController::resetResource(String const& resourceName) {
+ m_statCollection.resetResource(resourceName);
+}
+
+void StatusController::resetAllResources() {
+ m_statCollection.resetAllResources();
+}
+
+Maybe<float> StatusController::resourceMax(String const& resourceName) const {
+ return m_statCollection.resourceMax(resourceName);
+}
+
+Maybe<float> StatusController::resourcePercentage(String const& resourceName) const {
+ return m_statCollection.resourcePercentage(resourceName);
+}
+
+float StatusController::setResourcePercentage(String const& resourceName, float resourcePercentage) {
+ return m_statCollection.setResourcePercentage(resourceName, resourcePercentage);
+}
+
+float StatusController::modifyResourcePercentage(String const& resourceName, float resourcePercentage) {
+ return m_statCollection.modifyResourcePercentage(resourceName, resourcePercentage);
+}
+
+List<PersistentStatusEffect> StatusController::getPersistentEffects(String const& statEffectCategory) const {
+ auto category = m_persistentEffects.maybe(statEffectCategory).value();
+ List<PersistentStatusEffect> persistentEffects =
+ category.statModifiers.transformed(construct<PersistentStatusEffect>());
+ persistentEffects.appendAll(
+ List<UniqueStatusEffect>::from(category.uniqueEffects).transformed(construct<PersistentStatusEffect>()));
+ return persistentEffects;
+}
+
+void StatusController::addPersistentEffect(
+ String const& statusEffectCategory, PersistentStatusEffect const& persistentEffect) {
+ addPersistentEffects(statusEffectCategory, {persistentEffect});
+}
+
+void StatusController::addPersistentEffects(
+ String const& statusEffectCategory, List<PersistentStatusEffect> const& effectList) {
+ auto& persistentEffectCategory = m_persistentEffects[statusEffectCategory];
+ if (!persistentEffectCategory.modifierEffectsGroupId)
+ persistentEffectCategory.modifierEffectsGroupId = m_statCollection.addStatModifierGroup();
+
+ for (auto const& effect : effectList) {
+ if (effect.is<StatModifier>())
+ persistentEffectCategory.statModifiers.append(effect.get<StatModifier>());
+ else if (effect.is<UniqueStatusEffect>())
+ persistentEffectCategory.uniqueEffects.add(effect.get<UniqueStatusEffect>());
+ }
+ m_statCollection.setStatModifierGroup(
+ *persistentEffectCategory.modifierEffectsGroupId, persistentEffectCategory.statModifiers);
+
+ updatePersistentUniqueEffects();
+}
+
+void StatusController::setPersistentEffects(
+ String const& statusEffectCategory, List<PersistentStatusEffect> const& effectList) {
+ if (effectList.empty()) {
+ if (auto groupId = m_persistentEffects[statusEffectCategory].modifierEffectsGroupId)
+ m_statCollection.removeStatModifierGroup(*groupId);
+ m_persistentEffects.remove(statusEffectCategory);
+
+ updatePersistentUniqueEffects();
+ } else {
+ auto& persistentEffectCategory = m_persistentEffects[statusEffectCategory];
+ persistentEffectCategory.statModifiers.clear();
+ persistentEffectCategory.uniqueEffects.clear();
+ addPersistentEffects(statusEffectCategory, effectList);
+ }
+}
+
+void StatusController::clearPersistentEffects(String const& statusEffectCategory) {
+ setPersistentEffects(statusEffectCategory, {});
+}
+
+void StatusController::clearAllPersistentEffects() {
+ for (auto const& effectCategory : m_persistentEffects.keys())
+ clearPersistentEffects(effectCategory);
+}
+
+void StatusController::addEphemeralEffect(EphemeralStatusEffect const& effect, Maybe<EntityId> sourceEntityId) {
+ addEphemeralEffects({effect}, sourceEntityId);
+}
+
+void StatusController::addEphemeralEffects(
+ List<EphemeralStatusEffect> const& effectList, Maybe<EntityId> sourceEntityId) {
+ for (auto const& effect : effectList) {
+ if (auto existingEffect = m_uniqueEffects.ptr(effect.uniqueEffect)) {
+ auto metadata = m_uniqueEffectMetadata.getNetElement(existingEffect->metadataId);
+
+ // If the effect exists and does not have a null duration, then refresh
+ // the
+ // duration to the max
+ if (metadata->duration) {
+ auto newDuration = effect.duration.value(defaultUniqueEffectDuration(effect.uniqueEffect));
+ if (newDuration > *metadata->duration) {
+ // Only overwrite the sourceEntityId if the duration is *extended*
+ metadata->sourceEntityId.set(sourceEntityId);
+ metadata->duration = newDuration;
+ }
+ metadata->maxDuration.set(max(metadata->maxDuration.get(), newDuration));
+ }
+ } else {
+ addUniqueEffect(
+ effect.uniqueEffect, effect.duration.value(defaultUniqueEffectDuration(effect.uniqueEffect)), sourceEntityId);
+ }
+ }
+}
+
+bool StatusController::removeEphemeralEffect(UniqueStatusEffect const& effect) {
+ if (auto uniqueEffect = m_uniqueEffects.ptr(effect)) {
+ auto metadata = m_uniqueEffectMetadata.getNetElement(uniqueEffect->metadataId);
+ if (metadata->duration) {
+ removeUniqueEffect(effect);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void StatusController::clearEphemeralEffects() {
+ for (auto const& key : m_uniqueEffects.keys())
+ removeEphemeralEffect(key);
+}
+
+bool StatusController::appliesEnvironmentStatusEffects() const {
+ return m_appliesEnvironmentStatusEffects;
+}
+
+void StatusController::setAppliesEnvironmentStatusEffects(bool appliesEnvironmentStatusEffects) {
+ m_appliesEnvironmentStatusEffects = appliesEnvironmentStatusEffects;
+}
+
+ActiveUniqueStatusEffectSummary StatusController::activeUniqueStatusEffectSummary() const {
+ ActiveUniqueStatusEffectSummary summary;
+ for (auto const& metadata : m_uniqueEffectMetadata.netElements()) {
+ if (metadata->duration)
+ summary.append({metadata->effect, *metadata->duration / metadata->maxDuration.get()});
+ else
+ summary.append({metadata->effect, 1.0f});
+ }
+ return summary;
+}
+
+bool StatusController::uniqueStatusEffectActive(String const& effectName) const {
+ for (auto const& metadata : m_uniqueEffectMetadata.netElements()) {
+ if (metadata->effect == effectName)
+ return true;
+ }
+
+ return false;
+}
+
+String StatusController::primaryDirectives() const {
+ return m_primaryDirectives;
+}
+
+void StatusController::setPrimaryDirectives(String const& directives) {
+ m_primaryDirectives = directives;
+}
+
+List<DamageNotification> StatusController::applyDamageRequest(DamageRequest const& damageRequest) {
+ if (auto damageNotifications = m_primaryScript.invoke<List<DamageNotification>>("applyDamageRequest", damageRequest)) {
+ for (auto const& dn : *damageNotifications)
+ m_recentDamageTaken.add(dn);
+ return damageNotifications.take();
+ }
+ return {};
+}
+
+void StatusController::hitOther(EntityId targetEntityId, DamageRequest damageRequest) {
+ m_recentHitsGiven.add({targetEntityId, move(damageRequest)});
+}
+
+void StatusController::damagedOther(DamageNotification damageNotification) {
+ m_recentDamageGiven.add(move(damageNotification));
+}
+
+List<DamageNotification> StatusController::pullSelfDamageNotifications() {
+ return take(m_pendingSelfDamageNotifications);
+}
+
+void StatusController::applySelfDamageRequest(DamageRequest dr) {
+ auto damageNotifications = applyDamageRequest(move(dr));
+ for (auto const& dn : damageNotifications)
+ m_recentDamageTaken.add(dn);
+ m_pendingSelfDamageNotifications.appendAll(move(damageNotifications));
+}
+
+pair<List<DamageNotification>, uint64_t> StatusController::damageTakenSince(uint64_t since) const {
+ return m_recentDamageTaken.query(since);
+}
+
+pair<List<pair<EntityId, DamageRequest>>, uint64_t> StatusController::inflictedHitsSince(uint64_t since) const {
+ return m_recentHitsGiven.query(since);
+}
+
+pair<List<DamageNotification>, uint64_t> StatusController::inflictedDamageSince(uint64_t since) const {
+ return m_recentDamageGiven.query(since);
+}
+
+void StatusController::init(Entity* parentEntity, ActorMovementController* movementController) {
+ uninit();
+
+ m_parentEntity = parentEntity;
+ m_movementController = movementController;
+
+ if (m_parentEntity->isMaster()) {
+ initPrimaryScript();
+ for (auto& p : m_uniqueEffects)
+ initUniqueEffectScript(p.second);
+ }
+
+ m_environmentStatusEffectUpdateTimer.reset();
+}
+
+void StatusController::uninit() {
+ m_parentEntity = nullptr;
+ m_movementController = nullptr;
+
+ for (auto& p : m_uniqueEffects)
+ uninitUniqueEffectScript(p.second);
+ uninitPrimaryScript();
+
+ m_recentHitsGiven.reset();
+ m_recentDamageGiven.reset();
+ m_recentDamageTaken.reset();
+}
+
+void StatusController::initNetVersion(NetElementVersion const* version) {
+ m_netGroup.initNetVersion(version);
+}
+
+void StatusController::netStore(DataStream& ds) const {
+ m_netGroup.netStore(ds);
+}
+
+void StatusController::netLoad(DataStream& ds) {
+ clearAllPersistentEffects();
+ clearEphemeralEffects();
+
+ m_netGroup.netLoad(ds);
+}
+
+void StatusController::enableNetInterpolation(float extrapolationHint) {
+ m_netGroup.enableNetInterpolation(extrapolationHint);
+}
+
+void StatusController::disableNetInterpolation() {
+ m_netGroup.disableNetInterpolation();
+}
+
+void StatusController::tickNetInterpolation(float dt) {
+ m_netGroup.tickNetInterpolation(dt);
+}
+
+bool StatusController::writeNetDelta(DataStream& ds, uint64_t fromStep) const {
+ return m_netGroup.writeNetDelta(ds, fromStep);
+}
+
+void StatusController::readNetDelta(DataStream& ds, float interpolationTime) {
+ m_netGroup.readNetDelta(ds, interpolationTime);
+}
+
+void StatusController::blankNetDelta(float interpolationTime) {
+ m_netGroup.blankNetDelta(interpolationTime);
+}
+
+void StatusController::tickMaster() {
+ m_statCollection.tickMaster();
+
+ m_recentHitsGiven.tick(1);
+ m_recentDamageGiven.tick(1);
+ m_recentDamageTaken.tick(1);
+
+ bool statusImmune = statPositive("statusImmunity");
+
+ if (!statusImmune && m_movementController->liquidPercentage() > m_minimumLiquidStatusEffectPercentage) {
+ auto liquidsDatabase = Root::singleton().liquidsDatabase();
+ if (auto liquidSettings = liquidsDatabase->liquidSettings(m_movementController->liquidId())) {
+ for (auto const& effect : liquidSettings->statusEffects)
+ addEphemeralEffect(jsonToEphemeralStatusEffect(effect));
+ }
+ }
+
+ if (m_environmentStatusEffectUpdateTimer.wrapTick()) {
+ PolyF collisionBody = m_movementController->collisionBody();
+ List<PersistentStatusEffect> entityEffects;
+ if (!statusImmune) {
+ m_parentEntity->world()->forEachEntity(collisionBody.boundBox(),
+ [this, collisionBody, &entityEffects](EntityPtr const& e) {
+ if (auto entity = as<StatusEffectEntity>(e)) {
+ auto statusEffectArea = entity->statusEffectArea();
+ statusEffectArea.translate(entity->position());
+ if (m_parentEntity->world()->geometry().polyIntersectsPoly(statusEffectArea, collisionBody))
+ entityEffects.appendAll(entity->statusEffects());
+ }
+ });
+ }
+ setPersistentEffects("entities", entityEffects);
+
+ if (!statusImmune && m_appliesEnvironmentStatusEffects)
+ setPersistentEffects("environment", m_parentEntity->world()->environmentStatusEffects(m_parentEntity->position()).transformed(jsonToPersistentStatusEffect));
+
+ if (!statusImmune && m_appliesWeatherStatusEffects)
+ addEphemeralEffects(m_parentEntity->world()->weatherStatusEffects(m_parentEntity->position()).transformed(jsonToEphemeralStatusEffect));
+ }
+
+ m_primaryScript.update(m_primaryScript.updateDt());
+ for (auto& p : m_uniqueEffects) {
+ p.second.script.update(p.second.script.updateDt());
+ auto metadata = m_uniqueEffectMetadata.getNetElement(p.second.metadataId);
+ if (metadata->duration)
+ *metadata->duration -= WorldTimestep;
+ }
+
+ for (auto const& key : m_uniqueEffects.keys()) {
+ auto& uniqueEffect = m_uniqueEffects[key];
+ auto metadata = m_uniqueEffectMetadata.getNetElement(uniqueEffect.metadataId);
+ if (metadata->duration && *metadata->duration <= 0.0f)
+ removeUniqueEffect(key);
+ else if ((metadata->duration && statPositive("statusImmunity")) || (uniqueEffect.effectConfig.blockingStat && statPositive(*uniqueEffect.effectConfig.blockingStat)))
+ removeUniqueEffect(key);
+ }
+
+ String parentDirectives = m_primaryDirectives;
+ for (auto const& pair : m_uniqueEffects) {
+ parentDirectives.append("?");
+ parentDirectives.append(pair.second.parentDirectives);
+ }
+ m_parentDirectives.set(move(parentDirectives));
+
+ updateAnimators();
+}
+
+void StatusController::tickSlave() {
+ m_statCollection.tickSlave();
+ updateAnimators();
+}
+
+String StatusController::parentDirectives() const {
+ return m_parentDirectives.get();
+}
+
+List<Drawable> StatusController::drawables() const {
+ List<Drawable> drawables;
+ for (auto const& animator : m_effectAnimators.netElements())
+ drawables.appendAll(animator->animator.drawables(m_movementController->position()));
+ return drawables;
+}
+
+List<LightSource> StatusController::lightSources() const {
+ List<LightSource> lightSources;
+ for (auto const& animator : m_effectAnimators.netElements())
+ lightSources.appendAll(animator->animator.lightSources(m_movementController->position()));
+ return lightSources;
+}
+
+List<OverheadBar> StatusController::overheadBars() {
+ if (auto bars = m_primaryScript.invoke<JsonArray>("overheadBars"))
+ return bars->transformed(construct<OverheadBar>());
+ return {};
+}
+
+List<AudioInstancePtr> StatusController::pullNewAudios() {
+ List<AudioInstancePtr> newAudios;
+ for (auto const& animator : m_effectAnimators.netElements())
+ newAudios.appendAll(animator->dynamicTarget.pullNewAudios());
+ return newAudios;
+}
+
+List<Particle> StatusController::pullNewParticles() {
+ List<Particle> newParticles;
+ for (auto const& animator : m_effectAnimators.netElements())
+ newParticles.appendAll(animator->dynamicTarget.pullNewParticles());
+ return newParticles;
+}
+
+Maybe<Json> StatusController::receiveMessage(String const& message, bool localMessage, JsonArray const& args) {
+ Maybe<Json> result = m_primaryScript.handleMessage(message, localMessage, args);
+ for (auto& p : m_uniqueEffects)
+ result = result.orMaybe(p.second.script.handleMessage(message, localMessage, args));
+ return result;
+}
+
+StatusController::EffectAnimator::EffectAnimator(Maybe<String> config) {
+ animationConfig = move(config);
+ animator = animationConfig ? NetworkedAnimator(*animationConfig) : NetworkedAnimator();
+}
+
+void StatusController::EffectAnimator::initNetVersion(NetElementVersion const* version) {
+ animator.initNetVersion(version);
+}
+
+void StatusController::EffectAnimator::netStore(DataStream& ds) const {
+ ds.write(animationConfig);
+ animator.netStore(ds);
+}
+
+void StatusController::EffectAnimator::netLoad(DataStream& ds) {
+ ds.read(animationConfig);
+ animator = animationConfig ? NetworkedAnimator(*animationConfig) : NetworkedAnimator();
+ animator.netLoad(ds);
+}
+
+void StatusController::EffectAnimator::enableNetInterpolation(float extrapolationHint) {
+ animator.enableNetInterpolation(extrapolationHint);
+}
+
+void StatusController::EffectAnimator::disableNetInterpolation() {
+ animator.disableNetInterpolation();
+}
+
+void StatusController::EffectAnimator::tickNetInterpolation(float dt) {
+ animator.tickNetInterpolation(dt);
+}
+
+bool StatusController::EffectAnimator::writeNetDelta(DataStream& ds, uint64_t fromVersion) const {
+ return animator.writeNetDelta(ds, fromVersion);
+}
+
+void StatusController::EffectAnimator::readNetDelta(DataStream& ds, float interpolationTime) {
+ animator.readNetDelta(ds, interpolationTime);
+}
+
+void StatusController::EffectAnimator::blankNetDelta(float interpolationTime) {
+ animator.blankNetDelta(interpolationTime);
+}
+
+StatusController::UniqueEffectMetadata::UniqueEffectMetadata() {
+ addNetElement(&durationNetState);
+ addNetElement(&maxDuration);
+ addNetElement(&sourceEntityId);
+ durationNetState.setFixedPointBase(0.01f);
+ durationNetState.setInterpolator(lerp<float, float>);
+}
+
+StatusController::UniqueEffectMetadata::UniqueEffectMetadata(UniqueStatusEffect effect, Maybe<float> duration, Maybe<EntityId> sourceEntityId)
+ : UniqueEffectMetadata() {
+ this->effect = move(effect);
+ this->duration = move(duration);
+ this->maxDuration.set(this->duration.value());
+ this->sourceEntityId.set(sourceEntityId);
+}
+
+void StatusController::UniqueEffectMetadata::netElementsNeedLoad(bool) {
+ duration = durationNetState.get() >= 0.0f ? Maybe<float>(durationNetState.get()) : Maybe<float>();
+}
+
+void StatusController::UniqueEffectMetadata::netElementsNeedStore() {
+ durationNetState.set(duration ? *duration : -1.0f);
+}
+
+void StatusController::updateAnimators() {
+ for (auto const& animator : m_effectAnimators.netElements()) {
+ if (m_parentEntity->world()->isServer()) {
+ animator->animator.update(WorldTimestep, nullptr);
+ } else {
+ animator->animator.update(WorldTimestep, &animator->dynamicTarget);
+ animator->dynamicTarget.updatePosition(m_movementController->position());
+ }
+ }
+}
+
+void StatusController::updatePersistentUniqueEffects() {
+ Set<UniqueStatusEffect> activePersistentUniqueEffects;
+ for (auto const& categoryPair : m_persistentEffects)
+ activePersistentUniqueEffects.addAll(categoryPair.second.uniqueEffects);
+
+ for (auto const& uniqueEffectName : activePersistentUniqueEffects) {
+ // It is important to note here that if a unique effect exists, it *may*
+ // not come from a persistent effect, it *may* be from an ephemeral effect.
+ // Here, when a persistent effect overrides an ephemeral effect, it is
+ // clearing the duration making it into a solely persistent effect. This
+ // means that by applying a persistent effect and then clearing it, you can
+ // remove an ephemeral effect.
+ if (auto existingEffect = m_uniqueEffects.ptr(uniqueEffectName))
+ m_uniqueEffectMetadata.getNetElement(existingEffect->metadataId)->duration.reset();
+ else
+ addUniqueEffect(uniqueEffectName, {}, {});
+ }
+
+ // Again, here we are using "durationless" to mean "persistent"
+ for (auto const& key : m_uniqueEffects.keys()) {
+ auto metadata = m_uniqueEffectMetadata.getNetElement(m_uniqueEffects[key].metadataId);
+ if (!metadata->duration && !activePersistentUniqueEffects.contains(key))
+ removeUniqueEffect(key);
+ }
+}
+
+float StatusController::defaultUniqueEffectDuration(UniqueStatusEffect const& effect) const {
+ return Root::singleton().statusEffectDatabase()->uniqueEffectConfig(effect).defaultDuration;
+}
+
+void StatusController::addUniqueEffect(
+ UniqueStatusEffect const& effect, Maybe<float> duration, Maybe<EntityId> sourceEntityId) {
+ auto statusEffectDatabase = Root::singleton().statusEffectDatabase();
+ if (statusEffectDatabase->isUniqueEffect(effect)) {
+ auto effectConfig = statusEffectDatabase->uniqueEffectConfig(effect);
+ if ((duration && statPositive("statusImmunity")) || (effectConfig.blockingStat && statPositive(*effectConfig.blockingStat)))
+ return;
+
+ auto& uniqueEffect = m_uniqueEffects[effect];
+ uniqueEffect.effectConfig = effectConfig;
+ uniqueEffect.script.setScripts(uniqueEffect.effectConfig.scripts);
+ uniqueEffect.script.setUpdateDelta(uniqueEffect.effectConfig.scriptDelta);
+
+ uniqueEffect.metadataId =
+ m_uniqueEffectMetadata.addNetElement(make_shared<UniqueEffectMetadata>(effect, duration, sourceEntityId));
+
+ uniqueEffect.animatorId = UniqueEffectMetadataGroup::NullElementId;
+ if (uniqueEffect.effectConfig.animationConfig)
+ uniqueEffect.animatorId =
+ m_effectAnimators.addNetElement(make_shared<EffectAnimator>(uniqueEffect.effectConfig.animationConfig));
+
+ if (m_parentEntity)
+ initUniqueEffectScript(uniqueEffect);
+ } else {
+ Logger::warn("Unique status effect '%s' not found in status effect database", effect);
+ }
+}
+
+void StatusController::removeUniqueEffect(UniqueStatusEffect const& effect) {
+ auto& uniqueEffect = m_uniqueEffects[effect];
+
+ uniqueEffect.script.invoke("onExpire");
+
+ uninitUniqueEffectScript(uniqueEffect);
+
+ m_uniqueEffectMetadata.removeNetElement(uniqueEffect.metadataId);
+
+ if (uniqueEffect.animatorId != EffectAnimatorGroup::NullElementId)
+ m_effectAnimators.removeNetElement(uniqueEffect.animatorId);
+
+ m_uniqueEffects.remove(effect);
+}
+
+void StatusController::initPrimaryScript() {
+ m_primaryScript.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(this));
+ m_primaryScript.addCallbacks("entity", LuaBindings::makeEntityCallbacks(m_parentEntity));
+ if (m_primaryAnimatorId != EffectAnimatorGroup::NullElementId) {
+ auto animator = m_effectAnimators.getNetElement(m_primaryAnimatorId);
+ m_primaryScript.addCallbacks("animator", LuaBindings::makeNetworkedAnimatorCallbacks(&animator->animator));
+ }
+ m_primaryScript.addActorMovementCallbacks(m_movementController);
+ m_primaryScript.init(m_parentEntity->world());
+}
+
+void StatusController::uninitPrimaryScript() {
+ m_primaryScript.uninit();
+ m_primaryScript.removeCallbacks("status");
+ m_primaryScript.removeCallbacks("entity");
+ m_primaryScript.removeCallbacks("animator");
+ m_primaryScript.removeActorMovementCallbacks();
+}
+
+void StatusController::initUniqueEffectScript(UniqueEffectInstance& uniqueEffect) {
+ uniqueEffect.script.addCallbacks("effect", makeUniqueEffectCallbacks(uniqueEffect));
+ uniqueEffect.script.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(this));
+ uniqueEffect.script.addCallbacks("config", LuaBindings::makeConfigCallbacks([&uniqueEffect](String const& name, Json const& def) {
+ return uniqueEffect.effectConfig.effectConfig.query(name, def);
+ }));
+ uniqueEffect.script.addCallbacks("entity", LuaBindings::makeEntityCallbacks(m_parentEntity));
+ if (uniqueEffect.animatorId != EffectAnimatorGroup::NullElementId) {
+ auto animator = m_effectAnimators.getNetElement(uniqueEffect.animatorId);
+ uniqueEffect.script.addCallbacks("animator", LuaBindings::makeNetworkedAnimatorCallbacks(&animator->animator));
+ }
+ uniqueEffect.script.addActorMovementCallbacks(m_movementController);
+ uniqueEffect.script.init(m_parentEntity->world());
+}
+
+void StatusController::uninitUniqueEffectScript(UniqueEffectInstance& uniqueEffect) {
+ uniqueEffect.script.uninit();
+ uniqueEffect.script.removeCallbacks("effect");
+ uniqueEffect.script.removeCallbacks("status");
+ uniqueEffect.script.removeCallbacks("config");
+ uniqueEffect.script.removeCallbacks("entity");
+ uniqueEffect.script.removeCallbacks("animator");
+ uniqueEffect.script.removeActorMovementCallbacks();
+
+ for (auto modifierGroup : uniqueEffect.modifierGroups)
+ m_statCollection.removeStatModifierGroup(modifierGroup);
+ uniqueEffect.modifierGroups.clear();
+}
+
+LuaCallbacks StatusController::makeUniqueEffectCallbacks(UniqueEffectInstance& uniqueEffect) {
+ LuaCallbacks callbacks;
+
+ callbacks.registerCallback("duration", [this, &uniqueEffect]() {
+ return m_uniqueEffectMetadata.getNetElement(uniqueEffect.metadataId)->duration;
+ });
+ callbacks.registerCallback("modifyDuration", [this, &uniqueEffect](float duration) {
+ auto metadata = m_uniqueEffectMetadata.getNetElement(uniqueEffect.metadataId);
+ if (metadata->duration)
+ *metadata->duration += duration;
+ });
+ callbacks.registerCallback("expire", [this, &uniqueEffect]() {
+ auto metadata = m_uniqueEffectMetadata.getNetElement(uniqueEffect.metadataId);
+ if (metadata->duration)
+ metadata->duration = 0.0f;
+ });
+ callbacks.registerCallback("sourceEntity", [this, &uniqueEffect]() -> Maybe<EntityId> {
+ auto metadata = m_uniqueEffectMetadata.getNetElement(uniqueEffect.metadataId);
+ auto sourceEntityId = metadata->sourceEntityId.get();
+ if (!sourceEntityId)
+ return m_parentEntity->entityId();
+ if (sourceEntityId == NullEntityId)
+ return {};
+ return sourceEntityId;
+ });
+ callbacks.registerCallback("setParentDirectives", [&uniqueEffect](Maybe<String> const& directives) {
+ uniqueEffect.parentDirectives = directives.value();
+ });
+ callbacks.registerCallback("getParameter", [&uniqueEffect](String const& name, Json const& def) -> Json {
+ return uniqueEffect.effectConfig.effectConfig.query(name, def);
+ });
+ callbacks.registerCallback("addStatModifierGroup", [this, &uniqueEffect](List<StatModifier> const& modifiers) -> StatModifierGroupId {
+ auto newGroupId = m_statCollection.addStatModifierGroup(modifiers);
+ uniqueEffect.modifierGroups.add(newGroupId);
+ return newGroupId;
+ });
+ callbacks.registerCallback("setStatModifierGroup", [this, &uniqueEffect](StatModifierGroupId groupId, List<StatModifier> const& modifiers) {
+ if (!uniqueEffect.modifierGroups.contains(groupId))
+ throw StatusException("Cannot set stat modifier group that was not added from this effect");
+ m_statCollection.setStatModifierGroup(groupId, modifiers);
+ });
+ callbacks.registerCallback("removeStatModifierGroup", [this, &uniqueEffect](StatModifierGroupId groupId) {
+ if (!uniqueEffect.modifierGroups.contains(groupId))
+ throw StatusException("Cannot remove stat modifier group that was not added from this effect");
+ m_statCollection.removeStatModifierGroup(groupId);
+ uniqueEffect.modifierGroups.remove(groupId);
+ });
+
+ return callbacks;
+}
+
+}