539 lines
29 KiB
C++
539 lines
29 KiB
C++
#include "weapon/WeaponDefinitionRuntime.h"
|
|
|
|
#include "common/Value.h"
|
|
#include "weapon/Weapon.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
namespace eve::weapon {
|
|
namespace {
|
|
|
|
template <class T>
|
|
eve::Result<T> invalid(std::string message, std::string path = {}) {
|
|
return eve::Result<T>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, std::move(message),
|
|
std::move(path), {}, "weapon.definition_runtime"));
|
|
}
|
|
|
|
const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
|
|
const auto it = object.find(std::string(name));
|
|
return it == object.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
bool readText(const eve::Value::Object& object, std::string_view name, std::string& output) {
|
|
const auto* value = field(object, name);
|
|
if (value == nullptr) return true;
|
|
const auto* text = value->getIf<std::string>();
|
|
if (text == nullptr) return false;
|
|
output = *text;
|
|
return true;
|
|
}
|
|
|
|
bool readNumber(const eve::Value::Object& object, std::string_view name, float& output) {
|
|
const auto* value = field(object, name);
|
|
if (value == nullptr) return true;
|
|
if (const auto* number = value->getIf<double>()) {
|
|
if (!std::isfinite(*number)) return false;
|
|
output = static_cast<float>(*number);
|
|
return std::isfinite(output);
|
|
}
|
|
if (const auto* integer = value->getIf<std::int64_t>()) {
|
|
output = static_cast<float>(*integer);
|
|
return std::isfinite(output);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool readInteger(const eve::Value::Object& object, std::string_view name, int& output) {
|
|
const auto* value = field(object, name);
|
|
if (value == nullptr) return true;
|
|
const auto* integer = value->getIf<std::int64_t>();
|
|
if (integer == nullptr || *integer < std::numeric_limits<int>::min() || *integer > std::numeric_limits<int>::max())
|
|
return false;
|
|
output = static_cast<int>(*integer);
|
|
return true;
|
|
}
|
|
|
|
bool readBoolean(const eve::Value::Object& object, std::string_view name, bool& output) {
|
|
const auto* value = field(object, name);
|
|
if (value == nullptr) return true;
|
|
const auto* boolean = value->getIf<bool>();
|
|
if (boolean == nullptr) return false;
|
|
output = *boolean;
|
|
return true;
|
|
}
|
|
|
|
bool readStringArray(const eve::Value::Object& object, std::string_view name,
|
|
std::vector<std::string>& output) {
|
|
const auto* value = field(object, name);
|
|
if (value == nullptr) return true;
|
|
const auto* array = value->getIf<eve::Value::Array>();
|
|
if (array == nullptr) return false;
|
|
std::vector<std::string> candidate;
|
|
candidate.reserve(array->size());
|
|
for (const auto& entry : *array) {
|
|
const auto* text = entry.getIf<std::string>();
|
|
if (text == nullptr || text->empty()) return false;
|
|
candidate.push_back(*text);
|
|
}
|
|
std::sort(candidate.begin(), candidate.end());
|
|
if (std::adjacent_find(candidate.begin(), candidate.end()) != candidate.end()) return false;
|
|
output = std::move(candidate);
|
|
return true;
|
|
}
|
|
|
|
const char* resourceKindName(ResourceKind kind) noexcept {
|
|
switch (kind) {
|
|
case ResourceKind::None: return "none";
|
|
case ResourceKind::Ammo: return "ammo";
|
|
case ResourceKind::Mana: return "mana";
|
|
case ResourceKind::Charges: return "charges";
|
|
case ResourceKind::Stamina: return "stamina";
|
|
}
|
|
return "none";
|
|
}
|
|
|
|
ResourceKind resourceKindFromName(std::string_view name) noexcept {
|
|
if (name == "ammo") return ResourceKind::Ammo;
|
|
if (name == "mana") return ResourceKind::Mana;
|
|
if (name == "charges") return ResourceKind::Charges;
|
|
if (name == "stamina") return ResourceKind::Stamina;
|
|
return ResourceKind::None;
|
|
}
|
|
|
|
AttackStage attackStageFromName(std::string_view name) noexcept {
|
|
if (name == "windup") return AttackStage::Windup;
|
|
if (name == "active") return AttackStage::Active;
|
|
if (name == "recover") return AttackStage::Recover;
|
|
return AttackStage::Idle;
|
|
}
|
|
|
|
eve::Result<WeaponDefinition> parseDefinition(const eve::definitions::Definition& source,
|
|
const eve::DefinitionRef& reference) {
|
|
auto parsed = eve::Value::fromJson(source.json);
|
|
if (!parsed) return eve::Result<WeaponDefinition>::failure(parsed.status());
|
|
auto value = std::move(parsed).takeValue();
|
|
const auto* object = value.getIf<eve::Value::Object>();
|
|
if (object == nullptr) return invalid<WeaponDefinition>("weapon definition must be an object", "json");
|
|
|
|
WeaponDefinition result;
|
|
result.id = std::string(reference.id().name());
|
|
std::string id = result.id;
|
|
if (!readText(*object, "id", id) || id != result.id)
|
|
return invalid<WeaponDefinition>("weapon definition id does not match its logical reference", "id");
|
|
std::string kind = weaponKindName(result.kind);
|
|
std::string logic = result.logic;
|
|
if (!readText(*object, "kind", kind) || !readText(*object, "logic", logic))
|
|
return invalid<WeaponDefinition>("weapon kind and logic must be strings", "definition");
|
|
result.kind = weaponKindFromName(kind);
|
|
result.logic = std::move(logic);
|
|
|
|
if (!readNumber(*object, "damage", result.damage) || !readNumber(*object, "penetration", result.penetration) ||
|
|
!readNumber(*object, "range", result.range) || !readNumber(*object, "spread", result.spread) ||
|
|
!readNumber(*object, "falloffStart", result.falloffStart) ||
|
|
!readNumber(*object, "minimumDamageFactor", result.minimumDamageFactor) ||
|
|
!readNumber(*object, "splashMinimumDamageFactor", result.splashMinimumDamageFactor) ||
|
|
!readNumber(*object, "preferredTargetBonus", result.preferredTargetBonus) ||
|
|
!readNumber(*object, "accuracy", result.accuracy) ||
|
|
!readNumber(*object, "scatterRadius", result.scatterRadius) ||
|
|
!readNumber(*object, "spreadMin", result.spreadMin) || !readNumber(*object, "spreadMax", result.spreadMax) ||
|
|
!readNumber(*object, "spreadPerShot", result.spreadPerShot) ||
|
|
!readNumber(*object, "spreadRecover", result.spreadRecover) ||
|
|
!readNumber(*object, "recoilPitch", result.recoilPitch) ||
|
|
!readNumber(*object, "recoilYaw", result.recoilYaw) ||
|
|
!readNumber(*object, "recoilRecover", result.recoilRecover) ||
|
|
!readNumber(*object, "zoomFov", result.zoomFov) || !readNumber(*object, "cooldown", result.cooldown) ||
|
|
!readNumber(*object, "arc", result.arc))
|
|
return invalid<WeaponDefinition>("weapon numeric fields must be finite numbers", "definition");
|
|
if (result.falloffStart < 0.0f || result.falloffStart > result.range ||
|
|
result.minimumDamageFactor < 0.0f || result.minimumDamageFactor > 1.0f ||
|
|
result.splashMinimumDamageFactor < 0.0f || result.splashMinimumDamageFactor > 1.0f ||
|
|
result.accuracy < 0.0f || result.accuracy > 1.0f || result.scatterRadius < 0.0f ||
|
|
result.preferredTargetBonus < 1.0f)
|
|
return invalid<WeaponDefinition>("weapon damage falloff fields are outside valid ranges", "definition");
|
|
|
|
if (!readText(*object, "damageType", result.damageType) || !readText(*object, "element", result.element) ||
|
|
!readBoolean(*object, "targetsGround", result.targetsGround) ||
|
|
!readBoolean(*object, "targetsAir", result.targetsAir) ||
|
|
!readBoolean(*object, "friendlyFire", result.friendlyFire) ||
|
|
!readBoolean(*object, "blockedByObstacles", result.blockedByObstacles) ||
|
|
!readStringArray(*object, "requiredTargetTags", result.requiredTargetTags) ||
|
|
!readStringArray(*object, "excludedTargetTags", result.excludedTargetTags) ||
|
|
!readStringArray(*object, "preferredTargetTags", result.preferredTargetTags))
|
|
return invalid<WeaponDefinition>("weapon target policy fields are invalid", "definition");
|
|
if (!result.targetsGround && !result.targetsAir)
|
|
return invalid<WeaponDefinition>("weapon must target ground, air, or both", "definition");
|
|
|
|
if (const auto* modes = field(*object, "fireModes")) {
|
|
const auto* array = modes->getIf<eve::Value::Array>();
|
|
if (array == nullptr) return invalid<WeaponDefinition>("fireModes must be an array", "fireModes");
|
|
for (const auto& entry : *array) {
|
|
const auto* text = entry.getIf<std::string>();
|
|
if (text == nullptr) return invalid<WeaponDefinition>("fire mode must be a string", "fireModes");
|
|
result.selectableModes.push_back(fireModeFromName(*text));
|
|
}
|
|
}
|
|
std::string fireMode = fireModeName(result.fireMode);
|
|
if (!readText(*object, "fireMode", fireMode))
|
|
return invalid<WeaponDefinition>("fireMode must be a string", "fireMode");
|
|
result.fireMode = fireModeFromName(fireMode);
|
|
|
|
if (const auto* resource = field(*object, "resource")) {
|
|
const auto* nested = resource->getIf<eve::Value::Object>();
|
|
if (nested == nullptr) return invalid<WeaponDefinition>("resource must be an object", "resource");
|
|
std::string resourceName = resourceKindName(result.resource.kind);
|
|
if (!readText(*nested, "kind", resourceName) || !readNumber(*nested, "max", result.resource.max) ||
|
|
!readNumber(*nested, "regen", result.resource.regen) ||
|
|
!readNumber(*nested, "cost", result.resource.cost) ||
|
|
!readBoolean(*nested, "infinite", result.resource.infinite))
|
|
return invalid<WeaponDefinition>("weapon resource fields are invalid", "resource");
|
|
result.resource.kind = resourceKindFromName(resourceName);
|
|
}
|
|
if (const auto* stages = field(*object, "stages")) {
|
|
const auto* nested = stages->getIf<eve::Value::Object>();
|
|
if (nested == nullptr || !readNumber(*nested, "windup", result.stages.windupTime) ||
|
|
!readNumber(*nested, "active", result.stages.activeTime) ||
|
|
!readNumber(*nested, "recover", result.stages.recoverTime))
|
|
return invalid<WeaponDefinition>("stages fields are invalid", "stages");
|
|
}
|
|
if (const auto* burst = field(*object, "burst")) {
|
|
const auto* nested = burst->getIf<eve::Value::Object>();
|
|
if (nested == nullptr || !readInteger(*nested, "size", result.burstSize) ||
|
|
!readNumber(*nested, "interval", result.burstInterval))
|
|
return invalid<WeaponDefinition>("burst fields are invalid", "burst");
|
|
}
|
|
if (const auto* ammo = field(*object, "ammo")) {
|
|
const auto* nested = ammo->getIf<eve::Value::Object>();
|
|
if (nested == nullptr || !readInteger(*nested, "mag", result.magSize) ||
|
|
!readInteger(*nested, "reserve", result.reserveSize) || !readNumber(*nested, "reload", result.reloadTime))
|
|
return invalid<WeaponDefinition>("ammo fields are invalid", "ammo");
|
|
}
|
|
if (const auto* projectile = field(*object, "projectile")) {
|
|
const auto* nested = projectile->getIf<eve::Value::Object>();
|
|
if (nested == nullptr || !readText(*nested, "type", result.projectile.type) ||
|
|
!readNumber(*nested, "speed", result.projectile.speed) ||
|
|
!readNumber(*nested, "gravity", result.projectile.gravity) ||
|
|
!readNumber(*nested, "aoe", result.projectile.aoe) ||
|
|
!readInteger(*nested, "pelletCount", result.projectile.pelletCount) ||
|
|
!readNumber(*nested, "pelletSpread", result.projectile.pelletSpread))
|
|
return invalid<WeaponDefinition>("projectile fields are invalid", "projectile");
|
|
}
|
|
if (const auto* effects = field(*object, "effects")) {
|
|
const auto* nested = effects->getIf<eve::Value::Object>();
|
|
if (nested == nullptr || !readText(*nested, "muzzle", result.effectMuzzle) ||
|
|
!readText(*nested, "sound", result.effectSound))
|
|
return invalid<WeaponDefinition>("effects fields are invalid", "effects");
|
|
}
|
|
if (result.resource.kind == ResourceKind::None) result.resource.infinite = true;
|
|
return eve::Result<WeaponDefinition>::success(std::move(result));
|
|
}
|
|
|
|
eve::Result<eve::definition::DefinitionHandle> currentHandle(eve::definitions::DefinitionRegistry& registry,
|
|
const eve::DefinitionRef& reference) {
|
|
const auto& logical = reference.id();
|
|
return registry.handle(std::string(logical.namespaceName()), std::string(logical.name()));
|
|
}
|
|
|
|
eve::Result<WeaponDefinition> definitionFor(eve::definitions::DefinitionRegistry& registry,
|
|
const eve::definition::DefinitionHandle& handle,
|
|
const eve::DefinitionRef& reference) {
|
|
auto resolved = registry.resolveHandle(handle);
|
|
if (!resolved) return eve::Result<WeaponDefinition>::failure(resolved.status());
|
|
return parseDefinition(resolved.value().get(), reference);
|
|
}
|
|
|
|
eve::LogicalId runtimeSchema() {
|
|
static const eve::LogicalId value = [] {
|
|
auto parsed = eve::LogicalId::parse("weapon:runtime");
|
|
if (!parsed) throw std::logic_error("invalid built-in weapon snapshot schema");
|
|
return *parsed;
|
|
}();
|
|
return value;
|
|
}
|
|
|
|
WeaponRuntimeState initialState(const WeaponDefinition& definition) {
|
|
WeaponRuntimeState result;
|
|
result.selector = definition.fireMode;
|
|
result.currentSpread = definition.spreadMin;
|
|
result.resource.kind = definition.resource.kind;
|
|
result.resource.max = definition.resource.max;
|
|
result.resource.regen = definition.resource.regen;
|
|
result.resource.cost = definition.resource.cost;
|
|
result.resource.infinite = definition.resource.infinite;
|
|
if (definition.kind == WeaponKind::Ranged) {
|
|
result.resource.kind = ResourceKind::Ammo;
|
|
result.resource.max = static_cast<float>(definition.magSize);
|
|
result.resource.value = static_cast<float>(definition.magSize);
|
|
result.resource.cost = 1.f;
|
|
result.resource.reserve = definition.reserveSize < 0 ? 0 : definition.reserveSize;
|
|
result.resource.infinite = definition.reserveSize < 0;
|
|
} else {
|
|
result.resource.value = result.resource.infinite ? 0.f : result.resource.max;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
eve::Value encodeState(const WeaponRuntimeState& state) {
|
|
const Resource& resource = state.resource;
|
|
eve::Value::Object resourceObject{
|
|
{"cost", eve::Value(resource.cost)},
|
|
{"infinite", eve::Value(resource.infinite)},
|
|
{"kind", eve::Value(resourceKindName(resource.kind))},
|
|
{"max", eve::Value(resource.max)},
|
|
{"regen", eve::Value(resource.regen)},
|
|
{"reloadProgress", eve::Value(resource.reloadProgress)},
|
|
{"reloading", eve::Value(resource.reloading)},
|
|
{"reserve", eve::Value(resource.reserve)},
|
|
{"value", eve::Value(resource.value)},
|
|
};
|
|
return eve::Value(eve::Value::Object{
|
|
{"aiming", eve::Value(state.aiming)},
|
|
{"burstRemaining", eve::Value(state.burstRemaining)},
|
|
{"burstTimer", eve::Value(state.burstTimer)},
|
|
{"cooldown", eve::Value(state.cooldown)},
|
|
{"currentSpread", eve::Value(state.currentSpread)},
|
|
{"jammed", eve::Value(state.jammed)},
|
|
{"recoilPitch", eve::Value(state.recoilPitch)},
|
|
{"recoilYaw", eve::Value(state.recoilYaw)},
|
|
{"resource", eve::Value(std::move(resourceObject))},
|
|
{"selector", eve::Value(fireModeName(state.selector))},
|
|
{"stage", eve::Value(attackStageName(state.stage))},
|
|
{"stageTimer", eve::Value(state.stageTimer)},
|
|
});
|
|
}
|
|
|
|
bool readStateNumber(const eve::Value::Object& object, const char* name, float& output) {
|
|
return readNumber(object, name, output);
|
|
}
|
|
|
|
eve::Result<WeaponRuntimeState> decodeState(const eve::Value& value) {
|
|
const auto* object = value.getIf<eve::Value::Object>();
|
|
if (object == nullptr) return invalid<WeaponRuntimeState>("weapon runtime state must be an object", "state");
|
|
static const std::set<std::string> fields = {"aiming", "burstRemaining", "burstTimer", "cooldown",
|
|
"currentSpread", "jammed", "recoilPitch", "recoilYaw",
|
|
"resource", "selector", "stage", "stageTimer"};
|
|
for (const auto& [name, unused] : *object) {
|
|
(void)unused;
|
|
if (!fields.contains(name))
|
|
return invalid<WeaponRuntimeState>("unknown weapon runtime state field", "state." + name);
|
|
}
|
|
for (const auto& name : fields)
|
|
if (!object->contains(name))
|
|
return invalid<WeaponRuntimeState>("weapon runtime state is missing a field", "state." + name);
|
|
|
|
WeaponRuntimeState result;
|
|
std::string selector;
|
|
std::string stage;
|
|
if (!readBoolean(*object, "aiming", result.aiming) ||
|
|
!readInteger(*object, "burstRemaining", result.burstRemaining) ||
|
|
!readStateNumber(*object, "burstTimer", result.burstTimer) ||
|
|
!readStateNumber(*object, "cooldown", result.cooldown) ||
|
|
!readStateNumber(*object, "currentSpread", result.currentSpread) ||
|
|
!readBoolean(*object, "jammed", result.jammed) ||
|
|
!readStateNumber(*object, "recoilPitch", result.recoilPitch) ||
|
|
!readStateNumber(*object, "recoilYaw", result.recoilYaw) || !readText(*object, "selector", selector) ||
|
|
!readText(*object, "stage", stage) || !readStateNumber(*object, "stageTimer", result.stageTimer))
|
|
return invalid<WeaponRuntimeState>("weapon runtime scalar field is invalid", "state");
|
|
result.selector = fireModeFromName(selector);
|
|
result.stage = attackStageFromName(stage);
|
|
const auto* resourceValue = object->at("resource").getIf<eve::Value::Object>();
|
|
if (resourceValue == nullptr)
|
|
return invalid<WeaponRuntimeState>("weapon runtime resource is invalid", "state.resource");
|
|
std::string kind;
|
|
if (!readText(*resourceValue, "kind", kind) || !readStateNumber(*resourceValue, "value", result.resource.value) ||
|
|
!readStateNumber(*resourceValue, "max", result.resource.max) ||
|
|
!readStateNumber(*resourceValue, "regen", result.resource.regen) ||
|
|
!readStateNumber(*resourceValue, "cost", result.resource.cost) ||
|
|
!readBoolean(*resourceValue, "infinite", result.resource.infinite) ||
|
|
!readInteger(*resourceValue, "reserve", result.resource.reserve) ||
|
|
!readBoolean(*resourceValue, "reloading", result.resource.reloading) ||
|
|
!readStateNumber(*resourceValue, "reloadProgress", result.resource.reloadProgress))
|
|
return invalid<WeaponRuntimeState>("weapon runtime resource fields are invalid", "state.resource");
|
|
result.resource.kind = resourceKindFromName(kind);
|
|
return eve::Result<WeaponRuntimeState>::success(std::move(result));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
eve::Result<WeaponDefinition> parseWeaponDefinition(const eve::definitions::Definition& source) {
|
|
auto logical = eve::LogicalId::fromParts("weapon", source.id);
|
|
if (!logical) return invalid<WeaponDefinition>("weapon definition id is invalid", "id");
|
|
auto reference = eve::DefinitionRef::fromId(*logical);
|
|
if (!reference) return eve::Result<WeaponDefinition>::failure(reference.status());
|
|
if (source.type != "weapon") return invalid<WeaponDefinition>("definition type must be weapon", "type");
|
|
return parseDefinition(source, reference.value());
|
|
}
|
|
|
|
eve::Result<WeaponDefinitionRuntime> WeaponDefinitionRuntime::create(eve::definitions::DefinitionRegistry& registry,
|
|
eve::DefinitionRef definition,
|
|
eve::PersistentId instanceId,
|
|
eve::definition::ReloadPolicy policy) {
|
|
if (!definition.id().isValid() || definition.id().namespaceName() != "weapon")
|
|
return invalid<WeaponDefinitionRuntime>("weapon definition reference must use weapon namespace", "definition");
|
|
auto handle = currentHandle(registry, definition);
|
|
if (!handle) return eve::Result<WeaponDefinitionRuntime>::failure(handle.status());
|
|
auto typed = definitionFor(registry, handle.value(), definition);
|
|
if (!typed) return eve::Result<WeaponDefinitionRuntime>::failure(typed.status());
|
|
auto runtime = eve::definition::RuntimeInstance<WeaponRuntimeState>::create(
|
|
instanceId, std::move(definition), handle.value().generation, initialState(typed.value()));
|
|
if (!runtime) return eve::Result<WeaponDefinitionRuntime>::failure(runtime.status());
|
|
return eve::Result<WeaponDefinitionRuntime>::success(
|
|
WeaponDefinitionRuntime(registry, std::move(runtime).takeValue(), policy));
|
|
}
|
|
|
|
eve::Result<WeaponDefinitionRuntime> WeaponDefinitionRuntime::create(eve::definitions::DefinitionRegistry& registry,
|
|
std::string_view definitionId,
|
|
eve::PersistentId instanceId,
|
|
eve::definition::ReloadPolicy policy) {
|
|
auto logical = eve::LogicalId::fromParts("weapon", definitionId);
|
|
if (!logical) return invalid<WeaponDefinitionRuntime>("weapon id is not a valid logical name", "definitionId");
|
|
auto reference = eve::DefinitionRef::fromId(*logical);
|
|
if (!reference) return eve::Result<WeaponDefinitionRuntime>::failure(reference.status());
|
|
return create(registry, std::move(reference).takeValue(), instanceId, policy);
|
|
}
|
|
|
|
eve::Result<WeaponDefinitionRuntime> WeaponDefinitionRuntime::create(Weapon& module, std::string_view definitionId,
|
|
eve::PersistentId instanceId,
|
|
eve::definition::ReloadPolicy policy) {
|
|
return create(module.definitionRegistry(), definitionId, instanceId, policy);
|
|
}
|
|
|
|
const eve::definition::InstanceIdentity& WeaponDefinitionRuntime::identity() const noexcept {
|
|
return runtime_.identity();
|
|
}
|
|
|
|
const WeaponRuntimeState& WeaponDefinitionRuntime::state() const noexcept { return runtime_.state(); }
|
|
|
|
WeaponRuntimeState& WeaponDefinitionRuntime::state() noexcept { return runtime_.state(); }
|
|
|
|
eve::definition::DefinitionHandle WeaponDefinitionRuntime::definitionHandle() const noexcept {
|
|
return runtime_.identity().definitionHandle();
|
|
}
|
|
|
|
eve::Result<WeaponDefinition> WeaponDefinitionRuntime::definition() const {
|
|
if (registry_ == nullptr) return invalid<WeaponDefinition>("weapon definition registry is not bound", "registry");
|
|
return definitionFor(*registry_, definitionHandle(), identity().definition);
|
|
}
|
|
|
|
void WeaponDefinitionRuntime::setActive(bool active) noexcept { runtime_.setActive(active); }
|
|
|
|
bool WeaponDefinitionRuntime::isActive() const noexcept { return runtime_.isActive(); }
|
|
|
|
eve::Result<void> WeaponDefinitionRuntime::applyTo(WeaponEntity* entity) const {
|
|
if (entity == nullptr)
|
|
return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument,
|
|
"weapon runtime cannot project to a null WeaponEntity",
|
|
"entity", {}, "weapon.definition_runtime"));
|
|
auto typed = definition();
|
|
if (!typed) return eve::Result<void>::failure(typed.status());
|
|
try {
|
|
auto definitionOwner = std::make_shared<const WeaponDefinition>(typed.value());
|
|
auto definitionComponent = *entity->definition();
|
|
auto binding = *entity->definitionBinding();
|
|
auto targetState = *entity->state();
|
|
definitionComponent.owned = std::move(definitionOwner);
|
|
definitionComponent.def = definitionComponent.owned.get();
|
|
binding.identity = identity();
|
|
binding.reloadPolicy = policy_;
|
|
binding.active = isActive();
|
|
const auto& source = state();
|
|
targetState.resource = source.resource;
|
|
targetState.cooldown = source.cooldown;
|
|
targetState.burstRemaining = source.burstRemaining;
|
|
targetState.burstTimer = source.burstTimer;
|
|
targetState.jammed = source.jammed;
|
|
targetState.stage = source.stage;
|
|
targetState.stageTimer = source.stageTimer;
|
|
targetState.stages = &definitionComponent.def->stages;
|
|
targetState.currentSpread = source.currentSpread;
|
|
targetState.recoilPitch = source.recoilPitch;
|
|
targetState.recoilYaw = source.recoilYaw;
|
|
targetState.selector = source.selector;
|
|
targetState.aiming = source.aiming;
|
|
using std::swap;
|
|
swap(*entity->definition(), definitionComponent);
|
|
swap(*entity->definitionBinding(), binding);
|
|
swap(*entity->state(), targetState);
|
|
return eve::Result<void>::success();
|
|
} catch (const std::exception&) {
|
|
return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed,
|
|
"weapon runtime projection allocation failed",
|
|
"entity", {}, "weapon.definition_runtime"));
|
|
}
|
|
}
|
|
|
|
eve::Result<eve::definition::ReloadOutcome> WeaponDefinitionRuntime::reload(eve::definition::ReloadPolicy policy) {
|
|
if (registry_ == nullptr)
|
|
return invalid<eve::definition::ReloadOutcome>("weapon definition registry is not bound", "registry");
|
|
auto next = currentHandle(*registry_, identity().definition);
|
|
if (!next) return eve::Result<eve::definition::ReloadOutcome>::failure(next.status());
|
|
auto typed = definitionFor(*registry_, next.value(), identity().definition);
|
|
if (!typed) return eve::Result<eve::definition::ReloadOutcome>::failure(typed.status());
|
|
const WeaponRuntimeState defaults = initialState(typed.value());
|
|
const float maxSpread = std::max(defaults.currentSpread, typed.value().spreadMax);
|
|
auto rebuild = [defaults, maxSpread](const WeaponRuntimeState& oldState, const eve::definition::InstanceIdentity&,
|
|
const eve::definition::DefinitionHandle&) {
|
|
WeaponRuntimeState rebuilt = oldState;
|
|
if (oldState.resource.kind != defaults.resource.kind) {
|
|
rebuilt.resource = defaults.resource;
|
|
} else {
|
|
rebuilt.resource.kind = defaults.resource.kind;
|
|
rebuilt.resource.max = defaults.resource.max;
|
|
rebuilt.resource.regen = defaults.resource.regen;
|
|
rebuilt.resource.cost = defaults.resource.cost;
|
|
rebuilt.resource.infinite = defaults.resource.infinite;
|
|
if (rebuilt.resource.infinite) {
|
|
rebuilt.resource.value = 0.f;
|
|
} else {
|
|
rebuilt.resource.value = std::clamp(rebuilt.resource.value, 0.f, rebuilt.resource.max);
|
|
}
|
|
rebuilt.resource.reserve = defaults.resource.reserve < 0 ? 0 : rebuilt.resource.reserve;
|
|
}
|
|
rebuilt.selector = defaults.selector;
|
|
rebuilt.currentSpread = std::clamp(oldState.currentSpread, 0.f, maxSpread);
|
|
return eve::Result<WeaponRuntimeState>::success(std::move(rebuilt));
|
|
};
|
|
auto result = runtime_.reload(next.value(), policy, defaults, std::move(rebuild));
|
|
if (result.ok()) policy_ = policy;
|
|
return result;
|
|
}
|
|
|
|
eve::Result<eve::SnapshotEnvelope> WeaponDefinitionRuntime::snapshot(
|
|
eve::Revision revision, eve::SimulationTick tick, const eve::SnapshotHashProvider& hashProvider) const {
|
|
const eve::definition::RuntimeStateEncoder<WeaponRuntimeState> encoder = [](const WeaponRuntimeState& value) {
|
|
return eve::Result<eve::Value>::success(encodeState(value));
|
|
};
|
|
return eve::definition::snapshotRuntimeInstance(runtime_, "weapon.runtime", runtimeSchema(), eve::SchemaVersion(1),
|
|
revision, tick, hashProvider, encoder);
|
|
}
|
|
|
|
eve::Result<std::string> WeaponDefinitionRuntime::snapshotJson(eve::Revision revision, eve::SimulationTick tick,
|
|
const eve::SnapshotHashProvider& hashProvider) const {
|
|
auto result = snapshot(revision, tick, hashProvider);
|
|
if (!result) return eve::Result<std::string>::failure(result.status());
|
|
return std::move(result).andThen(
|
|
[](eve::SnapshotEnvelope&& value) { return eve::serializeSnapshotEnvelope(value); });
|
|
}
|
|
|
|
eve::Result<void> WeaponDefinitionRuntime::restore(const eve::SnapshotEnvelope& snapshotValue,
|
|
const eve::SnapshotHashProvider& hashProvider) {
|
|
if (registry_ == nullptr) return invalid<void>("weapon definition registry is not bound", "registry");
|
|
auto current = currentHandle(*registry_, identity().definition);
|
|
if (!current) return eve::Result<void>::failure(current.status());
|
|
if (current.value() != definitionHandle())
|
|
return eve::Result<void>::failure(eve::Diagnostic::error(
|
|
eve::DiagnosticCode::StaleHandle, "weapon snapshot cannot restore against a replaced definition",
|
|
"definitionGeneration", {}, "weapon.definition_runtime"));
|
|
const eve::definition::RuntimeStateDecoder<WeaponRuntimeState> decoder = decodeState;
|
|
return eve::definition::restoreRuntimeInstance(runtime_, snapshotValue, "weapon.runtime", runtimeSchema(),
|
|
eve::SchemaVersion(1), hashProvider, decoder);
|
|
}
|
|
|
|
} // namespace eve::weapon
|