载入中...
搜索中...
未找到
Combat.cpp
浏览该文件的文档.
1#include "combat/Combat.h"
2
6#include "combat/Damage.h"
10
11#include <simplesquirrel/simplesquirrel.hpp>
12
13#include <algorithm>
14#include <cmath>
15#include <cstdint>
16#include <functional>
17#include <map>
18#include <memory>
19#include <string>
20#include <utility>
21
22namespace eve::combat {
23namespace {
24
25Result<SubjectRef> parseSubject(const std::string& text, std::string path) {
26 const auto parsed = PersistentId::parse(text);
27 if (!parsed)
29 DiagnosticCode::InvalidArgument, "combat subject must be a UUID", path, {}, "combat.squirrel"));
31}
32
33Value vectorValue(CombatVector2 value) {
34 Value::Object result;
35 result["x"] = value.x;
36 result["z"] = value.z;
37 return Value(std::move(result));
38}
39
40std::string reactionName(HitReaction reaction) {
41 switch (reaction) {
42 case HitReaction::None: return "none";
43 case HitReaction::Flinch: return "flinch";
44 case HitReaction::Stagger: return "stagger";
45 case HitReaction::Knockdown: return "knockdown";
46 case HitReaction::Death: return "death";
47 }
48 return "none";
49}
50
51class ScriptCombatRuntime {
52public:
53 ScriptCombatRuntime() : locomotion_(navigation_) {}
54
55 Result<void> initializeAbilities() {
57 if (!definitions) return Result<void>::failure(definitions.status());
58 for (auto& definition : definitions.value()) {
59 const std::string definitionId = definition.id.format();
60 abilityActions_[definitionId] = definition.action.id;
61 auto registered = abilities_.registerDefinition(std::move(definition));
62 if (!registered) return registered;
63 }
65 }
66
67 Result<Value> registerFighter(const std::string& subjectText, const std::string& ownerId,
68 double x, double z, double health, double poise,
69 double maximumSpeed, double acceleration) {
70 auto subject = parseSubject(subjectText, "subject");
71 if (!subject) return Result<Value>::failure(subject.status());
72 CombatState combatState{subject.value(), health, health, poise, poise};
73 auto valid = combatState.validate();
74 if (!valid) return Result<Value>::failure(valid.status());
75 if (states_.contains(subjectText))
77 DiagnosticCode::AlreadyExists, "combat fighter already exists", subjectText, {}, "combat.squirrel"));
78 auto registered = locomotion_.registerSubject(
79 {subject.value(), ownerId, {x, z}, maximumSpeed, acceleration});
80 if (!registered) return Result<Value>::failure(registered.status());
81 states_.emplace(subjectText, std::move(combatState));
82 return state(subjectText);
83 }
84
85 Result<Value> setMoveIntent(const std::string& subjectText, double x, double z,
86 double speedFraction) {
87 auto subject = parseSubject(subjectText, "subject");
88 if (!subject) return Result<Value>::failure(subject.status());
89 auto changed = locomotion_.setMoveIntent(subject.value(), {x, z}, speedFraction);
90 if (!changed) return Result<Value>::failure(changed.status());
91 return state(subjectText);
92 }
93
94 Result<Value> navigateTo(const std::string& subjectText, double x, double z,
95 double acceptanceRadius) {
96 auto subject = parseSubject(subjectText, "subject");
97 if (!subject) return Result<Value>::failure(subject.status());
98 auto changed = locomotion_.navigateTo(subject.value(), {{x, z}, acceptanceRadius});
99 if (!changed) return Result<Value>::failure(changed.status());
100 return state(subjectText);
101 }
102
103 Result<Value> stop(const std::string& subjectText) {
104 auto subject = parseSubject(subjectText, "subject");
105 if (!subject) return Result<Value>::failure(subject.status());
106 auto stopped = locomotion_.stop(subject.value());
107 if (!stopped) return Result<Value>::failure(stopped.status());
108 return state(subjectText);
109 }
110
111 Result<Value> advance(std::int64_t tick, double seconds) {
112 if (tick < 0)
114 DiagnosticCode::InvalidArgument, "combat tick must be non-negative", "tick", {}, "combat.squirrel"));
115 auto delta = Duration::fromSeconds(seconds);
116 if (!delta) return Result<Value>::failure(delta.status());
117 auto advanced = locomotion_.advance(
118 {SimulationTick(static_cast<std::uint64_t>(tick)), std::move(delta).takeValue()});
119 if (!advanced) return Result<Value>::failure(advanced.status());
121 events.reserve(advanced.value().events.size());
122 for (const auto& event : advanced.value().events) {
124 value["subject"] = event.subject.format();
125 value["kind"] = event.kind == CombatLocomotionEventKind::Arrived ? "arrived" : "moved";
126 value["position"] = vectorValue(event.position);
127 events.emplace_back(std::move(value));
128 }
129 Value::Object result;
130 result["tick"] = tick;
131 result["events"] = std::move(events);
133 }
134
135 Result<Value> applyDamage(const std::string& sourceText, const std::string& targetText,
136 const std::string& damageType, double healthDamage,
137 double poiseDamage, double knockbackX, double knockbackY,
138 double knockbackZ) {
139 auto source = parseSubject(sourceText, "source");
140 if (!source) return Result<Value>::failure(source.status());
141 auto target = parseSubject(targetText, "target");
142 if (!target) return Result<Value>::failure(target.status());
143 const auto found = states_.find(targetText);
144 if (found == states_.end())
145 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::NotFound, "combat target was not found",
146 targetText, {}, "combat.squirrel"));
147 DamageRequest request;
148 request.source = source.value();
149 request.target = target.value();
150 request.damageType = damageType;
151 request.healthDamage = healthDamage;
152 request.poiseDamage = poiseDamage;
153 request.knockback = {knockbackX, knockbackY, knockbackZ};
154 auto outcome = damage_.apply(found->second, request);
155 if (!outcome) return Result<Value>::failure(outcome.status());
156 Value::Object result;
157 result["source"] = outcome.value().source.format();
158 result["target"] = outcome.value().target.format();
159 result["health"] = outcome.value().health;
160 result["poise"] = outcome.value().poise;
161 result["appliedHealthDamage"] = outcome.value().appliedHealthDamage;
162 result["appliedPoiseDamage"] = outcome.value().appliedPoiseDamage;
163 result["reaction"] = reactionName(outcome.value().reaction);
165 }
166
167 Result<Value> applyTimelineDamage(const std::string& sourceText, const std::string& targetText,
168 const std::string& payloadJson) {
169 auto decoded = Value::fromJson(payloadJson);
170 if (!decoded) return Result<Value>::failure(decoded.status());
171 const auto* payload = decoded.value().getIf<Value::Object>();
172 if (!payload)
174 "combat timeline damage payload must be an object",
175 "payload", {}, "combat.squirrel"));
177 if (!binding) return Result<Value>::failure(binding.status());
178 return applyDamage(sourceText, targetText, binding.value().damageType, binding.value().amount,
179 binding.value().poiseAmount, binding.value().knockback.x,
180 binding.value().knockback.y, binding.value().knockback.z);
181 }
182
183 Result<Value> grantAbility(const std::string& ownerId, const std::string& definitionText) {
184 auto definitionId = LogicalId::parse(definitionText);
185 if (!definitionId)
187 "ability definition id is invalid", "definitionId", {},
188 "combat.squirrel"));
189 const auto action = abilityActions_.find(definitionText);
190 if (action == abilityActions_.end())
192 "ability definition is not registered", definitionText, {},
193 "combat.squirrel"));
194 auto granted = abilities_.grant(ownerId, *definitionId);
195 if (!granted) return Result<Value>::failure(granted.status());
196 return abilityGrantValue(granted.value());
197 }
198
199 Result<Value> registerTimelineAbility(const std::string& definitionText,
200 const std::string& timelineJson, double cooldownSeconds,
201 const std::string& instancingText,
202 const std::string& groupText,
203 const std::string& triggerTag) {
204 return setTimelineAbility(definitionText, timelineJson, cooldownSeconds, instancingText,
205 groupText, triggerTag, false);
206 }
207
208 Result<Value> replaceTimelineAbility(const std::string& definitionText,
209 const std::string& timelineJson, double cooldownSeconds,
210 const std::string& instancingText,
211 const std::string& groupText,
212 const std::string& triggerTag) {
213 return setTimelineAbility(definitionText, timelineJson, cooldownSeconds, instancingText,
214 groupText, triggerTag, true);
215 }
216
217 Result<Value> setTimelineAbility(const std::string& definitionText,
218 const std::string& timelineJson, double cooldownSeconds,
219 const std::string& instancingText,
220 const std::string& groupText,
221 const std::string& triggerTag, bool replace) {
222 auto definitionId = LogicalId::parse(definitionText);
223 if (!definitionId)
225 "ability definition id is invalid", "definitionId", {},
226 "combat.squirrel"));
227 auto decoded = Value::fromJson(timelineJson);
228 if (!decoded) return Result<Value>::failure(decoded.status());
229 auto timeline = action::ActionTimeline::fromValue(decoded.value());
230 if (!timeline) return Result<Value>::failure(timeline.status());
231 auto cooldown = Duration::fromSeconds(cooldownSeconds);
232 if (!cooldown) return Result<Value>::failure(cooldown.status());
233
234 action::AbilityDefinition definition;
235 definition.id = *definitionId;
236 definition.action.id = timeline.value().actionId;
237 definition.action.timeline = timeline.value();
238 definition.cooldown = cooldown.value();
239 if (instancingText == "non-instanced")
240 definition.instancing = action::AbilityInstancingPolicy::NonInstanced;
241 else if (instancingText == "per-owner")
242 definition.instancing = action::AbilityInstancingPolicy::PerOwner;
243 else if (instancingText == "per-execution")
244 definition.instancing = action::AbilityInstancingPolicy::PerExecution;
245 else
247 "ability instancing policy is invalid", "instancing", {},
248 "combat.squirrel"));
249 if (groupText == "independent")
250 definition.activationGroup = action::AbilityActivationGroup::Independent;
251 else if (groupText == "exclusive-replaceable")
252 definition.activationGroup = action::AbilityActivationGroup::ExclusiveReplaceable;
253 else if (groupText == "exclusive-blocking")
254 definition.activationGroup = action::AbilityActivationGroup::ExclusiveBlocking;
255 else
257 "ability activation group is invalid", "activationGroup",
258 {}, "combat.squirrel"));
259 if (!triggerTag.empty()) {
260 if (!tags::isValidGameplayTagName(triggerTag))
262 "ability trigger tag is invalid", "triggerTag", {},
263 "combat.squirrel"));
264 definition.triggers.push_back({triggerTag, tags::GameplayTagMatch::Exact});
265 }
266
267 const auto& splits = timeline.value().splitTimestamps;
268 if (splits.empty()) {
269 definition.action.timing.active = timeline.value().duration;
270 } else if (splits.size() == 1) {
271 definition.action.timing.windup = splits[0];
272 definition.action.timing.active = Duration::fromNanoseconds(
273 timeline.value().duration.nanoseconds() - splits[0].nanoseconds());
274 } else {
275 definition.action.timing.windup = splits[0];
276 definition.action.timing.active = Duration::fromNanoseconds(
277 splits[1].nanoseconds() - splits[0].nanoseconds());
278 definition.action.timing.recover = Duration::fromNanoseconds(
279 timeline.value().duration.nanoseconds() - splits[1].nanoseconds());
280 }
281 auto changed = replace ? abilities_.replaceDefinition(definition)
282 : abilities_.registerDefinition(definition);
283 if (!changed) return Result<Value>::failure(changed.status());
284 abilityActions_[definitionText] = definition.action.id;
285 Value::Object result;
286 result["definitionId"] = definitionText;
287 result["actionId"] = definition.action.id.format();
288 result["durationSeconds"] = timeline.value().duration.seconds();
289 result["sectionCount"] = static_cast<std::int64_t>(timeline.value().sectionCount());
291 }
292
293 Result<Value> activateAbility(std::int64_t grantValue, std::int64_t tickValue) {
294 auto grant = parseGrant(grantValue);
295 if (!grant) return Result<Value>::failure(grant.status());
296 if (tickValue < 0)
298 DiagnosticCode::InvalidArgument, "ability tick must be non-negative", "tick", {}, "combat.squirrel"));
299 auto grantState = abilities_.findGrant(grant.value());
300 if (!grantState) return Result<Value>::failure(grantState.status());
301 const auto action = abilityActions_.find(grantState.value().definitionId.format());
302 if (action == abilityActions_.end())
304 "ability definition action is unavailable", "definitionId",
305 {}, "combat.squirrel"));
306 action::ActionRequest request;
307 request.actionId = action->second;
308 request.requestedTick = SimulationTick(static_cast<std::uint64_t>(tickValue));
309 const auto tick = request.requestedTick;
310 auto activated = abilities_.activate(grant.value(), std::move(request), tick);
311 if (!activated) return Result<Value>::failure(activated.status());
312 return Result<Value>::success(abilityActivationValue(activated.value()),
314 }
315
316 Result<Value> advanceAbilities(std::int64_t tickValue, double seconds) {
317 if (tickValue < 0)
319 DiagnosticCode::InvalidArgument, "ability tick must be non-negative", "tick", {}, "combat.squirrel"));
320 auto delta = Duration::fromSeconds(seconds);
321 if (!delta) return Result<Value>::failure(delta.status());
322 Value::Array advances;
323 for (const auto& activation : abilities_.activeActivations()) {
324 auto advanced = actions_.advance(activation.executionId,
325 SimulationTick(static_cast<std::uint64_t>(tickValue)), delta.value());
326 if (!advanced) return Result<Value>::failure(advanced.status());
328 value["activationId"] = static_cast<std::int64_t>(activation.id.value());
329 value["executionId"] = static_cast<std::int64_t>(activation.executionId.value());
330 value["phase"] = std::string(action::actionPhaseName(advanced.value().phase));
331 value["elapsedSeconds"] = advanced.value().totalElapsed.seconds();
332 advances.emplace_back(std::move(value));
333 }
334 auto cooled = abilities_.advanceCooldowns(delta.value());
335 if (!cooled) return Result<Value>::failure(cooled.status());
336 auto synchronized = abilities_.synchronize();
337 if (!synchronized) return Result<Value>::failure(synchronized.status());
338 Value::Object result;
339 result["advances"] = std::move(advances);
340 result["completedCount"] = static_cast<std::int64_t>(synchronized.value());
341 result["activeCount"] = static_cast<std::int64_t>(abilities_.activeActivations().size());
343 }
344
345 Result<Value> cancelAbility(std::int64_t activationValue, std::int64_t tickValue) {
346 if (activationValue <= 0 || tickValue < 0)
348 "ability activation or tick is invalid", "activation", {},
349 "combat.squirrel"));
350 const action::AbilityActivationId activationId(static_cast<std::uint64_t>(activationValue));
351 const auto active = abilities_.activeActivations();
352 const auto found = std::find_if(active.begin(), active.end(),
353 [&](const auto& item) { return item.id == activationId; });
354 if (found == active.end())
356 DiagnosticCode::NotFound, "ability activation was not found", "activationId", {}, "combat.squirrel"));
357 auto cancelled = actions_.cancel(found->executionId,
358 SimulationTick(static_cast<std::uint64_t>(tickValue)));
359 if (!cancelled) return Result<Value>::failure(cancelled.status());
360 auto synchronized = abilities_.synchronize();
361 if (!synchronized) return Result<Value>::failure(synchronized.status());
362 Value::Object result;
363 result["activationId"] = activationValue;
364 result["phase"] = std::string("cancelled");
366 }
367
368 Result<Value> abilityGrant(std::int64_t grantValue) const {
369 auto grant = parseGrant(grantValue);
370 if (!grant) return Result<Value>::failure(grant.status());
371 return abilityGrantValue(grant.value());
372 }
373
374 Result<Value> matchingAbilities(const std::string& ownerId, const std::string& eventTag) const {
375 if (!tags::isValidGameplayTagName(eventTag))
377 DiagnosticCode::InvalidArgument, "ability event tag is invalid", "eventTag", {}, "combat.squirrel"));
378 Value::Array result;
379 for (const auto grant : abilities_.matchingGrants(ownerId, eventTag))
380 result.emplace_back(static_cast<std::int64_t>(grant.value()));
381 return Result<Value>::success(Value(std::move(result)));
382 }
383
384 Result<Value> state(const std::string& subjectText) const {
385 auto subject = parseSubject(subjectText, "subject");
386 if (!subject) return Result<Value>::failure(subject.status());
387 const auto found = states_.find(subjectText);
388 if (found == states_.end())
389 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::NotFound, "combat fighter was not found",
390 subjectText, {}, "combat.squirrel"));
391 auto movement = locomotion_.state(subject.value());
392 if (!movement) return Result<Value>::failure(movement.status());
393 Value::Object result;
394 result["subject"] = subjectText;
395 result["ownerId"] = movement.value().ownerId;
396 result["position"] = vectorValue(movement.value().position);
397 result["velocity"] = vectorValue(movement.value().velocity);
398 result["facing"] = vectorValue(movement.value().facing);
399 result["health"] = found->second.health;
400 result["maxHealth"] = found->second.maxHealth;
401 result["poise"] = found->second.poise;
402 result["maxPoise"] = found->second.maxPoise;
403 result["navigating"] = movement.value().navigationGoal.has_value();
404 return Result<Value>::success(Value(std::move(result)));
405 }
406
407private:
408 static Result<action::AbilityGrantId> parseGrant(std::int64_t value) {
409 if (value <= 0)
411 "ability grant id must be positive",
412 "grantId", {}, "combat.squirrel"));
414 action::AbilityGrantId(static_cast<std::uint64_t>(value)));
415 }
416
417 Result<Value> abilityGrantValue(action::AbilityGrantId grantId) const {
418 auto state = abilities_.findGrant(grantId);
419 if (!state) return Result<Value>::failure(state.status());
420 Value::Object result;
421 result["grantId"] = static_cast<std::int64_t>(state.value().grantId.value());
422 result["ownerId"] = state.value().ownerId;
423 result["definitionId"] = state.value().definitionId.format();
424 result["cooldownSeconds"] = state.value().cooldownRemaining.seconds();
425 return Result<Value>::success(Value(std::move(result)));
426 }
427
428 static Value abilityActivationValue(const action::AbilityActivation& activation) {
429 Value::Object result;
430 result["activationId"] = static_cast<std::int64_t>(activation.id.value());
431 result["grantId"] = static_cast<std::int64_t>(activation.grantId.value());
432 result["instanceId"] = static_cast<std::int64_t>(activation.instanceId.value());
433 result["executionId"] = static_cast<std::int64_t>(activation.executionId.value());
434 result["ownerId"] = activation.ownerId;
435 return Value(std::move(result));
436 }
437
438 DirectCombatNavigationProvider navigation_;
439 CombatLocomotionRuntime locomotion_;
440 DamageRuntime damage_;
441 std::map<std::string, CombatState, std::less<>> states_;
442 action::ActionRuntime actions_;
443 action::AbilityRuntime abilities_{actions_};
444 std::map<std::string, LogicalId, std::less<>> abilityActions_;
445};
446
447ssq::Table project(HSQUIRRELVM vm, Result<Value>&& result) {
448 return script::projectResult(vm, std::move(result), [](Value value) { return value; });
449}
450
451ssq::Table newRuntime(HSQUIRRELVM vm) {
452 auto runtime = std::make_unique<ScriptCombatRuntime>();
453 auto initialized = runtime->initializeAbilities();
454 if (!initialized) return script::projectStatusResult(vm, initialized.status());
455 auto object = script::makeOwnedSquirrelInstance<ScriptCombatRuntime>(vm, std::move(runtime));
456 if (!object) return script::projectStatusResult(vm, object.status());
458 result.set("value", std::move(object).takeValue());
459 result.set("ownership", std::string("owned"));
460 return result;
461}
462
463} // namespace
464
466
467void Combat::expose(ssq::Table& table) {
468 const HSQUIRRELVM vm = table.getHandle();
469 auto runtime = table.addClass<ScriptCombatRuntime>(
470 "CombatRuntime", std::function<ScriptCombatRuntime*()>([] { return nullptr; }), true);
471 runtime.addFunc("ownership", [](ScriptCombatRuntime*) { return std::string("owned"); });
472 runtime.addFunc(
473 "registerFighter", [vm](ScriptCombatRuntime* self, const std::string& subject, const std::string& owner,
474 float x, float z, float health, float poise, float speed, float acceleration) {
475 return project(vm, self ? self->registerFighter(subject, owner, x, z, health, poise, speed, acceleration)
476 : Result<Value>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
477 "combat runtime must not be null",
478 "runtime", {}, "combat.squirrel")));
479 });
480 runtime.addFunc(
481 "setMoveIntent", [vm](ScriptCombatRuntime* self, const std::string& subject, float x, float z, float speed) {
482 return project(vm, self ? self->setMoveIntent(subject, x, z, speed)
484 "combat runtime must not be null",
485 "runtime", {}, "combat.squirrel")));
486 });
487 runtime.addFunc(
488 "navigateTo", [vm](ScriptCombatRuntime* self, const std::string& subject, float x, float z, float radius) {
489 return project(vm, self ? self->navigateTo(subject, x, z, radius)
491 "combat runtime must not be null",
492 "runtime", {}, "combat.squirrel")));
493 });
494 runtime.addFunc("stop", [vm](ScriptCombatRuntime* self, const std::string& subject) {
495 return project(vm, self ? self->stop(subject)
497 "combat runtime must not be null", "runtime",
498 {}, "combat.squirrel")));
499 });
500 runtime.addFunc("advance", [vm](ScriptCombatRuntime* self, std::int64_t tick, float seconds) {
501 return project(vm, self ? self->advance(tick, seconds)
503 "combat runtime must not be null", "runtime",
504 {}, "combat.squirrel")));
505 });
506 runtime.addFunc("applyDamage", [vm](ScriptCombatRuntime* self, const std::string& source, const std::string& target,
507 const std::string& type, float health, float poise, float x, float y, float z) {
508 return project(vm, self ? self->applyDamage(source, target, type, health, poise, x, y, z)
510 "combat runtime must not be null", "runtime",
511 {}, "combat.squirrel")));
512 });
513 runtime.addFunc("applyTimelineDamage", [vm](ScriptCombatRuntime* self, const std::string& source,
514 const std::string& target, const std::string& payloadJson) {
515 return project(vm, self ? self->applyTimelineDamage(source, target, payloadJson)
517 "combat runtime must not be null", "runtime",
518 {}, "combat.squirrel")));
519 });
520 runtime.addFunc(
521 "grantAbility", [vm](ScriptCombatRuntime* self, const std::string& owner, const std::string& definitionId) {
522 return project(vm, self ? self->grantAbility(owner, definitionId)
524 "combat runtime must not be null",
525 "runtime", {}, "combat.squirrel")));
526 });
527 runtime.addFunc("registerTimelineAbility", [vm](ScriptCombatRuntime* self, const std::string& definitionId,
528 const std::string& timelineJson, float cooldownSeconds,
529 const std::string& instancing, const std::string& activationGroup,
530 const std::string& triggerTag) {
531 return project(vm, self ? self->registerTimelineAbility(definitionId, timelineJson, cooldownSeconds, instancing,
532 activationGroup, triggerTag)
534 "combat runtime must not be null", "runtime",
535 {}, "combat.squirrel")));
536 });
537 runtime.addFunc("replaceTimelineAbility", [vm](ScriptCombatRuntime* self, const std::string& definitionId,
538 const std::string& timelineJson, float cooldownSeconds,
539 const std::string& instancing, const std::string& activationGroup,
540 const std::string& triggerTag) {
541 return project(vm, self ? self->replaceTimelineAbility(definitionId, timelineJson, cooldownSeconds, instancing,
542 activationGroup, triggerTag)
544 "combat runtime must not be null", "runtime",
545 {}, "combat.squirrel")));
546 });
547 runtime.addFunc("activateAbility", [vm](ScriptCombatRuntime* self, std::int64_t grantId, std::int64_t tick) {
548 return project(vm, self ? self->activateAbility(grantId, tick)
550 "combat runtime must not be null", "runtime",
551 {}, "combat.squirrel")));
552 });
553 runtime.addFunc("advanceAbilities", [vm](ScriptCombatRuntime* self, std::int64_t tick, float seconds) {
554 return project(vm, self ? self->advanceAbilities(tick, seconds)
556 "combat runtime must not be null", "runtime",
557 {}, "combat.squirrel")));
558 });
559 runtime.addFunc("cancelAbility", [vm](ScriptCombatRuntime* self, std::int64_t activationId, std::int64_t tick) {
560 return project(vm, self ? self->cancelAbility(activationId, tick)
562 "combat runtime must not be null", "runtime",
563 {}, "combat.squirrel")));
564 });
565 runtime.addFunc("abilityGrant", [vm](ScriptCombatRuntime* self, std::int64_t grantId) {
566 return project(vm, self ? self->abilityGrant(grantId)
568 "combat runtime must not be null", "runtime",
569 {}, "combat.squirrel")));
570 });
571 runtime.addFunc(
572 "matchingAbilities", [vm](ScriptCombatRuntime* self, const std::string& owner, const std::string& eventTag) {
573 return project(vm, self ? self->matchingAbilities(owner, eventTag)
575 "combat runtime must not be null",
576 "runtime", {}, "combat.squirrel")));
577 });
578 runtime.addFunc("state", [vm](ScriptCombatRuntime* self, const std::string& subject) {
579 return project(vm, self ? self->state(subject)
581 "combat runtime must not be null", "runtime",
582 {}, "combat.squirrel")));
583 });
584
585 auto module = table.addClass(name, Combat::create, false);
586 module.addFunc("getName", &Combat::getName);
587 module.addFunc("newRuntime", [vm](Combat*) { return newRuntime(vm); });
588}
589
590void Combat::expose(ssq::Class& cls) {
591 cls.addFunc("getName", &Combat::getName);
592 cls.addFunc("newRuntime", [vm = cls.getHandle()](Combat*) { return newRuntime(vm); });
593}
594
595} // namespace eve::combat
Grants, cooldowns, instancing, activation groups and gameplay-event triggers.
Typed instantaneous combat-damage action contract.
LogicalId target
double value
Value::Object payload
bool & active
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int subject
Definition AnimSmr.cpp:163
Deterministic player/navigation locomotion adapter for combat subjects.
Script-facing combat composition module.
struct SQVM * HSQUIRRELVM
Deterministic damage, poise, hit-reaction and knockback resolution.
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
std::string text
bool valid
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
std::string error
Definition Package.cpp:60
eve::action::ActionVfxBinding binding
float radius
std::string action
Definition PlayHost.cpp:117
std::string path
Definition PlayHost.cpp:110
bool found
std::string damageType
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
AbilityInstancingPolicy instancing
Registerable standard action-combat ability archetypes.
Battle::Events events
SimulationTick tick
const UnitySourceAsset & source
const std::map< std::string, Value > & definitions
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
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
static constexpr Duration fromNanoseconds(std::int64_t nanoseconds) noexcept
Construct an exact duration from nanoseconds.
Definition Time.h:55
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
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
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
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
Result< CombatLocomotionAdvance > advance(const SimulationStep &step)
Atomically advance every subject using only the supplied deterministic step.
Result< CombatLocomotionState > state(SubjectRef subject) const
Return an owning state snapshot, or NotFound for a stale subject.
Result< void > stop(SubjectRef subject)
Clear a navigation goal and desired movement for one subject.
Factory module for script-owned deterministic combat runtimes.
Definition Combat.h:18
Result< DamageOutcome > apply(CombatState &target, const DamageRequest &request) const
Resolve and atomically commit one damage request.
Definition Damage.cpp:200
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
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.
Definition Action.cpp:42
eve::detail::StrongUint64< AbilityActivationIdTag > AbilityActivationId
Opaque identity of one activation attempt accepted by ActionRuntime.
eve::detail::StrongUint64< AbilityGrantIdTag > AbilityGrantId
Opaque identity of one owner-to-definition grant.
Result< std::vector< action::AbilityDefinition > > standardCombatAbilities()
Build twenty validated, registerable standard combat ability definitions.
HitReaction
Typed reaction selected after health and poise are resolved.
Definition Damage.h:26
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
eve::Diagnostic Diagnostic
eve::Result< T > Result
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.
bool isValidGameplayTagName(std::string_view name) noexcept
Returns whether a name is a canonical dot-separated gameplay tag.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
static Result< ActionDamageBinding > fromPayload(const Value::Object &payload)
Decode and validate a damage payload without mutating combat state.
static Result< ActionTimeline > fromValue(const Value &value)
Decode and validate one canonical schema value transactionally.