载入中...
搜索中...
未找到
SkillDefinitionRuntime.cpp
浏览该文件的文档.
2
3#include "common/Value.h"
4#include "rpg/RPGActor.h"
6
7#include <algorithm>
8#include <cmath>
9#include <cstdint>
10#include <limits>
11#include <set>
12#include <stdexcept>
13#include <utility>
14
15namespace eve::rpg {
16namespace {
17
18eve::LogicalId skillSchema() {
19 static const eve::LogicalId value = [] {
20 const auto parsed = eve::LogicalId::parse("rpg.skill:runtime");
21 if (!parsed) throw std::logic_error("invalid built-in skill snapshot schema");
22 return *parsed;
23 }();
24 return value;
25}
26
29 const auto& logical = reference.id();
30 return registry.handle(std::string(logical.namespaceName()), std::string(logical.name()));
31}
32
33const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
34 const auto it = object.find(std::string(name));
35 return it == object.end() ? nullptr : &it->second;
36}
37
38bool readNumber(const eve::Value::Object& object, std::string_view name, double& result) {
39 const auto* value = field(object, name);
40 if (const auto* number = value ? value->getIf<double>() : nullptr) {
41 result = *number;
42 return std::isfinite(result);
43 }
44 if (const auto* integer = value ? value->getIf<std::int64_t>() : nullptr) {
45 result = static_cast<double>(*integer);
46 return std::isfinite(result);
47 }
48 return false;
49}
50
51eve::Result<std::vector<std::string>> readStrings(const eve::Value::Object& object, std::string_view name) {
52 const auto* value = field(object, name);
53 if (value == nullptr) return eve::Result<std::vector<std::string>>::success({});
54 const auto* array = value->getIf<eve::Value::Array>();
55 if (array == nullptr)
58 std::string(name), {}, "rpg.skill.definition_runtime"));
59 std::vector<std::string> result;
60 result.reserve(array->size());
61 for (std::size_t index = 0; index < array->size(); ++index) {
62 const auto* text = (*array)[index].getIf<std::string>();
63 if (text == nullptr || text->empty())
64 return eve::Result<std::vector<std::string>>::failure(eve::Diagnostic::error(
65 eve::DiagnosticCode::InvalidArgument, "skill array item must be a non-empty string",
66 std::string(name) + "[" + std::to_string(index) + "]", {}, "rpg.skill.definition_runtime"));
67 result.push_back(*text);
68 }
69 return eve::Result<std::vector<std::string>>::success(std::move(result));
70}
71
75 const auto* payload = owned.value().getIf<eve::Value::Object>();
76 if (payload == nullptr)
78 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill definition payload must be an object",
79 "json", {}, "rpg.skill.definition_runtime"));
80
81 SkillDefinition result;
82 result.id = source.id;
83 const auto* target = field(*payload, "targetType");
84 if (target != nullptr) {
85 const auto* text = target->getIf<std::string>();
86 if (text == nullptr || text->empty())
88 eve::DiagnosticCode::InvalidArgument, "skill targetType must be a non-empty string", "targetType", {},
89 "rpg.skill.definition_runtime"));
90 result.targetType = *text;
91 }
92 double number = 0.0;
93 if (field(*payload, "cooldown") != nullptr) {
94 if (!readNumber(*payload, "cooldown", number) || number < 0.0 || number > std::numeric_limits<float>::max())
96 eve::DiagnosticCode::InvalidArgument, "skill cooldown must be finite and non-negative", "cooldown", {},
97 "rpg.skill.definition_runtime"));
98 result.cooldown = static_cast<float>(number);
99 }
100 if (field(*payload, "castTime") != nullptr) {
101 if (!readNumber(*payload, "castTime", number) || number < 0.0 || number > std::numeric_limits<float>::max())
103 eve::DiagnosticCode::InvalidArgument, "skill castTime must be finite and non-negative", "castTime", {},
104 "rpg.skill.definition_runtime"));
105 result.castTime = static_cast<float>(number);
106 }
107 auto effects = readStrings(*payload, "grantedEffects");
108 if (!effects) return eve::Result<SkillDefinition>::failure(effects.status());
109 result.grantedEffects = std::move(effects).takeValue();
110 auto tags = readStrings(*payload, "tags");
111 if (!tags) return eve::Result<SkillDefinition>::failure(tags.status());
112 result.tags = std::move(tags).takeValue();
113 if (const auto* condition = field(*payload, "castCondition")) {
114 auto decoded = decodeSkillCondition(*condition);
115 if (!decoded) return eve::Result<SkillDefinition>::failure(decoded.status());
116 result.castCondition = std::move(decoded).takeValue();
117 }
118 auto extras = field(*payload, "extra");
119 if (extras != nullptr) {
120 const auto* extraObject = extras->getIf<eve::Value::Object>();
121 if (extraObject == nullptr)
123 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill extra must be an object", "extra",
124 {}, "rpg.skill.definition_runtime"));
125 for (const auto& [key, value] : *extraObject) {
126 const auto* text = value.getIf<std::string>();
127 if (text == nullptr)
129 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill extra values must be strings",
130 "extra." + key, {}, "rpg.skill.definition_runtime"));
131 result.extra.emplace(key, *text);
132 }
133 }
134 const auto* costs = field(*payload, "costs");
135 if (costs != nullptr) {
136 const auto* array = costs->getIf<eve::Value::Array>();
137 if (array == nullptr)
139 "skill costs must be an array", "costs",
140 {}, "rpg.skill.definition_runtime"));
141 std::vector<eve::resource::ResourceCost> items;
142 for (std::size_t index = 0; index < array->size(); ++index) {
143 const auto* item = (*array)[index].getIf<eve::Value::Object>();
144 if (item == nullptr)
146 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill cost must be an object",
147 "costs[" + std::to_string(index) + "]", {}, "rpg.skill.definition_runtime"));
148 const auto* resource = field(*item, "attribute");
149 const auto* amount = field(*item, "amount");
150 const auto* name = resource ? resource->getIf<std::string>() : nullptr;
151 const auto* integer = amount ? amount->getIf<std::int64_t>() : nullptr;
152 if (name == nullptr || name->empty() || integer == nullptr || *integer <= 0)
154 eve::DiagnosticCode::InvalidArgument, "skill cost requires a positive attribute amount",
155 "costs[" + std::to_string(index) + "]", {}, "rpg.skill.definition_runtime"));
157 if (!cost) return eve::Result<SkillDefinition>::failure(cost.status());
158 items.push_back(std::move(cost).takeValue());
159 }
160 if (!items.empty()) {
161 auto cost = eve::resource::CostSpec::create(std::move(items));
162 if (!cost) return eve::Result<SkillDefinition>::failure(cost.status());
163 result.cost = std::move(cost).takeValue();
164 }
165 }
166 return eve::Result<SkillDefinition>::success(std::move(result));
167}
168
169eve::Value encodeState(const SkillRuntimeState& state) {
171 {"cooldownRemaining", eve::Value(static_cast<double>(state.cooldownRemaining))},
172 {"learned", eve::Value(state.learned)},
173 });
174}
175
177 const auto* object = value.getIf<eve::Value::Object>();
178 if (object == nullptr)
180 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill runtime state must be an object",
181 "state", {}, "rpg.skill.definition_runtime"));
182 static const std::set<std::string> fields = {"cooldownRemaining", "learned"};
183 for (const auto& [name, unused] : *object) {
184 (void)unused;
185 if (!fields.contains(name))
187 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unknown skill runtime state field",
188 "state." + name, {}, "rpg.skill.definition_runtime"));
189 }
190 if (!object->contains("cooldownRemaining") || !object->contains("learned"))
192 eve::DiagnosticCode::InvalidArgument, "skill runtime state is missing a required field", "state", {},
193 "rpg.skill.definition_runtime"));
194 const auto* cooldown = object->at("cooldownRemaining").getIf<double>();
195 const auto* learned = object->at("learned").getIf<bool>();
196 if (cooldown == nullptr || learned == nullptr || !std::isfinite(*cooldown) || *cooldown < 0.0 ||
197 *cooldown > std::numeric_limits<float>::max())
199 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill cooldown state is invalid",
200 "state.cooldownRemaining", {}, "rpg.skill.definition_runtime"));
201 return eve::Result<SkillRuntimeState>::success(SkillRuntimeState{static_cast<float>(*cooldown), *learned});
202}
203
204} // namespace
205
207 eve::DefinitionRef definition,
208 eve::PersistentId instanceId,
210 if (!definition.id().isValid() || definition.id().namespaceName() != "rpg.skill")
212 eve::DiagnosticCode::InvalidArgument, "skill definition reference must use rpg.skill namespace",
213 "definition", {}, "rpg.skill.definition_runtime"));
214 auto handle = currentHandle(registry, definition);
216 auto resolved = registry.resolveHandle(handle.value());
217 if (!resolved) return eve::Result<SkillDefinitionRuntime>::failure(resolved.status());
218 auto typed = parseDefinition(resolved.value().get());
219 if (!typed) return eve::Result<SkillDefinitionRuntime>::failure(typed.status());
221 instanceId, std::move(definition), handle.value().generation, SkillRuntimeState{});
222 if (!runtime) return eve::Result<SkillDefinitionRuntime>::failure(runtime.status());
224 SkillDefinitionRuntime(registry, std::move(runtime).takeValue(), policy));
225}
226
228 std::string_view skillId,
230 auto logical = eve::LogicalId::fromParts("rpg.skill", skillId);
231 if (!logical)
233 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill id is not a valid logical name",
234 "skillId", {}, "rpg.skill.definition_runtime"));
235 auto reference = eve::DefinitionRef::fromId(*logical);
237 return create(eve::rpg::SkillRegistry::definitionRegistry(), std::move(reference).takeValue(), instanceId, policy);
238}
239
241 return runtime_.identity();
242}
243
244const SkillRuntimeState& SkillDefinitionRuntime::state() const noexcept { return runtime_.state(); }
245
246SkillRuntimeState& SkillDefinitionRuntime::state() noexcept { return runtime_.state(); }
247
249 return runtime_.identity().definitionHandle();
250}
251
253 if (registry_ == nullptr)
255 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill definition registry is not bound",
256 "registry", {}, "rpg.skill.definition_runtime"));
257 auto resolved = registry_->resolveHandle(definitionHandle());
258 if (!resolved) return eve::Result<SkillDefinition>::failure(resolved.status());
259 return parseDefinition(resolved.value().get());
260}
261
262void SkillDefinitionRuntime::setActive(bool active) noexcept { runtime_.setActive(active); }
263
264bool SkillDefinitionRuntime::isActive() const noexcept { return runtime_.isActive(); }
265
267 if (actor == nullptr)
269 "skill runtime cannot project to a null RPGActor",
270 "actor", {}, "rpg.skill.definition_runtime"));
271 auto candidate = *actor->skills().operator->();
272 const std::string id(identity().definition.id().name());
273 if (state().learned)
274 candidate.known[id] = SkillRuntime{state().cooldownRemaining};
275 else
276 candidate.known.erase(id);
277 using std::swap;
278 swap(*actor->skills().operator->(), candidate);
280}
281
283 if (registry_ == nullptr)
285 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "skill definition registry is not bound",
286 "registry", {}, "rpg.skill.definition_runtime"));
287 auto next = currentHandle(*registry_, identity().definition);
288 if (!next) return eve::Result<eve::definition::ReloadOutcome>::failure(next.status());
289 auto resolved = registry_->resolveHandle(next.value());
290 if (!resolved) return eve::Result<eve::definition::ReloadOutcome>::failure(resolved.status());
291 auto typed = parseDefinition(resolved.value().get());
292 if (!typed) return eve::Result<eve::definition::ReloadOutcome>::failure(typed.status());
293 const float nextCooldown = typed.value().cooldown;
294 SkillRuntimeState defaults{};
295 auto rebuild = [nextCooldown](const SkillRuntimeState& oldState, const eve::definition::InstanceIdentity&,
297 SkillRuntimeState rebuilt = oldState;
298 rebuilt.cooldownRemaining = std::clamp(oldState.cooldownRemaining, 0.f, nextCooldown);
300 };
301 auto result = runtime_.reload(next.value(), policy, defaults, std::move(rebuild));
302 if (result.ok()) policy_ = policy;
303 return result;
304}
305
314
316 const eve::SnapshotHashProvider& hashProvider) const {
317 auto result = snapshot(revision, tick, hashProvider);
318 if (!result) return eve::Result<std::string>::failure(result.status());
319 return std::move(result).andThen(
321}
322
324 const eve::SnapshotHashProvider& hashProvider) {
325 if (registry_ == nullptr)
327 "skill definition registry is not bound", "registry",
328 {}, "rpg.skill.definition_runtime"));
329 auto current = currentHandle(*registry_, identity().definition);
330 if (!current) return eve::Result<void>::failure(current.status());
331 if (current.value() != definitionHandle())
333 eve::DiagnosticCode::StaleHandle, "skill snapshot cannot restore against a replaced definition",
334 "definitionGeneration", {}, "rpg.skill.definition_runtime"));
336 return eve::definition::restoreRuntimeInstance(runtime_, snapshotValue, "rpg.skill.runtime", skillSchema(),
337 eve::SchemaVersion(1), hashProvider, decoder);
338}
339
340} // namespace eve::rpg
LogicalId target
double value
Value::Object payload
bool & active
ActiveSource owned
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
eve::resource::CostSpec cost
eve::Value condition
std::uint32_t key
std::string text
std::string name
std::uint64_t revision
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
PrimitiveHandle handle
double number
double current
std::string resource
RPG-owned Value codec for the shared decision condition tree.
Strongly typed runtime adapter for RPG skill definitions.
SimulationTick tick
Json object
uint32_t index
const UnitySourceAsset & source
Owning, renderer-independent dynamic values.
Stable definition reference backed by LogicalId.
Definition ResourceRef.h:85
static Result< DefinitionRef > fromId(LogicalId id)
Build a definition reference from a valid LogicalId.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
static std::optional< LogicalId > fromParts(std::string_view namespaceName, std::string_view name)
Builds a logical ID from separate namespace and name components.
Definition Identity.cpp:48
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
static eve::Result< RuntimeInstance > create(eve::PersistentId instanceId, eve::DefinitionRef definition, eve::Generation definitionGeneration, State state, bool active=true)
Construct a runtime instance after validating its identity.
Deterministic registry of topic-neutral, versioned JSON definitions.
Definition Definitions.h:58
eve::ResultRef< const Definition > resolveHandle(const DefinitionHandle &handle) const
Resolves a generation-qualified handle and reports stale identities.
eve::Result< DefinitionHandle > handle(const std::string &type, const std::string &id) const
Returns the current generation-qualified handle through Result.
Id128 public API.
Definition Identity.h:112
static eve::Result< CostSpec > create(std::vector< ResourceCost > items)
Validate and canonicalize resource items.
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
Typed SkillDefinition-to-runtime adapter.
eve::Result< std::string > snapshotJson(eve::Revision revision, eve::SimulationTick tick, const eve::SnapshotHashProvider &hashProvider) const
Serialize snapshot() as canonical JSON.
void setActive(bool active) noexcept
Set the active-cast guard used by RejectWhileActive.
eve::definition::DefinitionHandle definitionHandle() const noexcept
Return the exact generation-qualified definition handle.
const eve::definition::InstanceIdentity & identity() const noexcept
Borrow the common identity, including exact definition generation.
eve::Result< SkillDefinition > definition() const
Resolve and parse the current typed definition projection.
bool isActive() const noexcept
Return whether this skill is currently active.
eve::Result< eve::SnapshotEnvelope > snapshot(eve::Revision revision, eve::SimulationTick tick, const eve::SnapshotHashProvider &hashProvider) const
Capture this skill's instance state in a common SnapshotEnvelope.
eve::Result< void > applyTo(RPGActor *actor) const
Project this skill instance into an RPGActor's Skills component.
eve::Result< void > restore(const eve::SnapshotEnvelope &snapshot, const eve::SnapshotHashProvider &hashProvider)
Verify and restore a same-generation skill snapshot atomically.
const SkillRuntimeState & state() const noexcept
Borrow mutable per-instance skill state.
static eve::Result< SkillDefinitionRuntime > create(eve::definitions::DefinitionRegistry &registry, eve::DefinitionRef definition, eve::PersistentId instanceId, eve::definition::ReloadPolicy policy=eve::definition::ReloadPolicy::RebuildInstance)
Create a runtime from a generation-qualified common definition.
eve::Result< eve::definition::ReloadOutcome > reload(eve::definition::ReloadPolicy policy)
Reload the current registry generation using a typed policy.
static eve::definitions::DefinitionRegistry & definitionRegistry()
Return the common registry that owns canonical skill JSON.
Definition Skill.cpp:70
ReloadPolicy
Policy used when a live definition is replaced.
eve::Result< eve::SnapshotEnvelope > snapshotRuntimeInstance(const RuntimeInstance< State > &runtime, std::string type, eve::LogicalId schema, eve::SchemaVersion schemaVersion, eve::Revision revision, eve::SimulationTick tick, const eve::SnapshotHashProvider &hashProvider, const RuntimeStateEncoder< State > &encode)
Capture a typed runtime instance in the common snapshot envelope.
eve::Result< void > restoreRuntimeInstance(RuntimeInstance< State > &runtime, const eve::SnapshotEnvelope &snapshot, std::string_view expectedType, const eve::LogicalId &expectedSchema, eve::SchemaVersion expectedVersion, const eve::SnapshotHashProvider &hashProvider, const RuntimeStateDecoder< State > &decode)
Verify and atomically restore a typed runtime instance.
std::function< eve::Result< State >(const eve::Value &)> RuntimeStateDecoder
Decodes one domain-owned runtime state from an owning Value.
std::function< eve::Result< eve::Value >(const State &)> RuntimeStateEncoder
Converts one domain-owned runtime state into an owning Value.
const EditorValue * field(const EditorValue &value, const char *name)
bool readNumber(const EditorValue &value, const char *name, float &output)
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
eve::Result< eve::decision::Condition > decodeSkillCondition(const eve::Value &value)
Decode one Skill condition tree from an owning Value.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
Result< std::string > serializeSnapshotEnvelope(const SnapshotEnvelope &snapshot)
Serialize an envelope as deterministic compact JSON.
Definition Snapshot.cpp:184
std::function< Result< ContentId >(std::string_view canonicalInput)> SnapshotHashProvider
Injected content-digest implementation used by snapshots.
Definition Snapshot.h:36
Stable outer format shared by persistence and cross-process snapshots.
Definition Snapshot.h:46
A generation-qualified reference to one definition incarnation.
Canonical identity carried by every typed runtime instance.
One immutable view of a registered versioned JSON definition.
Definition Definitions.h:31
static eve::Result< ResourceCost > create(std::string_view resourceId, std::int64_t amountValue)
Construct and validate one cost item.
Mutable state belonging to one learned skill instance.
单个已学会技能的运行时状态(冷却)。
Definition SkillTypes.h:11