载入中...
搜索中...
未找到
NpcAiModule.cpp
浏览该文件的文档.
2
5#include "npc_ai/NpcAi.h"
6
7#include <simplesquirrel/simplesquirrel.hpp>
8
9#include <cstdint>
10#include <functional>
11#include <limits>
12#include <memory>
13#include <optional>
14#include <string>
15#include <type_traits>
16#include <utility>
17#include <variant>
18
19namespace eve::npc_ai {
20namespace {
21
22Result<BlackboardType> parseBlackboardType(const std::string& text) {
28 "npc_ai blackboard type is invalid", "type", {},
29 "npc_ai.squirrel"));
30}
31
32Result<CompareOp> parseCompareOp(const std::string& text) {
33 if (text == "Exists") return Result<CompareOp>::success(CompareOp::Exists);
34 if (text == "Equal") return Result<CompareOp>::success(CompareOp::Equal);
35 if (text == "NotEqual") return Result<CompareOp>::success(CompareOp::NotEqual);
36 if (text == "Less") return Result<CompareOp>::success(CompareOp::Less);
37 if (text == "LessEqual") return Result<CompareOp>::success(CompareOp::LessEqual);
38 if (text == "Greater") return Result<CompareOp>::success(CompareOp::Greater);
39 if (text == "GreaterEqual") return Result<CompareOp>::success(CompareOp::GreaterEqual);
41 "npc_ai compare op is invalid", "op", {}, "npc_ai.squirrel"));
42}
43
44Result<BlackboardValue> parseBlackboardValue(const Value& value) {
45 if (const auto* flag = value.getIf<bool>()) return Result<BlackboardValue>::success(*flag);
46 if (const auto* integer = value.getIf<std::int64_t>()) return Result<BlackboardValue>::success(*integer);
47 if (const auto* number = value.getIf<double>()) return Result<BlackboardValue>::success(*number);
48 if (const auto* text = value.getIf<std::string>()) return Result<BlackboardValue>::success(*text);
50 "npc_ai blackboard value type is unsupported", "value",
51 {}, "npc_ai.squirrel"));
52}
53
54Result<std::uint32_t> parseUInt32Field(std::int64_t value, const std::string& path) {
55 constexpr auto kMax = static_cast<std::int64_t>(std::numeric_limits<std::uint32_t>::max());
56 if (value < 0 || value > kMax)
58 DiagnosticCode::InvalidArgument, "npc_ai unsigned 32-bit field is out of range", path, {},
59 "npc_ai.squirrel"));
60 return Result<std::uint32_t>::success(static_cast<std::uint32_t>(value));
61}
62
63Result<BlackboardPredicate> parsePredicate(const Value& value, const std::string& path) {
64 const auto* object = value.getIf<Value::Object>();
65 if (!object)
67 DiagnosticCode::InvalidArgument, "npc_ai predicate must be an object", path, {}, "npc_ai.squirrel"));
68 BlackboardPredicate predicate;
69 const auto key = object->find("key");
70 const auto op = object->find("op");
71 if (key == object->end() || !key->second.getIf<std::string>())
73 DiagnosticCode::InvalidArgument, "npc_ai predicate key is required", path + ".key", {}, "npc_ai.squirrel"));
74 if (op == object->end() || !op->second.getIf<std::string>())
76 DiagnosticCode::InvalidArgument, "npc_ai predicate op is required", path + ".op", {}, "npc_ai.squirrel"));
77 predicate.key = *key->second.getIf<std::string>();
78 auto parsedOp = parseCompareOp(*op->second.getIf<std::string>());
79 if (!parsedOp) return Result<BlackboardPredicate>::failure(parsedOp.status());
80 predicate.op = parsedOp.value();
81 if (const auto found = object->find("value"); found != object->end()) {
82 auto parsed = parseBlackboardValue(found->second);
83 if (!parsed) return Result<BlackboardPredicate>::failure(parsed.status());
84 predicate.value = std::move(parsed).takeValue();
85 }
86 return Result<BlackboardPredicate>::success(std::move(predicate));
87}
88
89Result<BehaviorDefinition> parseBehaviorDefinition(const Value& value) {
90 const auto* root = value.getIf<Value::Object>();
91 if (!root)
93 "npc_ai behavior must be an object", "behavior",
94 {}, "npc_ai.squirrel"));
95 BehaviorDefinition definition;
96 const auto id = root->find("id");
97 if (id == root->end() || !id->second.getIf<std::string>())
99 DiagnosticCode::InvalidArgument, "npc_ai behavior id is required", "id", {}, "npc_ai.squirrel"));
100 definition.id = *id->second.getIf<std::string>();
101 if (const auto version = root->find("schemaVersion"); version != root->end()) {
102 if (const auto* integer = version->second.getIf<std::int64_t>()) {
103 auto parsedVersion = parseUInt32Field(*integer, "schemaVersion");
104 if (!parsedVersion) return Result<BehaviorDefinition>::failure(parsedVersion.status());
105 definition.schemaVersion = parsedVersion.value();
106 } else
108 "npc_ai schemaVersion must be an integer",
109 "schemaVersion", {}, "npc_ai.squirrel"));
110 }
111 const auto initial = root->find("initialState");
112 if (initial == root->end() || !initial->second.getIf<std::string>())
114 "npc_ai initialState is required", "initialState",
115 {}, "npc_ai.squirrel"));
116 definition.initialState = *initial->second.getIf<std::string>();
117
118 if (const auto schema = root->find("blackboardSchema"); schema != root->end()) {
119 const auto* array = schema->second.getIf<Value::Array>();
120 if (!array)
122 "npc_ai blackboardSchema must be an array",
123 "blackboardSchema", {}, "npc_ai.squirrel"));
124 for (std::size_t index = 0; index < array->size(); ++index) {
125 const auto* item = (*array)[index].getIf<Value::Object>();
126 if (!item)
128 DiagnosticCode::InvalidArgument, "npc_ai blackboard schema entry must be an object",
129 "blackboardSchema[" + std::to_string(index) + "]", {}, "npc_ai.squirrel"));
130 BlackboardKeySpec keySpec;
131 const auto key = item->find("key");
132 const auto type = item->find("type");
133 if (key == item->end() || !key->second.getIf<std::string>() || type == item->end() ||
134 !type->second.getIf<std::string>())
136 DiagnosticCode::InvalidArgument, "npc_ai blackboard schema key/type are required",
137 "blackboardSchema[" + std::to_string(index) + "]", {}, "npc_ai.squirrel"));
138 keySpec.key = *key->second.getIf<std::string>();
139 auto parsed = parseBlackboardType(*type->second.getIf<std::string>());
140 if (!parsed) return Result<BehaviorDefinition>::failure(parsed.status());
141 keySpec.type = parsed.value();
142 if (const auto required = item->find("required"); required != item->end()) {
143 if (const auto* flag = required->second.getIf<bool>())
144 keySpec.required = *flag;
145 else
147 DiagnosticCode::InvalidArgument, "npc_ai blackboard required must be a bool",
148 "blackboardSchema[" + std::to_string(index) + "].required", {}, "npc_ai.squirrel"));
149 }
150 if (const auto defaultValue = item->find("default"); defaultValue != item->end()) {
151 auto parsedDefault = parseBlackboardValue(defaultValue->second);
152 if (!parsedDefault) return Result<BehaviorDefinition>::failure(parsedDefault.status());
153 keySpec.defaultValue = std::move(parsedDefault).takeValue();
154 }
155 definition.blackboardSchema.push_back(std::move(keySpec));
156 }
157 }
158
159 const auto states = root->find("states");
160 if (states == root->end() || !states->second.getIf<Value::Array>())
162 DiagnosticCode::InvalidArgument, "npc_ai states must be an array", "states", {}, "npc_ai.squirrel"));
163 const auto& stateArray = *states->second.getIf<Value::Array>();
164 for (std::size_t index = 0; index < stateArray.size(); ++index) {
165 const std::string path = "states[" + std::to_string(index) + "]";
166 const auto* item = stateArray[index].getIf<Value::Object>();
167 if (!item)
169 DiagnosticCode::InvalidArgument, "npc_ai state must be an object", path, {}, "npc_ai.squirrel"));
170 StateDefinition state;
171 const auto stateId = item->find("id");
172 if (stateId == item->end() || !stateId->second.getIf<std::string>())
174 DiagnosticCode::InvalidArgument, "npc_ai state id is required", path + ".id", {}, "npc_ai.squirrel"));
175 state.id = *stateId->second.getIf<std::string>();
176 if (const auto parent = item->find("parent"); parent != item->end()) {
177 if (const auto* text = parent->second.getIf<std::string>())
178 state.parent = *text;
179 else
181 "npc_ai state parent must be a string",
182 path + ".parent", {}, "npc_ai.squirrel"));
183 }
184 if (const auto enter = item->find("enterConditions"); enter != item->end()) {
185 const auto* array = enter->second.getIf<Value::Array>();
186 if (!array)
188 Diagnostic::error(DiagnosticCode::InvalidArgument, "npc_ai enterConditions must be an array",
189 path + ".enterConditions", {}, "npc_ai.squirrel"));
190 for (std::size_t predicateIndex = 0; predicateIndex < array->size(); ++predicateIndex) {
191 auto predicate =
192 parsePredicate((*array)[predicateIndex], path + ".enterConditions[" + std::to_string(predicateIndex) + "]");
193 if (!predicate) return Result<BehaviorDefinition>::failure(predicate.status());
194 state.enterConditions.push_back(std::move(predicate).takeValue());
195 }
196 }
197 if (const auto tasks = item->find("tasks"); tasks != item->end()) {
198 const auto* array = tasks->second.getIf<Value::Array>();
199 if (!array)
201 DiagnosticCode::InvalidArgument, "npc_ai tasks must be an array", path + ".tasks", {},
202 "npc_ai.squirrel"));
203 for (std::size_t taskIndex = 0; taskIndex < array->size(); ++taskIndex) {
204 const auto* taskObject = (*array)[taskIndex].getIf<Value::Object>();
205 if (!taskObject)
207 DiagnosticCode::InvalidArgument, "npc_ai task must be an object",
208 path + ".tasks[" + std::to_string(taskIndex) + "]", {}, "npc_ai.squirrel"));
209 TaskSpec task;
210 const auto taskId = taskObject->find("id");
211 const auto taskType = taskObject->find("type");
212 if (taskId == taskObject->end() || !taskId->second.getIf<std::string>() ||
213 taskType == taskObject->end() || !taskType->second.getIf<std::string>())
215 DiagnosticCode::InvalidArgument, "npc_ai task id/type are required",
216 path + ".tasks[" + std::to_string(taskIndex) + "]", {}, "npc_ai.squirrel"));
217 task.id = *taskId->second.getIf<std::string>();
218 task.type = *taskType->second.getIf<std::string>();
219 if (const auto parameters = taskObject->find("parametersJson"); parameters != taskObject->end()) {
220 if (const auto* text = parameters->second.getIf<std::string>())
221 task.parametersJson = *text;
222 else
224 DiagnosticCode::InvalidArgument, "npc_ai task parametersJson must be a string",
225 path + ".tasks[" + std::to_string(taskIndex) + "].parametersJson", {}, "npc_ai.squirrel"));
226 }
227 state.tasks.push_back(std::move(task));
228 }
229 }
230 if (const auto transitions = item->find("transitions"); transitions != item->end()) {
231 const auto* array = transitions->second.getIf<Value::Array>();
232 if (!array)
234 Diagnostic::error(DiagnosticCode::InvalidArgument, "npc_ai transitions must be an array",
235 path + ".transitions", {}, "npc_ai.squirrel"));
236 for (std::size_t transitionIndex = 0; transitionIndex < array->size(); ++transitionIndex) {
237 const std::string transitionPath =
238 path + ".transitions[" + std::to_string(transitionIndex) + "]";
239 const auto* transitionObject = (*array)[transitionIndex].getIf<Value::Object>();
240 if (!transitionObject)
242 DiagnosticCode::InvalidArgument, "npc_ai transition must be an object", transitionPath, {},
243 "npc_ai.squirrel"));
244 Transition transition;
245 const auto target = transitionObject->find("targetState");
246 if (target == transitionObject->end() || !target->second.getIf<std::string>())
248 DiagnosticCode::InvalidArgument, "npc_ai transition targetState is required",
249 transitionPath + ".targetState", {}, "npc_ai.squirrel"));
250 transition.targetState = *target->second.getIf<std::string>();
251 if (const auto signal = transitionObject->find("signal"); signal != transitionObject->end()) {
252 if (const auto* text = signal->second.getIf<std::string>())
253 transition.signal = *text;
254 else
256 DiagnosticCode::InvalidArgument, "npc_ai transition signal must be a string",
257 transitionPath + ".signal", {}, "npc_ai.squirrel"));
258 }
259 if (const auto priority = transitionObject->find("priority"); priority != transitionObject->end()) {
260 if (const auto* integer = priority->second.getIf<std::int64_t>()) {
261 auto parsedPriority = parseUInt32Field(*integer, transitionPath + ".priority");
262 if (!parsedPriority) return Result<BehaviorDefinition>::failure(parsedPriority.status());
263 transition.priority = parsedPriority.value();
264 } else
266 DiagnosticCode::InvalidArgument, "npc_ai transition priority must be an integer",
267 transitionPath + ".priority", {}, "npc_ai.squirrel"));
268 }
269 if (const auto conditions = transitionObject->find("conditions");
270 conditions != transitionObject->end()) {
271 const auto* conditionArray = conditions->second.getIf<Value::Array>();
272 if (!conditionArray)
274 DiagnosticCode::InvalidArgument, "npc_ai transition conditions must be an array",
275 transitionPath + ".conditions", {}, "npc_ai.squirrel"));
276 for (std::size_t conditionIndex = 0; conditionIndex < conditionArray->size(); ++conditionIndex) {
277 auto predicate = parsePredicate(
278 (*conditionArray)[conditionIndex],
279 transitionPath + ".conditions[" + std::to_string(conditionIndex) + "]");
280 if (!predicate) return Result<BehaviorDefinition>::failure(predicate.status());
281 transition.conditions.push_back(std::move(predicate).takeValue());
282 }
283 }
284 state.transitions.push_back(std::move(transition));
285 }
286 }
287 definition.states.push_back(std::move(state));
288 }
289 return Result<BehaviorDefinition>::success(std::move(definition));
290}
291
292Value blackboardValueToValue(const BlackboardValue& value) {
293 return std::visit(
294 [](const auto& item) -> Value {
295 using T = std::decay_t<decltype(item)>;
296 if constexpr (std::is_same_v<T, bool>)
297 return Value(item);
298 else if constexpr (std::is_same_v<T, std::int64_t>)
299 return Value(item);
300 else if constexpr (std::is_same_v<T, double>)
301 return Value(item);
302 else
303 return Value(item);
304 },
305 value);
306}
307
308Result<AgentHandle> parseAgentHandle(std::int64_t packed) {
309 if (packed <= 0)
311 DiagnosticCode::InvalidArgument, "npc_ai agent handle must be positive", "agent", {}, "npc_ai.squirrel"));
312 return Result<AgentHandle>::success(AgentHandle::fromPacked(static_cast<std::uint64_t>(packed)));
313}
314
315class ScriptNpcAiWorld {
316public:
317 explicit ScriptNpcAiWorld(std::int64_t traceCapacity, std::int64_t maxMemoriesPerAgent) {
318 NpcAiWorldConfig config;
319 if (traceCapacity > 0) config.traceCapacity = static_cast<std::size_t>(traceCapacity);
320 // Zero disables perception memory in the core world; only negative keeps the default.
321 if (maxMemoriesPerAgent >= 0) config.maxMemoriesPerAgent = static_cast<std::size_t>(maxMemoriesPerAgent);
322 world_ = std::make_unique<NpcAiWorld>(config);
323 }
324
325 Result<Value> registerBehavior(const std::string& definitionJson) {
326 auto decoded = Value::fromJson(definitionJson);
327 if (!decoded) return Result<Value>::failure(decoded.status());
328 auto definition = parseBehaviorDefinition(decoded.value());
329 if (!definition) return Result<Value>::failure(definition.status());
330 const std::string id = definition.value().id;
331 auto registered = world_->registerBehavior(std::move(definition).takeValue());
332 if (!registered) return Result<Value>::failure(registered.status());
333 Value::Object result;
334 result["behaviorId"] = id;
336 }
337
338 Result<Value> validateBehavior(const std::string& definitionJson) const {
339 auto decoded = Value::fromJson(definitionJson);
340 if (!decoded) return Result<Value>::failure(decoded.status());
341 auto definition = parseBehaviorDefinition(decoded.value());
342 if (!definition) return Result<Value>::failure(definition.status());
343 auto validated = NpcAiWorld::validate(definition.value());
344 if (!validated) return Result<Value>::failure(validated.status());
345 Value::Object result;
346 result["behaviorId"] = definition.value().id;
347 return Result<Value>::success(Value(std::move(result)));
348 }
349
350 Result<Value> createAgent(const std::string& behaviorId) {
351 auto created = world_->createAgent(behaviorId);
352 if (!created) return Result<Value>::failure(created.status());
353 return Result<Value>::success(Value(static_cast<std::int64_t>(created.value().packed())),
355 }
356
357 Result<Value> destroyAgent(std::int64_t packed) {
358 auto handle = parseAgentHandle(packed);
359 if (!handle) return Result<Value>::failure(handle.status());
360 auto destroyed = world_->destroyAgent(handle.value());
361 if (!destroyed) return Result<Value>::failure(destroyed.status());
363 }
364
365 bool isAgentStale(std::int64_t packed) const {
366 auto handle = parseAgentHandle(packed);
367 if (!handle) return true;
368 return world_->isStale(handle.value());
369 }
370
371 Result<Value> setBlackboardBool(std::int64_t packed, const std::string& key, bool value) {
372 return setBlackboard(packed, key, BlackboardValue(value));
373 }
374 Result<Value> setBlackboardInt(std::int64_t packed, const std::string& key, std::int64_t value) {
375 return setBlackboard(packed, key, BlackboardValue(value));
376 }
377 Result<Value> setBlackboardNumber(std::int64_t packed, const std::string& key, double value) {
378 return setBlackboard(packed, key, BlackboardValue(value));
379 }
380 Result<Value> setBlackboardString(std::int64_t packed, const std::string& key, const std::string& value) {
381 return setBlackboard(packed, key, BlackboardValue(value));
382 }
383
384 Result<Value> signal(std::int64_t packed, const std::string& signalName) {
385 auto handle = parseAgentHandle(packed);
386 if (!handle) return Result<Value>::failure(handle.status());
387 auto signaled = world_->signal(handle.value(), signalName);
388 if (!signaled) return Result<Value>::failure(signaled.status());
390 }
391
392 Result<Value> remember(std::int64_t packed, const std::string& subject, const std::string& sense, double confidence,
393 std::int64_t observedTick, std::int64_t forgetAfterTicks, const std::string& payloadJson) {
394 auto handle = parseAgentHandle(packed);
395 if (!handle) return Result<Value>::failure(handle.status());
396 if (observedTick < 0 || forgetAfterTicks <= 0)
398 "npc_ai remember ticks are invalid", "tick", {},
399 "npc_ai.squirrel"));
400 PerceptionMemory memory;
401 memory.subject = subject;
402 memory.sense = sense;
403 memory.confidence = confidence;
404 memory.observedTick = static_cast<std::uint64_t>(observedTick);
405 memory.forgetAfterTicks = static_cast<std::uint64_t>(forgetAfterTicks);
406 memory.payloadJson = payloadJson.empty() ? "{}" : payloadJson;
407 auto remembered = world_->remember(handle.value(), std::move(memory));
408 if (!remembered) return Result<Value>::failure(remembered.status());
410 }
411
412 Result<Value> forget(std::int64_t packed, const std::string& subject, const std::string& sense) {
413 auto handle = parseAgentHandle(packed);
414 if (!handle) return Result<Value>::failure(handle.status());
415 auto forgotten = world_->forget(handle.value(), subject, sense);
416 if (!forgotten) return Result<Value>::failure(forgotten.status());
418 }
419
420 Result<Value> tick(std::int64_t simulationTick, double deltaSeconds, std::int64_t maxAgents,
421 std::int64_t maxTransitionsPerAgent) {
422 auto parsedMaxAgents = parseUInt32Field(maxAgents, "maxAgents");
423 if (!parsedMaxAgents) return Result<Value>::failure(parsedMaxAgents.status());
424 auto parsedMaxTransitions = parseUInt32Field(maxTransitionsPerAgent, "maxTransitionsPerAgent");
425 if (!parsedMaxTransitions) return Result<Value>::failure(parsedMaxTransitions.status());
426 if (simulationTick < 0 || !(deltaSeconds >= 0.0))
428 "npc_ai tick arguments are invalid", "tick", {},
429 "npc_ai.squirrel"));
430 TickContext context;
431 context.simulationTick = static_cast<std::uint64_t>(simulationTick);
432 context.deltaSeconds = deltaSeconds;
433 context.maxAgents = parsedMaxAgents.value();
434 context.maxTransitionsPerAgent = parsedMaxTransitions.value();
435 auto report = world_->tick(context);
436 if (!report) return Result<Value>::failure(report.status());
437 Value::Object result;
438 result["agentsUpdated"] = static_cast<std::int64_t>(report.value().agentsUpdated);
439 result["agentsDeferred"] = static_cast<std::int64_t>(report.value().agentsDeferred);
440 result["transitionsApplied"] = static_cast<std::int64_t>(report.value().transitionsApplied);
441 result["transitionBudgetsExhausted"] = static_cast<std::int64_t>(report.value().transitionBudgetsExhausted);
442 result["tasksTicked"] = static_cast<std::int64_t>(report.value().tasksTicked);
444 }
445
446 Result<Value> snapshotValue(std::int64_t packed) const {
447 auto handle = parseAgentHandle(packed);
448 if (!handle) return Result<Value>::failure(handle.status());
449 auto snapshot = world_->snapshot(handle.value());
450 if (!snapshot) return Result<Value>::failure(snapshot.status());
451 Value::Object result;
452 result["handle"] = static_cast<std::int64_t>(snapshot.value().handle.packed());
453 result["behaviorId"] = snapshot.value().behaviorId;
454 result["activeState"] = snapshot.value().activeState;
455 result["lastTick"] = static_cast<std::int64_t>(snapshot.value().lastTick);
457 for (const auto& state : snapshot.value().activePath) path.emplace_back(state);
458 result["activePath"] = std::move(path);
460 for (const auto& [key, value] : snapshot.value().blackboard) board[key] = blackboardValueToValue(value);
461 result["blackboard"] = std::move(board);
462 Value::Array perception;
463 for (const auto& memory : snapshot.value().perception) {
464 Value::Object item;
465 item["subject"] = memory.subject;
466 item["sense"] = memory.sense;
467 item["confidence"] = memory.confidence;
468 item["observedTick"] = static_cast<std::int64_t>(memory.observedTick);
469 item["forgetAfterTicks"] = static_cast<std::int64_t>(memory.forgetAfterTicks);
470 item["payloadJson"] = memory.payloadJson;
471 perception.emplace_back(std::move(item));
472 }
473 result["perception"] = std::move(perception);
474 return Result<Value>::success(Value(std::move(result)));
475 }
476
477 Result<Value> snapshotJson(std::int64_t packed) const {
478 auto snapshot = snapshotValue(packed);
479 if (!snapshot) return snapshot;
480 auto json = snapshot.value().toJson();
481 if (!json) return Result<Value>::failure(json.status());
482 return Result<Value>::success(Value(json.value()));
483 }
484
485private:
486 Result<Value> setBlackboard(std::int64_t packed, const std::string& key, BlackboardValue value) {
487 auto handle = parseAgentHandle(packed);
488 if (!handle) return Result<Value>::failure(handle.status());
489 auto written = world_->setBlackboard(handle.value(), key, std::move(value));
490 if (!written) return Result<Value>::failure(written.status());
492 }
493
494 std::unique_ptr<NpcAiWorld> world_;
495};
496
497ssq::Table project(HSQUIRRELVM vm, Result<Value>&& result) {
498 return script::projectResult(vm, std::move(result), [](Value value) { return value; });
499}
500
501ssq::Table nullWorld(HSQUIRRELVM vm) {
503 "npc_ai world must not be null", "world", {},
504 "npc_ai.squirrel")));
505}
506
507ssq::Table newWorld(HSQUIRRELVM vm, std::int64_t traceCapacity, std::int64_t maxMemoriesPerAgent) {
508 auto world = std::make_unique<ScriptNpcAiWorld>(traceCapacity, maxMemoriesPerAgent);
509 auto object = script::makeOwnedSquirrelInstance<ScriptNpcAiWorld>(vm, std::move(world));
510 if (!object) return script::projectStatusResult(vm, object.status());
512 result.set("value", std::move(object).takeValue());
513 result.set("ownership", std::string("owned"));
514 return result;
515}
516
517} // namespace
518
520
521void NpcAi::expose(ssq::Table& table) {
522 const HSQUIRRELVM vm = table.getHandle();
523 auto world = table.addClass<ScriptNpcAiWorld>(
524 "NpcAiWorld", std::function<ScriptNpcAiWorld*()>([] { return nullptr; }), true);
525 world.addFunc("ownership", [](ScriptNpcAiWorld*) { return std::string("owned"); });
526 world.addFunc("registerBehavior", [vm](ScriptNpcAiWorld* self, const std::string& json) {
527 return self ? project(vm, self->registerBehavior(json)) : nullWorld(vm);
528 });
529 world.addFunc("validateBehavior", [vm](ScriptNpcAiWorld* self, const std::string& json) {
530 return self ? project(vm, self->validateBehavior(json)) : nullWorld(vm);
531 });
532 world.addFunc("createAgent", [vm](ScriptNpcAiWorld* self, const std::string& behaviorId) {
533 return self ? project(vm, self->createAgent(behaviorId)) : nullWorld(vm);
534 });
535 world.addFunc("destroyAgent", [vm](ScriptNpcAiWorld* self, std::int64_t agent) {
536 return self ? project(vm, self->destroyAgent(agent)) : nullWorld(vm);
537 });
538 world.addFunc("isAgentStale", [](ScriptNpcAiWorld* self, std::int64_t agent) {
539 return !self || self->isAgentStale(agent);
540 });
541 world.addFunc("setBlackboardBool",
542 [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& key, bool value) {
543 return self ? project(vm, self->setBlackboardBool(agent, key, value)) : nullWorld(vm);
544 });
545 world.addFunc("setBlackboardInt",
546 [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& key, std::int64_t value) {
547 return self ? project(vm, self->setBlackboardInt(agent, key, value)) : nullWorld(vm);
548 });
549 world.addFunc("setBlackboardNumber",
550 [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& key, float value) {
551 return self ? project(vm, self->setBlackboardNumber(agent, key, value)) : nullWorld(vm);
552 });
553 world.addFunc("setBlackboardString",
554 [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& key, const std::string& value) {
555 return self ? project(vm, self->setBlackboardString(agent, key, value)) : nullWorld(vm);
556 });
557 world.addFunc("signal", [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& signalName) {
558 return self ? project(vm, self->signal(agent, signalName)) : nullWorld(vm);
559 });
560 world.addFunc("remember", [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& subject,
561 const std::string& sense, float confidence, std::int64_t observedTick,
562 std::int64_t forgetAfterTicks, const std::string& payloadJson) {
563 return self ? project(vm, self->remember(agent, subject, sense, confidence, observedTick, forgetAfterTicks,
564 payloadJson))
565 : nullWorld(vm);
566 });
567 world.addFunc("forget", [vm](ScriptNpcAiWorld* self, std::int64_t agent, const std::string& subject,
568 const std::string& sense) {
569 return self ? project(vm, self->forget(agent, subject, sense)) : nullWorld(vm);
570 });
571 world.addFunc("tick", [vm](ScriptNpcAiWorld* self, std::int64_t simulationTick, float deltaSeconds,
572 std::int64_t maxAgents, std::int64_t maxTransitionsPerAgent) {
573 return self ? project(vm, self->tick(simulationTick, deltaSeconds, maxAgents, maxTransitionsPerAgent))
574 : nullWorld(vm);
575 });
576 world.addFunc("snapshot", [vm](ScriptNpcAiWorld* self, std::int64_t agent) {
577 return self ? project(vm, self->snapshotValue(agent)) : nullWorld(vm);
578 });
579 world.addFunc("snapshotJson", [vm](ScriptNpcAiWorld* self, std::int64_t agent) {
580 return self ? project(vm, self->snapshotJson(agent)) : nullWorld(vm);
581 });
582
583 auto module = table.addClass(name, NpcAi::create, false);
584 module.addFunc("getName", &NpcAi::getName);
585 module.addFunc("newWorld", [vm](NpcAi*, std::int64_t traceCapacity, std::int64_t maxMemoriesPerAgent) {
586 return newWorld(vm, traceCapacity, maxMemoriesPerAgent);
587 });
588}
589
590void NpcAi::expose(ssq::Class& cls) {
591 cls.addFunc("getName", &NpcAi::getName);
592 cls.addFunc("newWorld", [vm = cls.getHandle()](NpcAi*, std::int64_t traceCapacity,
593 std::int64_t maxMemoriesPerAgent) {
594 return newWorld(vm, traceCapacity, maxMemoriesPerAgent);
595 });
596}
597
598} // namespace eve::npc_ai
LogicalId target
double value
int root
Definition AnimSmr.cpp:119
int subject
Definition AnimSmr.cpp:163
int priority
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::uint32_t key
vk::UniqueDeviceMemory memory
wgpu::PopErrorScopeStatus status
std::string text
std::int32_t parent
bool required
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
Script-facing factory for NpcAiWorld instances.
World3D * world
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
PrimitiveHandle handle
std::string taskId
double number
bool found
int created
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
SimulationTick tick
BoardState board
Json object
uint32_t index
const VegetationPresetContext & context
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 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 constexpr RuntimeHandle fromPacked(std::uint64_t value) noexcept
Reconstructs a handle from an explicit packed boundary value.
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 Result< void > validate(const BehaviorDefinition &definition)
Validates definition shape without publishing it.
Definition NpcAi.cpp:48
Factory module for script-owned NPC AI worlds.
Definition NpcAiModule.h:17
const char * defaultValue
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.
std::variant< bool, std::int64_t, double, std::string > BlackboardValue
Definition NpcAi.h:22
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.