10#include <simplesquirrel/simplesquirrel.hpp>
24class ScriptActionRuntime {
26 Result<Value> registerAbilityJson(
const std::string& abilityJson) {
31 const std::string definitionId = definition.value().id.format();
32 const LogicalId actionId = definition.value().action.id;
33 auto registered = abilities_.registerDefinition(std::move(definition).takeValue());
35 abilityActions_[definitionId] = actionId;
37 result[
"definitionId"] = definitionId;
38 result[
"actionId"] = actionId.format();
42 Result<Value> replaceAbilityJson(
const std::string& abilityJson) {
47 const std::string definitionId = definition.value().id.format();
48 const LogicalId actionId = definition.value().action.id;
49 auto replaced = abilities_.replaceDefinition(std::move(definition).takeValue());
51 abilityActions_[definitionId] = actionId;
53 result[
"definitionId"] = definitionId;
54 result[
"actionId"] = actionId.format();
58 Result<Value> registerTimelineAbility(
const std::string& definitionText,
const std::string& timelineJson,
59 double cooldownSeconds,
const std::string& instancingText,
60 const std::string& groupText,
const std::string& triggerTag) {
61 return setTimelineAbility(definitionText, timelineJson, cooldownSeconds, instancingText, groupText,
65 Result<Value> replaceTimelineAbility(
const std::string& definitionText,
const std::string& timelineJson,
66 double cooldownSeconds,
const std::string& instancingText,
67 const std::string& groupText,
const std::string& triggerTag) {
68 return setTimelineAbility(definitionText, timelineJson, cooldownSeconds, instancingText, groupText,
72 Result<Value> grantAbility(
const std::string& ownerId,
const std::string& definitionText) {
76 "ability definition id is invalid",
"definitionId", {},
78 if (!abilityActions_.contains(definitionText))
80 "ability definition is not registered", definitionText, {},
82 auto granted = abilities_.grant(ownerId, *definitionId);
84 return abilityGrantValue(granted.value());
87 Result<Value> revokeAbility(std::int64_t grantValue) {
88 auto grant = parseGrant(grantValue);
90 auto revoked = abilities_.revoke(grant.value());
95 Result<Value> activateAbility(std::int64_t grantValue, std::int64_t tickValue) {
96 auto grant = parseGrant(grantValue);
101 auto grantState = abilities_.findGrant(grant.value());
103 const auto action = abilityActions_.find(grantState.value().definitionId.format());
104 if (action == abilityActions_.end())
106 "ability definition action is unavailable",
"definitionId",
107 {},
"action.squirrel"));
112 auto activated = abilities_.activate(grant.value(), std::move(
request),
tick);
118 Result<Value> advanceAbilities(std::int64_t tickValue,
double seconds) {
125 std::optional<Status> firstFailure;
126 for (
const auto& activation : abilities_.activeActivations()) {
127 auto advanced = actions_.advance(activation.executionId,
130 if (!firstFailure) firstFailure = advanced.status();
134 value[
"activationId"] =
static_cast<std::int64_t
>(activation.id.value());
135 value[
"executionId"] =
static_cast<std::int64_t
>(activation.executionId.value());
137 value[
"elapsedSeconds"] = advanced.value().totalElapsed.seconds();
138 advances.emplace_back(std::move(
value));
140 auto cooled = abilities_.advanceCooldowns(delta.value());
145 auto synchronized = abilities_.synchronize();
149 result[
"advances"] = std::move(advances);
150 result[
"completedCount"] =
static_cast<std::int64_t
>(
synchronized.value());
151 result[
"activeCount"] =
static_cast<std::int64_t
>(abilities_.activeActivations().size());
155 Result<Value> cancelAbility(std::int64_t activationValue, std::int64_t tickValue) {
156 if (activationValue <= 0 || tickValue < 0)
158 "ability activation or tick is invalid",
"activation", {},
161 const auto active = abilities_.activeActivations();
163 std::find_if(
active.begin(),
active.end(), [&](
const auto& item) { return item.id == activationId; });
168 actions_.cancel(
found->executionId,
SimulationTick(
static_cast<std::uint64_t
>(tickValue)));
170 auto synchronized = abilities_.synchronize();
173 result[
"activationId"] = activationValue;
174 result[
"phase"] = std::string(
"cancelled");
178 Result<Value> abilityGrant(std::int64_t grantValue)
const {
179 auto grant = parseGrant(grantValue);
181 return abilityGrantValue(grant.value());
184 Result<Value> matchingAbilities(
const std::string& ownerId,
const std::string& eventTag)
const {
189 for (
const auto grant : abilities_.matchingGrants(ownerId, eventTag))
190 result.emplace_back(static_cast<
std::int64_t>(grant.
value()));
194 Result<Value> submitAction(
const std::string& actionIdText, std::int64_t windupNs, std::int64_t activeNs,
195 std::int64_t recoverNs, std::int64_t tickValue) {
199 "action id is invalid",
"actionId", {},
"action.squirrel"));
200 if (tickValue < 0 || windupNs < 0 || activeNs < 0 || recoverNs < 0)
202 "action timing or tick is invalid",
"timing", {},
204 ActionDefinition definition;
205 definition.id = *actionId;
212 auto submitted = actions_.submit(std::move(definition), std::move(
request));
215 result[
"executionId"] =
static_cast<std::int64_t
>(submitted.value().value());
219 Result<Value> advanceAction(std::int64_t executionValue, std::int64_t tickValue,
double seconds) {
220 if (executionValue <= 0 || tickValue < 0)
222 "action execution or tick is invalid",
"executionId", {},
231 result[
"executionId"] = executionValue;
232 result[
"phase"] = std::string(
actionPhaseName(advanced.value().phase));
233 result[
"elapsedSeconds"] = advanced.value().totalElapsed.seconds();
235 for (
const auto& transition : advanced.
value().transitions) {
239 item[
"tick"] =
static_cast<std::int64_t
>(transition.tick.value());
240 transitions.emplace_back(std::move(item));
242 result[
"transitions"] = std::move(transitions);
246 Result<Value> cancelAction(std::int64_t executionValue, std::int64_t tickValue) {
247 if (executionValue <= 0 || tickValue < 0)
249 "action execution or tick is invalid",
"executionId", {},
251 auto cancelled = actions_.cancel(
ActionExecutionId(
static_cast<std::uint64_t
>(executionValue)),
255 result[
"executionId"] = executionValue;
256 result[
"phase"] = std::string(
"cancelled");
260 Result<Value>
findAction(std::int64_t executionValue)
const {
261 if (executionValue <= 0)
263 "action execution id must be positive",
"executionId", {},
265 const auto* execution = actions_.find(
ActionExecutionId(
static_cast<std::uint64_t
>(executionValue)));
270 result[
"executionId"] = executionValue;
271 result[
"definitionId"] = execution->definition().id.format();
273 result[
"elapsedSeconds"] = execution->totalElapsed().seconds();
274 result[
"phaseSeconds"] = execution->phaseElapsed().seconds();
278 std::int64_t executionCount()
const {
return static_cast<std::int64_t
>(actions_.executionCount()); }
281 Result<Value> setTimelineAbility(
const std::string& definitionText,
const std::string& timelineJson,
282 double cooldownSeconds,
const std::string& instancingText,
283 const std::string& groupText,
const std::string& triggerTag,
bool replace) {
287 "ability definition id is invalid",
"definitionId", {},
296 AbilityDefinition definition;
297 definition.id = *definitionId;
298 definition.action.id = timeline.value().actionId;
299 definition.action.timeline = timeline.value();
300 definition.cooldown = cooldown.value();
301 if (instancingText ==
"non-instanced")
303 else if (instancingText ==
"per-owner")
305 else if (instancingText ==
"per-execution")
309 "ability instancing policy is invalid",
"instancing", {},
311 if (groupText ==
"independent")
313 else if (groupText ==
"exclusive-replaceable")
315 else if (groupText ==
"exclusive-blocking")
319 "ability activation group is invalid",
"activationGroup",
320 {},
"action.squirrel"));
321 if (!triggerTag.empty()) {
324 "ability trigger tag is invalid",
"triggerTag", {},
329 const auto& splits = timeline.value().splitTimestamps;
330 if (splits.empty()) {
331 definition.action.timing.active = timeline.value().duration;
332 }
else if (splits.size() == 1) {
333 definition.action.timing.windup = splits[0];
334 definition.action.timing.active =
337 definition.action.timing.windup = splits[0];
338 definition.action.timing.active =
340 definition.action.timing.recover =
344 replace ? abilities_.replaceDefinition(definition) : abilities_.registerDefinition(definition);
346 abilityActions_[definitionText] = definition.action.id;
348 result[
"definitionId"] = definitionText;
349 result[
"actionId"] = definition.action.id.format();
350 result[
"durationSeconds"] = timeline.value().duration.seconds();
351 result[
"sectionCount"] =
static_cast<std::int64_t
>(timeline.value().sectionCount());
355 static Result<AbilityGrantId> parseGrant(std::int64_t
value) {
358 "ability grant id must be positive",
"grantId", {},
364 auto state = abilities_.findGrant(grantId);
367 result[
"grantId"] =
static_cast<std::int64_t
>(
state.value().grantId.value());
368 result[
"ownerId"] =
state.value().ownerId;
369 result[
"definitionId"] =
state.value().definitionId.format();
370 result[
"cooldownSeconds"] =
state.value().cooldownRemaining.seconds();
374 static Value abilityActivationValue(
const AbilityActivation& activation) {
376 result[
"activationId"] =
static_cast<std::int64_t
>(activation.id.value());
377 result[
"grantId"] =
static_cast<std::int64_t
>(activation.grantId.value());
378 result[
"instanceId"] =
static_cast<std::int64_t
>(activation.instanceId.value());
379 result[
"executionId"] =
static_cast<std::int64_t
>(activation.executionId.value());
380 result[
"ownerId"] = activation.ownerId;
381 return Value(std::move(result));
384 ActionRuntime actions_;
385 AbilityRuntime abilities_{actions_};
386 std::map<std::string, LogicalId, std::less<>> abilityActions_;
395 "action runtime must not be null",
"runtime", {},
396 "action.squirrel")));
400 auto runtime = std::make_unique<ScriptActionRuntime>();
401 auto object = script::makeOwnedSquirrelInstance<ScriptActionRuntime>(
vm, std::move(runtime));
404 result.set(
"value", std::move(
object).takeValue());
405 result.set(
"ownership", std::string(
"owned"));
413void Action::expose(ssq::Table& table) {
415 auto runtime = table.addClass<ScriptActionRuntime>(
416 "ActionRuntime", std::function<ScriptActionRuntime*()>([] {
return nullptr; }),
true);
417 runtime.addFunc(
"ownership", [](ScriptActionRuntime*) {
return std::string(
"owned"); });
418 runtime.addFunc(
"registerAbilityJson", [
vm](ScriptActionRuntime* self,
const std::string& json) {
419 return self ? project(
vm, self->registerAbilityJson(json)) : nullRuntime(
vm);
421 runtime.addFunc(
"replaceAbilityJson", [
vm](ScriptActionRuntime* self,
const std::string& json) {
422 return self ?
project(
vm, self->replaceAbilityJson(json)) : nullRuntime(
vm);
424 runtime.addFunc(
"registerTimelineAbility",
425 [
vm](ScriptActionRuntime* self,
const std::string& definitionId,
const std::string& timelineJson,
426 float cooldownSeconds,
const std::string&
instancing,
const std::string& activationGroup,
427 const std::string& triggerTag) {
428 return self ?
project(
vm, self->registerTimelineAbility(definitionId, timelineJson,
430 activationGroup, triggerTag))
433 runtime.addFunc(
"replaceTimelineAbility",
434 [
vm](ScriptActionRuntime* self,
const std::string& definitionId,
const std::string& timelineJson,
435 float cooldownSeconds,
const std::string&
instancing,
const std::string& activationGroup,
436 const std::string& triggerTag) {
437 return self ?
project(
vm, self->replaceTimelineAbility(definitionId, timelineJson,
439 activationGroup, triggerTag))
442 runtime.addFunc(
"grantAbility",
443 [
vm](ScriptActionRuntime* self,
const std::string& owner,
const std::string& definitionId) {
444 return self ?
project(
vm, self->grantAbility(owner, definitionId)) : nullRuntime(
vm);
446 runtime.addFunc(
"revokeAbility", [
vm](ScriptActionRuntime* self, std::int64_t grantId) {
447 return self ?
project(
vm, self->revokeAbility(grantId)) : nullRuntime(
vm);
449 runtime.addFunc(
"activateAbility", [
vm](ScriptActionRuntime* self, std::int64_t grantId, std::int64_t
tick) {
450 return self ?
project(
vm, self->activateAbility(grantId,
tick)) : nullRuntime(
vm);
452 runtime.addFunc(
"advanceAbilities", [
vm](ScriptActionRuntime* self, std::int64_t
tick,
float seconds) {
453 return self ?
project(
vm, self->advanceAbilities(
tick, seconds)) : nullRuntime(
vm);
455 runtime.addFunc(
"cancelAbility", [
vm](ScriptActionRuntime* self, std::int64_t activationId, std::int64_t
tick) {
456 return self ?
project(
vm, self->cancelAbility(activationId,
tick)) : nullRuntime(
vm);
458 runtime.addFunc(
"abilityGrant", [
vm](ScriptActionRuntime* self, std::int64_t grantId) {
459 return self ?
project(
vm, self->abilityGrant(grantId)) : nullRuntime(
vm);
461 runtime.addFunc(
"matchingAbilities",
462 [
vm](ScriptActionRuntime* self,
const std::string& owner,
const std::string& eventTag) {
463 return self ?
project(
vm, self->matchingAbilities(owner, eventTag)) : nullRuntime(
vm);
465 runtime.addFunc(
"submitAction", [
vm](ScriptActionRuntime* self,
const std::string& actionId, std::int64_t windupNs,
466 std::int64_t activeNs, std::int64_t recoverNs, std::int64_t
tick) {
467 return self ?
project(
vm, self->submitAction(actionId, windupNs, activeNs, recoverNs,
tick))
470 runtime.addFunc(
"advanceAction",
471 [
vm](ScriptActionRuntime* self, std::int64_t
executionId, std::int64_t
tick,
float seconds) {
474 runtime.addFunc(
"cancelAction", [
vm](ScriptActionRuntime* self, std::int64_t
executionId, std::int64_t
tick) {
477 runtime.addFunc(
"findAction", [
vm](ScriptActionRuntime* self, std::int64_t
executionId) {
480 runtime.addFunc(
"executionCount",
481 [](ScriptActionRuntime* self) {
return self ? self->executionCount() : std::int64_t{0}; });
483 auto module = table.addClass(name, Action::create, false);
484 module.addFunc("getName", &Action::getName);
485 module.addFunc("newRuntime", [vm](Action*) { return newRuntime(vm); });
488void Action::expose(ssq::Class&
cls) {
489 cls.addFunc(
"getName", &Action::getName);
490 cls.addFunc(
"newRuntime", [
vm =
cls.getHandle()](Action*) { return newRuntime(vm); });
Versioned persistent codec for ability definitions.
Grants, cooldowns, instancing, activation groups and gameplay-event triggers.
Script-facing factory for ActionRuntime + AbilityRuntime.
Versioned, deterministic authoring timeline for gameplay actions.
eve::action::ActionExecutionId executionId
struct SQVM * HSQUIRRELVM
Stable hierarchical gameplay-tag definitions and registry.
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
#define Module_IMPL(ModuleName, newExpr)
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
AbilityInstancingPolicy instancing
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.
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
static constexpr Duration fromNanoseconds(std::int64_t nanoseconds) noexcept
Construct an exact duration from nanoseconds.
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
std::map< std::string, Value > Object
std::vector< Value > Array
Factory module for script-owned action/ability protocol runtimes.
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
const char * actionPhaseName(ActionPhase phase) noexcept
Return the stable lowercase spelling of an action phase.
eve::detail::StrongUint64< AbilityActivationIdTag > AbilityActivationId
Opaque identity of one activation attempt accepted by ActionRuntime.
Result< AbilityDefinition > decodeAbilityAsset(const Value &value)
Decode schema version 2 or migrate version 1 millisecond data to the current definition.
eve::detail::StrongUint64< ActionExecutionIdTag > ActionExecutionId
eve::detail::StrongUint64< AbilityGrantIdTag > AbilityGrantId
Opaque identity of one owner-to-definition grant.
const ClimbingActionDefinition * findAction(const ClimbingProfile &profile, std::string_view id)
Optional action. @borrowed From profile; lifetime ends when the caller-owned profile changes or is de...
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
ssq::Table project(HSQUIRRELVM vm, const eve::Result< void > &result)
Project a completed editing result that has no payload.
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
static Result< ActionTimeline > fromValue(const Value &value)
Decode and validate one canonical schema value transactionally.