载入中...
搜索中...
未找到
CombatCarrierScript.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
6#include "common/Time.h"
7#include "common/Value.h"
11#include "weapon/Weapon.h"
12
13#include <cmath>
14#include <cstdint>
15#include <limits>
16#include <map>
17#include <memory>
18#include <string>
19#include <utility>
20#include <vector>
21
22namespace eve::weapon {
23namespace {
24
25Result<Value> nullRuntime() {
27 "combat carrier runtime must not be null", "runtime", {},
28 "weapon.carrier.squirrel"));
29}
30
31class ScriptTargetProvider final : public IProjectileTargetProvider {
32public:
33 [[nodiscard]] Result<ProjectilePoint> position(ecs::EntityHandle target) const override {
34 const auto key = keyOf(target);
35 auto it = positions_.find(key);
36 if (it == positions_.end())
38 Diagnostic::error(DiagnosticCode::NotFound, "homing target position is not registered", "target", {},
39 "weapon.carrier.squirrel"));
40 return Result<ProjectilePoint>::success(it->second);
41 }
42
43 void set(ecs::EntityHandle target, ProjectilePoint point) { positions_[keyOf(target)] = point; }
44 void clear() { positions_.clear(); }
45 [[nodiscard]] bool empty() const noexcept { return positions_.empty(); }
46
47private:
48 [[nodiscard]] static std::uint64_t keyOf(ecs::EntityHandle target) {
49 return (static_cast<std::uint64_t>(target.generation) << 32) | target.id;
50 }
51
52 std::map<std::uint64_t, ProjectilePoint> positions_;
53};
54
55class ScriptCircleHitProbe final : public ICarrierHitProbe {
56public:
57 struct Target {
58 ecs::EntityHandle id{};
59 ProjectilePoint center{};
60 double radius = 0.5;
61 };
62
63 [[nodiscard]] Result<std::vector<Contact>> query(CarrierHandle, const CarrierMotion& previous,
64 const CarrierMotion& current) const override {
65 std::vector<Contact> contacts;
66 for (const auto& target : targets_) {
67 const double ax = previous.position.x - target.center.x;
68 const double ay = previous.position.y - target.center.y;
69 const double az = previous.position.z - target.center.z;
70 const double bx = current.position.x - target.center.x;
71 const double by = current.position.y - target.center.y;
72 const double bz = current.position.z - target.center.z;
73 const double abx = bx - ax;
74 const double aby = by - ay;
75 const double abz = bz - az;
76 const double ab2 = abx * abx + aby * aby + abz * abz;
77 double t = 0.0;
78 if (ab2 > 1e-12) {
79 t = -(ax * abx + ay * aby + az * abz) / ab2;
80 if (t < 0.0) t = 0.0;
81 if (t > 1.0) t = 1.0;
82 }
83 const double cx = ax + abx * t;
84 const double cy = ay + aby * t;
85 const double cz = az + abz * t;
86 const double dist2 = cx * cx + cy * cy + cz * cz;
87 if (dist2 <= target.radius * target.radius) {
88 Contact contact;
89 contact.target = target.id;
90 contact.point = {target.center.x + cx, target.center.y + cy, target.center.z + cz};
91 const double len = std::sqrt(dist2);
92 if (len > 1e-9) {
93 contact.normal = {cx / len, cy / len, cz / len};
94 } else {
95 contact.normal = {-1.0, 0.0, 0.0};
96 }
97 contacts.push_back(contact);
98 }
99 }
100 return Result<std::vector<Contact>>::success(std::move(contacts));
101 }
102
103 void add(Target target) { targets_.push_back(target); }
104 void clear() { targets_.clear(); }
105 [[nodiscard]] bool empty() const noexcept { return targets_.empty(); }
106
107private:
108 std::vector<Target> targets_;
109};
110
111Value encodeHandle(CarrierHandle handle) {
113 object.emplace("slot", Value(static_cast<std::int64_t>(handle.slot)));
114 object.emplace("generation", Value(static_cast<std::int64_t>(handle.generation)));
115 return Value(std::move(object));
116}
117
118const char* triggerKindName(CarrierTriggerKind kind) {
119 switch (kind) {
120 case CarrierTriggerKind::OnExpire: return "onExpire";
121 case CarrierTriggerKind::OnFuse: return "onFuse";
122 case CarrierTriggerKind::OnInterval: return "onInterval";
123 case CarrierTriggerKind::OnHit: return "onHit";
124 case CarrierTriggerKind::OnProximity: return "onProximity";
125 }
126 return "onExpire";
127}
128
129const char* impactKindName(CarrierImpactKind kind) {
130 switch (kind) {
131 case CarrierImpactKind::EmitHit: return "emitHit";
132 case CarrierImpactKind::Splash: return "splash";
133 case CarrierImpactKind::Pierce: return "pierce";
134 case CarrierImpactKind::Bounce: return "bounce";
135 case CarrierImpactKind::Release: return "release";
136 case CarrierImpactKind::SpawnChild: return "spawnChild";
137 }
138 return "release";
139}
140
141Value encodeEvent(const CarrierEvent& event) {
143 object.emplace("slot", Value(static_cast<std::int64_t>(event.carrier.slot)));
144 object.emplace("generation", Value(static_cast<std::int64_t>(event.carrier.generation)));
145 object.emplace("recipeId", Value(event.recipeId.format()));
146 object.emplace("trigger", Value(std::string(triggerKindName(event.trigger))));
147 object.emplace("impact", Value(std::string(impactKindName(event.impact))));
148 object.emplace("damage", Value(event.damage));
149 object.emplace("splashRadius", Value(event.splashRadius));
150 object.emplace("damageType", Value(event.damageType));
151 object.emplace("element", Value(event.element));
152 object.emplace("x", Value(event.position.x));
153 object.emplace("y", Value(event.position.y));
154 object.emplace("z", Value(event.position.z));
155 object.emplace("nx", Value(event.normal.x));
156 object.emplace("ny", Value(event.normal.y));
157 object.emplace("nz", Value(event.normal.z));
158 object.emplace("sourceId", Value(static_cast<std::int64_t>(event.source.id)));
159 object.emplace("sourceGeneration", Value(static_cast<std::int64_t>(event.source.generation)));
160 object.emplace("targetId", Value(static_cast<std::int64_t>(event.target.id)));
161 object.emplace("targetGeneration", Value(static_cast<std::int64_t>(event.target.generation)));
162 return Value(std::move(object));
163}
164
165Value encodeFrame(const CarrierFrame& frame) {
167 Value::Array advanced;
168 for (const auto& handle : frame.advanced) advanced.push_back(encodeHandle(handle));
169 Value::Array released;
170 for (const auto& handle : frame.released) released.push_back(encodeHandle(handle));
172 for (const auto& event : frame.events) events.push_back(encodeEvent(event));
173 object.emplace("advanced", Value(std::move(advanced)));
174 object.emplace("released", Value(std::move(released)));
175 object.emplace("events", Value(std::move(events)));
176 object.emplace("eventCount", Value(static_cast<std::int64_t>(frame.events.size())));
177 object.emplace("activeAdvanced", Value(static_cast<std::int64_t>(frame.advanced.size())));
178 return Value(std::move(object));
179}
180
181Value encodePlan(const SpellCastPlan& plan) {
183 object.emplace("recipeId", Value(plan.recipe.id.format()));
184 object.emplace("hasVolley", Value(plan.volley.has_value()));
185 if (plan.volley.has_value()) {
186 object.emplace("volleyCount", Value(static_cast<std::int64_t>(plan.volley->count)));
187 object.emplace("volleyPattern",
188 Value(std::string(plan.volley->pattern == CarrierVolleyPattern::Ring ? "ring" : "fan")));
189 object.emplace("volleySpread", Value(plan.volley->spreadDegrees));
190 }
191 return Value(std::move(object));
192}
193
194class ScriptCombatCarrierRuntime {
195public:
196 ScriptCombatCarrierRuntime() = default;
197
198 [[nodiscard]] Result<Value> configurePool(std::int64_t capacity) {
199 if (capacity < 1 || capacity > 1048576)
201 "pool capacity must be in 1..1048576", "capacity", {},
202 "weapon.carrier.squirrel"));
203 auto configured = runtime_.configurePool(static_cast<std::uint32_t>(capacity));
204 if (!configured) return Result<Value>::failure(configured.status());
206 object.emplace("capacity", Value(static_cast<std::int64_t>(runtime_.capacity())));
207 return Result<Value>::success(Value(std::move(object)));
208 }
209
210 [[nodiscard]] Result<Value> registerRecipeValue(const Value& value) {
211 auto recipe = decodeCarrierRecipe(value);
212 if (!recipe.ok()) return Result<Value>::failure(recipe.status());
213 CarrierRecipe owned = std::move(recipe).takeValue();
214 for (const auto& op : owned.motionOps) {
218 "SteerAvoidBody recipes are not supported by the script carrier runtime; omit avoidBody or use C++",
219 "motion", {}, "weapon.carrier.squirrel"));
220 }
221 }
222 const std::string id = owned.id.format();
223 auto registered = runtime_.registerRecipe(owned);
224 if (!registered) return Result<Value>::failure(registered.status());
226 object.emplace("recipeId", Value(id));
227 return Result<Value>::success(Value(std::move(object)));
228 }
229
230 [[nodiscard]] Result<Value> registerRecipeJson(const std::string& json) {
231 auto parsed = Value::fromJson(json);
232 if (!parsed.ok()) return Result<Value>::failure(parsed.status());
233 return registerRecipeValue(parsed.value());
234 }
235
236 [[nodiscard]] Result<Value> compileFragmentsValue(const Value& fragments) {
237 auto plan = compileSpellFragments(fragments);
238 if (!plan.ok()) return Result<Value>::failure(plan.status());
239 return registerPlan(std::move(plan).takeValue());
240 }
241
242 [[nodiscard]] Result<Value> compileFragmentsJson(const std::string& json) {
243 auto plan = compileSpellFragmentsJson(json);
244 if (!plan.ok()) return Result<Value>::failure(plan.status());
245 return registerPlan(std::move(plan).takeValue());
246 }
247
248 [[nodiscard]] Result<Value> castValue(const Value& fragments, double x, double y, double z, double dx, double dy,
249 double dz, std::int64_t targetId, std::int64_t targetGeneration) {
250 auto plan = compileSpellFragments(fragments);
251 if (!plan.ok()) return Result<Value>::failure(plan.status());
252 SpellCastPlan owned = std::move(plan).takeValue();
253 auto uniquified = uniquifyRecipeId(owned.recipe.id);
254 if (!uniquified.ok()) return Result<Value>::failure(uniquified.status());
255 owned.recipe.id = uniquified.value();
256
257 bool needsTarget = false;
258 for (const auto& op : owned.recipe.motionOps) {
259 if (op.kind == CarrierMotionOpKind::SteerHoming) needsTarget = true;
260 }
261 for (const auto& trigger : owned.recipe.triggers) {
262 if (trigger.kind == CarrierTriggerKind::OnProximity) needsTarget = true;
263 }
264 if (needsTarget && targetId <= 0) {
267 "Homing/proximity casts require castAtTarget with a positive target id", "target", {},
268 "weapon.carrier.squirrel"));
269 }
270
271 auto registered = registerPlan(owned);
272 if (!registered.ok()) return registered;
273 return spawnPlan(owned, x, y, z, dx, dy, dz, targetId, targetGeneration);
274 }
275
276 [[nodiscard]] Result<Value> castJson(const std::string& json, double x, double y, double z, double dx, double dy,
277 double dz, std::int64_t targetId, std::int64_t targetGeneration) {
278 auto parsed = Value::fromJson(json);
279 if (!parsed.ok()) return Result<Value>::failure(parsed.status());
280 return castValue(parsed.value(), x, y, z, dx, dy, dz, targetId, targetGeneration);
281 }
282
283 [[nodiscard]] static Result<void> validateHandleParts(std::int64_t id, std::int64_t generation,
284 std::string_view path) {
285 if (id < 0 || generation < 0 || static_cast<std::uint64_t>(id) > std::numeric_limits<std::uint32_t>::max() ||
286 static_cast<std::uint64_t>(generation) > std::numeric_limits<std::uint32_t>::max()) {
288 "handle id/generation must fit in uint32", std::string(path),
289 {}, "weapon.carrier.squirrel"));
290 }
291 return Result<void>::success();
292 }
293
294 [[nodiscard]] static Result<void> validatePoint(double x, double y, double z, std::string_view path) {
295 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z)) {
297 "coordinates must be finite", std::string(path), {},
298 "weapon.carrier.squirrel"));
299 }
300 return Result<void>::success();
301 }
302
303 [[nodiscard]] Result<Value> spawn(const std::string& recipeId, double x, double y, double z, double dx, double dy,
304 double dz, std::int64_t targetId, std::int64_t targetGeneration) {
305 auto id = LogicalId::parse(recipeId);
306 if (!id.has_value())
308 "recipe id must be namespace:name", "recipeId", {},
309 "weapon.carrier.squirrel"));
310 if (auto point = validatePoint(x, y, z, "position"); !point) return Result<Value>::failure(point.status());
311 if (auto point = validatePoint(dx, dy, dz, "direction"); !point) return Result<Value>::failure(point.status());
312 CarrierSpawnRequest request;
313 request.position = {x, y, z};
314 request.direction = {dx, dy, dz};
315 if (targetId > 0) {
316 auto handle = validateHandleParts(targetId, targetGeneration, "target");
317 if (!handle) return Result<Value>::failure(handle.status());
318 ecs::EntityHandle target;
319 target.id = static_cast<std::uint32_t>(targetId);
320 target.generation = static_cast<std::uint32_t>(targetGeneration);
321 request.target = target;
322 }
323 auto spawned = runtime_.spawn(*id, request);
324 if (!spawned.ok()) return Result<Value>::failure(spawned.status());
326 object.emplace("handle", encodeHandle(spawned.value()));
327 object.emplace("activeCount", Value(static_cast<std::int64_t>(runtime_.activeCount())));
328 return Result<Value>::success(Value(std::move(object)));
329 }
330
331 [[nodiscard]] Result<Value> spawnVolley(const std::string& recipeId, double x, double y, double z, double dx,
332 double dy, double dz, std::int64_t count, const std::string& pattern,
333 double spread, std::int64_t targetId, std::int64_t targetGeneration) {
334 auto id = LogicalId::parse(recipeId);
335 if (!id.has_value())
337 "recipe id must be namespace:name", "recipeId", {},
338 "weapon.carrier.squirrel"));
339 if (auto point = validatePoint(x, y, z, "position"); !point) return Result<Value>::failure(point.status());
340 if (auto point = validatePoint(dx, dy, dz, "direction"); !point) return Result<Value>::failure(point.status());
341 CarrierVolleySpec volley;
342 volley.count = static_cast<int>(count);
343 volley.spreadDegrees = spread;
344 volley.pattern = (pattern == "ring" || pattern == "Ring") ? CarrierVolleyPattern::Ring
346 CarrierSpawnRequest request;
347 request.position = {x, y, z};
348 request.direction = {dx, dy, dz};
349 if (targetId > 0) {
350 auto handle = validateHandleParts(targetId, targetGeneration, "target");
351 if (!handle) return Result<Value>::failure(handle.status());
352 ecs::EntityHandle target;
353 target.id = static_cast<std::uint32_t>(targetId);
354 target.generation = static_cast<std::uint32_t>(targetGeneration);
355 request.target = target;
356 }
357 auto spawned = runtime_.spawnVolley(*id, request, volley);
358 if (!spawned.ok()) return Result<Value>::failure(spawned.status());
359 Value::Array handles;
360 for (const auto& handle : spawned.value()) handles.push_back(encodeHandle(handle));
362 object.emplace("handles", Value(std::move(handles)));
363 object.emplace("count", Value(static_cast<std::int64_t>(spawned.value().size())));
364 object.emplace("activeCount", Value(static_cast<std::int64_t>(runtime_.activeCount())));
365 return Result<Value>::success(Value(std::move(object)));
366 }
367
368 [[nodiscard]] Result<Value> update(double seconds) {
369 auto delta = Duration::fromSeconds(seconds);
370 if (!delta.ok()) return Result<Value>::failure(delta.status());
371 // Always supply script probes: empty lists are valid "no contacts / no targets"
372 // answers. Passing nullptr rejects any live OnHit/Homing recipe.
373 auto frame = runtime_.update(delta.value(), &targets_, &hits_, nullptr);
374 if (!frame.ok()) return Result<Value>::failure(frame.status());
375 lastFrame_ = frame.value();
376 Value encoded = encodeFrame(lastFrame_);
377 if (auto* object = encoded.getIf<Value::Object>()) {
378 object->emplace("activeCount", Value(static_cast<std::int64_t>(runtime_.activeCount())));
379 }
380 return Result<Value>::success(std::move(encoded));
381 }
382
383 [[nodiscard]] Result<Value> addHitTarget(std::int64_t id, std::int64_t generation, double x, double y, double z,
384 double radius) {
385 auto handle = validateHandleParts(id, generation, "target");
386 if (!handle) return Result<Value>::failure(handle.status());
387 auto point = validatePoint(x, y, z, "target");
388 if (!point) return Result<Value>::failure(point.status());
389 if (!(radius > 0.0) || !std::isfinite(radius))
391 "hit target requires positive finite radius", "radius", {},
392 "weapon.carrier.squirrel"));
393 ScriptCircleHitProbe::Target target;
394 target.id.id = static_cast<std::uint32_t>(id);
395 target.id.generation = static_cast<std::uint32_t>(generation);
396 target.center = {x, y, z};
397 target.radius = radius;
398 hits_.add(target);
400 object.emplace("count", Value(static_cast<std::int64_t>(1)));
401 return Result<Value>::success(Value(std::move(object)));
402 }
403
404 [[nodiscard]] Result<Value> clearHitTargets() {
405 hits_.clear();
407 }
408
409 [[nodiscard]] Result<Value> setTargetPosition(std::int64_t id, std::int64_t generation, double x, double y,
410 double z) {
411 auto handle = validateHandleParts(id, generation, "target");
412 if (!handle) return Result<Value>::failure(handle.status());
413 auto point = validatePoint(x, y, z, "target");
414 if (!point) return Result<Value>::failure(point.status());
415 ecs::EntityHandle target;
416 target.id = static_cast<std::uint32_t>(id);
417 target.generation = static_cast<std::uint32_t>(generation);
418 targets_.set(target, {x, y, z});
420 }
421
422 [[nodiscard]] Result<Value> clearTargets() {
423 targets_.clear();
425 }
426
427 [[nodiscard]] std::int64_t activeCount() const noexcept {
428 return static_cast<std::int64_t>(runtime_.activeCount());
429 }
430
431 [[nodiscard]] std::int64_t capacity() const noexcept { return static_cast<std::int64_t>(runtime_.capacity()); }
432
433private:
434 [[nodiscard]] Result<LogicalId> uniquifyRecipeId(const LogicalId& base) {
435 ++castSeq_;
436 const std::string uniqueName = std::string(base.name()) + "-c" + std::to_string(castSeq_);
437 auto parsed = LogicalId::fromParts(base.namespaceName(), uniqueName);
438 if (!parsed.has_value())
440 "unable to allocate unique cast recipe id", "recipeId",
441 {}, "weapon.carrier.squirrel"));
442 return Result<LogicalId>::success(std::move(*parsed));
443 }
444
445 [[nodiscard]] Result<Value> registerPlan(SpellCastPlan plan) {
446 for (const auto& op : plan.recipe.motionOps) {
450 "SteerAvoidBody recipes are not supported by the script carrier runtime",
451 "motion", {}, "weapon.carrier.squirrel"));
452 }
453 }
454 for (const auto& dependent : plan.dependentRecipes) {
455 auto registered = runtime_.registerRecipe(dependent);
456 if (!registered) return Result<Value>::failure(registered.status());
457 }
458 auto registered = runtime_.registerRecipe(plan.recipe);
459 if (!registered) return Result<Value>::failure(registered.status());
460 return Result<Value>::success(encodePlan(plan));
461 }
462
463 [[nodiscard]] Result<Value> spawnPlan(const SpellCastPlan& plan, double x, double y, double z, double dx, double dy,
464 double dz, std::int64_t targetId, std::int64_t targetGeneration) {
465 auto spawned = plan.volley.has_value()
466 ? spawnVolley(plan.recipe.id.format(), x, y, z, dx, dy, dz, plan.volley->count,
467 plan.volley->pattern == CarrierVolleyPattern::Ring ? "ring" : "fan",
468 plan.volley->spreadDegrees, targetId, targetGeneration)
469 : spawn(plan.recipe.id.format(), x, y, z, dx, dy, dz, targetId, targetGeneration);
470 if (!spawned.ok()) return spawned;
471 Value owned = std::move(spawned).takeValue();
472 if (auto* object = owned.getIf<Value::Object>()) {
473 object->emplace("recipeId", Value(plan.recipe.id.format()));
474 }
475 return Result<Value>::success(std::move(owned));
476 }
477
478 CombatCarrierRuntime runtime_;
479 ScriptTargetProvider targets_;
480 ScriptCircleHitProbe hits_;
481 CarrierFrame lastFrame_{};
482 std::uint64_t castSeq_ = 0;
483};
484
485ssq::Table project(HSQUIRRELVM vm, Result<Value>&& result) {
486 return script::projectResult(vm, std::move(result), [](Value value) { return value; });
487}
488
489ssq::Table newCarrierRuntime(HSQUIRRELVM vm, std::int64_t capacity) {
490 if (capacity < 1) capacity = 256;
491 auto runtime = std::make_unique<ScriptCombatCarrierRuntime>();
492 auto configured = runtime->configurePool(capacity);
493 if (!configured.ok()) return script::projectStatusResult(vm, configured.status());
494 auto object = script::makeOwnedSquirrelInstance<ScriptCombatCarrierRuntime>(vm, std::move(runtime));
495 if (!object) return script::projectStatusResult(vm, object.status());
497 result.set("value", std::move(object).takeValue());
498 result.set("ownership", std::string("owned"));
499 return result;
500}
501
502Result<Value> valueFromObject(const ssq::Object& object) {
503 return script::valueFromSquirrel(object);
504}
505
506} // namespace
507
508void exposeCombatCarrierBindings(ssq::Table& table) {
509 const HSQUIRRELVM vm = table.getHandle();
510
511 auto runtime = table.addClass<ScriptCombatCarrierRuntime>(
512 "CombatCarrierRuntime", std::function<ScriptCombatCarrierRuntime*()>([] { return nullptr; }), true);
513 runtime.addFunc("ownership", [](ScriptCombatCarrierRuntime*) { return std::string("owned"); });
514 runtime.addFunc("activeCount", [](ScriptCombatCarrierRuntime* self) {
515 return self ? self->activeCount() : static_cast<std::int64_t>(0);
516 });
517 runtime.addFunc("capacity", [](ScriptCombatCarrierRuntime* self) {
518 return self ? self->capacity() : static_cast<std::int64_t>(0);
519 });
520 runtime.addFunc("configurePool", [vm](ScriptCombatCarrierRuntime* self, std::int64_t capacity) {
521 return project(vm, self ? self->configurePool(capacity) : nullRuntime());
522 });
523 runtime.addFunc("registerRecipe", [vm](ScriptCombatCarrierRuntime* self, ssq::Object recipe) {
524 if (!self) return project(vm, nullRuntime());
525 auto value = valueFromObject(recipe);
526 if (!value.ok()) return project(vm, Result<Value>::failure(value.status()));
527 return project(vm, self->registerRecipeValue(value.value()));
528 });
529 runtime.addFunc("registerRecipeJson", [vm](ScriptCombatCarrierRuntime* self, const std::string& json) {
530 return project(vm, self ? self->registerRecipeJson(json) : nullRuntime());
531 });
532 runtime.addFunc("compileFragments", [vm](ScriptCombatCarrierRuntime* self, ssq::Object fragments) {
533 if (!self) return project(vm, nullRuntime());
534 auto value = valueFromObject(fragments);
535 if (!value.ok()) return project(vm, Result<Value>::failure(value.status()));
536 return project(vm, self->compileFragmentsValue(value.value()));
537 });
538 runtime.addFunc("compileFragmentsJson", [vm](ScriptCombatCarrierRuntime* self, const std::string& json) {
539 return project(vm, self ? self->compileFragmentsJson(json) : nullRuntime());
540 });
541 runtime.addFunc("cast", [vm](ScriptCombatCarrierRuntime* self, ssq::Object fragments, float x, float y, float z,
542 float dx, float dy, float dz) {
543 if (!self) return project(vm, nullRuntime());
544 auto value = valueFromObject(fragments);
545 if (!value.ok()) return project(vm, Result<Value>::failure(value.status()));
546 return project(vm, self->castValue(value.value(), x, y, z, dx, dy, dz, 0, 0));
547 });
548 runtime.addFunc("castAtTarget", [vm](ScriptCombatCarrierRuntime* self, ssq::Object fragments, float x, float y,
549 float z, float dx, float dy, float dz, std::int64_t targetId,
550 std::int64_t targetGeneration) {
551 if (!self) return project(vm, nullRuntime());
552 auto value = valueFromObject(fragments);
553 if (!value.ok()) return project(vm, Result<Value>::failure(value.status()));
554 return project(vm, self->castValue(value.value(), x, y, z, dx, dy, dz, targetId, targetGeneration));
555 });
556 runtime.addFunc("castJson", [vm](ScriptCombatCarrierRuntime* self, const std::string& json, float x, float y,
557 float z, float dx, float dy, float dz) {
558 return project(vm, self ? self->castJson(json, x, y, z, dx, dy, dz, 0, 0) : nullRuntime());
559 });
560 runtime.addFunc("spawn", [vm](ScriptCombatCarrierRuntime* self, const std::string& recipeId, float x, float y,
561 float z, float dx, float dy, float dz) {
562 return project(vm, self ? self->spawn(recipeId, x, y, z, dx, dy, dz, 0, 0) : nullRuntime());
563 });
564 runtime.addFunc("spawnAtTarget", [vm](ScriptCombatCarrierRuntime* self, const std::string& recipeId, float x,
565 float y, float z, float dx, float dy, float dz, std::int64_t targetId,
566 std::int64_t targetGeneration) {
567 return project(vm, self ? self->spawn(recipeId, x, y, z, dx, dy, dz, targetId, targetGeneration)
568 : nullRuntime());
569 });
570 runtime.addFunc("spawnVolley", [vm](ScriptCombatCarrierRuntime* self, const std::string& recipeId, float x, float y,
571 float z, float dx, float dy, float dz, std::int64_t count,
572 const std::string& pattern, float spread) {
573 return project(vm, self ? self->spawnVolley(recipeId, x, y, z, dx, dy, dz, count, pattern, spread, 0, 0)
574 : nullRuntime());
575 });
576 runtime.addFunc("update", [vm](ScriptCombatCarrierRuntime* self, float seconds) {
577 return project(vm, self ? self->update(seconds) : nullRuntime());
578 });
579 runtime.addFunc("addHitTarget", [vm](ScriptCombatCarrierRuntime* self, std::int64_t id, std::int64_t generation,
580 float x, float y, float z, float radius) {
581 return project(vm, self ? self->addHitTarget(id, generation, x, y, z, radius) : nullRuntime());
582 });
583 runtime.addFunc("clearHitTargets", [vm](ScriptCombatCarrierRuntime* self) {
584 return project(vm, self ? self->clearHitTargets() : nullRuntime());
585 });
586 runtime.addFunc("setTargetPosition", [vm](ScriptCombatCarrierRuntime* self, std::int64_t id,
587 std::int64_t generation, float x, float y, float z) {
588 return project(vm, self ? self->setTargetPosition(id, generation, x, y, z) : nullRuntime());
589 });
590 runtime.addFunc("clearTargets", [vm](ScriptCombatCarrierRuntime* self) {
591 return project(vm, self ? self->clearTargets() : nullRuntime());
592 });
593
594 // Module methods are registered from Weapon::expose(Class); helpers also on table for discoverability.
595 (void)vm;
596}
597
599 const HSQUIRRELVM vm = cls.getHandle();
600 cls.addFunc("newCarrierRuntime", [vm](Weapon*, std::int64_t capacity) { return newCarrierRuntime(vm, capacity); });
601 cls.addFunc("newCarrierRuntimeDefault", [vm](Weapon*) { return newCarrierRuntime(vm, 256); });
602}
603
604} // namespace eve::weapon
LogicalId target
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
ActiveSource owned
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
Decode/encode CombatCarrier recipes from canonical eve::Value trees.
int bz
Definition CaveMesh.cpp:114
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
Composable combat-carrier runtime: motion ops, triggers, and impacts.
struct SQVM * HSQUIRRELVM
Stable, structured diagnostics shared by engine modules.
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::string pattern
const GltfImportRequest & request
std::uint32_t spawned
std::uint32_t capacity
std::uint32_t key
wgpu::PopErrorScopeStatus status
TokenKind kind
std::array< float, 3 > position
graphics::Canvas * previous
float radius
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::uint32_t generation
std::uint32_t targetId
PrimitiveHandle handle
float t
double current
float dz
float dy
float dx
std::uint32_t count
std::optional< CarrierVolleySpec > volley
Compile Noita-style spell/modifier fragment sequences into carrier recipes.
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
Battle::Events events
Json object
Common time values and an injectable deterministic simulation clock.
Owning, renderer-independent dynamic values.
glm::vec3 point
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 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
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
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 Value object(Object value)
Compatibility factory for an object value.
Definition Value.h:114
武器模块(eve.Weapon)。
Definition Weapon.h:24
I * query()
Queries .
Definition Capability.h:88
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
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.
Result< Value > valueFromSquirrel(HSQUIRRELVM vm, SQInteger index, const SquirrelValueOptions &options)
Convert one Squirrel value into the canonical owning Value tree.
载具模块入口(eve.Vehicle):定义注册 / 实体工厂 / RTS 命令 / 帧调度。 设计文档:docs/dev/通用载具系统设计.md
CarrierImpactKind
What happens when a matching trigger fires.
void exposeCombatCarrierModuleMethods(ssq::Class &cls)
Register Weapon module methods that create owned carrier runtimes.
Result< CarrierRecipe > decodeCarrierRecipe(const Value &value)
Decode one CarrierRecipe from an owning Value object.
CarrierTriggerKind
When an impact may fire.
void exposeCombatCarrierBindings(ssq::Table &table)
Expose CombatCarrierRuntime class bindings under the shared eve table.
Result< SpellCastPlan > compileSpellFragmentsJson(const std::string &json)
Compile a JSON array string into one cast plan.
Result< SpellCastPlan > compileSpellFragments(const Value &fragments)
Compile a fragment array Value into one cast plan.
CarrierVolleyPattern
Multi-projectile aim pattern for one salvo.