载入中...
搜索中...
未找到
Dialogue.cpp
浏览该文件的文档.
1#include "dialogue/Dialogue.h"
3
5#include "common/Capability.h"
7#include "common/utf8.h"
10#include "i18n/I18n.h"
11#include "scene/Scene.h"
12#include "scene/SceneHost.h"
13
14#include <algorithm>
15#include <cmath>
16#include <cstdio>
17#include <iterator>
18#include <simplesquirrel/simplesquirrel.hpp>
19#include <squirrel.h>
20
21namespace eve::dialogue {
22
24
25namespace {
26
27std::string floatToString(double v) {
28 char buf[64];
29 std::snprintf(buf, sizeof(buf), "%g", v);
30 return buf;
31}
32
33bool squirrelToVarValueAt(HSQUIRRELVM vm, SQInteger idx, Dialogue::VarValue &out) {
34 switch (sq_gettype(vm, idx)) {
35 case OT_INTEGER: {
36 SQInteger v = 0;
37 if (SQ_FAILED(sq_getinteger(vm, idx, &v))) return false;
39 return true;
40 }
41 case OT_FLOAT: {
42 SQFloat v = 0;
43 if (SQ_FAILED(sq_getfloat(vm, idx, &v))) return false;
45 return true;
46 }
47 case OT_BOOL: {
48 SQBool v = SQFalse;
49 if (SQ_FAILED(sq_getbool(vm, idx, &v))) return false;
51 return true;
52 }
53 case OT_STRING: {
54 const SQChar *v = nullptr;
55 if (SQ_FAILED(sq_getstring(vm, idx, &v))) return false;
56 out = Dialogue::VarValue::string(v ? v : "");
57 return true;
58 }
59 default:
60 return false;
61 }
62}
63
65bool squirrelToDataValue(HSQUIRRELVM vm, SQInteger idx, DataValue &out) {
66 switch (sq_gettype(vm, idx)) {
67 case OT_NULL:
68 out = DataValue::null();
69 return true;
70 case OT_INTEGER: {
71 SQInteger v = 0;
72 if (SQ_FAILED(sq_getinteger(vm, idx, &v))) return false;
73 out = DataValue::integer(v);
74 return true;
75 }
76 case OT_FLOAT: {
77 SQFloat v = 0;
78 if (SQ_FAILED(sq_getfloat(vm, idx, &v))) return false;
79 out = DataValue::number(v);
80 return true;
81 }
82 case OT_BOOL: {
83 SQBool v = SQFalse;
84 if (SQ_FAILED(sq_getbool(vm, idx, &v))) return false;
85 out = DataValue::boolean(v != 0);
86 return true;
87 }
88 case OT_STRING: {
89 const SQChar *s = nullptr;
90 if (SQ_FAILED(sq_getstring(vm, idx, &s))) return false;
91 out = DataValue::string(s ? s : "");
92 return true;
93 }
94 case OT_ARRAY: {
95 const SQInteger size = sq_getsize(vm, idx);
96 const SQInteger absIdx = idx > 0 ? idx : sq_gettop(vm) + idx + 1;
97 std::vector<DataValue> items;
98 for (SQInteger i = 0; i < size; ++i) {
99 sq_pushinteger(vm, i);
100 if (SQ_FAILED(sq_get(vm, absIdx))) return false;
101 DataValue item;
102 const bool ok = squirrelToDataValue(vm, -1, item);
103 sq_pop(vm, 1);
104 if (!ok) return false;
105 items.push_back(std::move(item));
106 }
107 out = DataValue::array(std::move(items));
108 return true;
109 }
110 case OT_TABLE: {
111 std::vector<std::pair<std::string, DataValue>> fields;
112 sq_pushnull(vm); // iterator
113 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
114 std::string key;
115 if (sq_gettype(vm, -2) == OT_STRING) {
116 const SQChar *k = nullptr;
117 if (SQ_SUCCEEDED(sq_getstring(vm, -2, &k)) && k) key = k;
118 }
119 DataValue value;
120 const bool ok = !key.empty() && squirrelToDataValue(vm, -1, value);
121 sq_pop(vm, 2);
122 if (!ok) {
123 sq_pop(vm, 1); // iterator
124 return false;
125 }
126 fields.emplace_back(std::move(key), std::move(value));
127 }
128 sq_pop(vm, 1); // iterator
129 out = DataValue::object(std::move(fields));
130 return true;
131 }
132 default:
133 return false;
134 }
135}
136
137bool objectToDataValue(HSQUIRRELVM vm, const ssq::Object &obj, DataValue &out) {
138 if (!vm) return false;
139 const SQInteger top = sq_gettop(vm);
140 sq_pushobject(vm, obj.getRaw());
141 const bool ok = squirrelToDataValue(vm, -1, out);
142 sq_settop(vm, top);
143 return ok;
144}
145
146bool objectToVarParams(HSQUIRRELVM vm, const ssq::Object &obj,
147 std::unordered_map<std::string, Dialogue::VarValue> &out) {
148 out.clear();
149 if (!vm) return true; // no VM -> treat as empty params
150 const HSQOBJECT raw = obj.getRaw();
151 if (raw._type != OT_TABLE) return true; // null / other -> empty params
152 const SQInteger top = sq_gettop(vm);
153 sq_pushobject(vm, raw);
154 sq_pushnull(vm); // iterator
155 while (SQ_SUCCEEDED(sq_next(vm, -2))) {
156 const SQChar *k = nullptr;
157 if (sq_gettype(vm, -2) == OT_STRING && SQ_SUCCEEDED(sq_getstring(vm, -2, &k)) && k) {
158 Dialogue::VarValue v;
159 if (squirrelToVarValueAt(vm, -1, v)) out[k] = std::move(v);
160 }
161 sq_pop(vm, 2);
162 }
163 sq_settop(vm, top);
164 return true;
165}
166
167void pushVarValue(HSQUIRRELVM vm, const Dialogue::VarValue &v) {
168 switch (v.type) {
170 sq_pushinteger(vm, SQInteger(v.i));
171 break;
173 sq_pushfloat(vm, SQFloat(v.f));
174 break;
176 sq_pushbool(vm, v.b ? SQTrue : SQFalse);
177 break;
179 sq_pushstring(vm, v.s.c_str(), v.s.size());
180 break;
181 }
182}
183
184void pushVarTable(HSQUIRRELVM vm, const std::unordered_map<std::string, Dialogue::VarValue> &vars) {
185 sq_newtable(vm);
186 for (const auto &kv : vars) {
187 sq_pushstring(vm, kv.first.c_str(), kv.first.size());
188 pushVarValue(vm, kv.second);
189 sq_newslot(vm, -3, SQFalse);
190 }
191}
192
193std::vector<Dialogue*>& liveDialogues() {
194 static std::vector<Dialogue*> instances;
195 return instances;
196}
197
198StateValue varValueToState(const Dialogue::VarValue& v) {
199 switch (v.type) {
204 }
205 return StateValue::null();
206}
207
208bool stateToVarValue(const StateValue& v, Dialogue::VarValue& out) {
209 switch (v.kind()) {
210 case StateValue::Kind::Int: out = Dialogue::VarValue::integer(v.asInt()); return true;
211 case StateValue::Kind::Float: out = Dialogue::VarValue::number(v.asDouble()); return true;
212 case StateValue::Kind::Bool: out = Dialogue::VarValue::boolean(v.asBool()); return true;
213 case StateValue::Kind::String: out = Dialogue::VarValue::string(v.asString()); return true;
214 default: return false;
215 }
216}
217
219class DialogueStateProvider : public eve::caps::IStateProvider {
220public:
221 const char* stateKind() const override { return "dialogue"; }
222
223 bool captureState(StateValue& out) override {
224 StateValue arr = StateValue::array();
225 for (Dialogue* d : liveDialogues()) {
226 StateValue sv;
227 if (d->captureState(sv)) arr.pushBack(std::move(sv));
228 }
229 out = std::move(arr);
230 return true;
231 }
232
233 bool restoreState(const StateValue& in, std::string* err) override {
234 if (!in.isArray()) {
235 if (err) *err = "dialogue: expected array of instances";
236 return false;
237 }
238 const size_t n = std::min(in.arraySize(), liveDialogues().size());
239 for (size_t i = 0; i < n; ++i) {
240 if (!liveDialogues()[i]->restoreState(in.at(i), err)) return false;
241 }
242 return true;
243 }
244
245 bool resetToDefaults() override {
246 for (Dialogue* d : liveDialogues()) d->reset();
247 return true;
248 }
249};
250
251struct Register {
252 Register() {
253 static DialogueStateProvider provider;
255 }
256} g_register;
257
258} // namespace
259
261 VarValue x;
262 x.type = Type::Int;
263 x.i = v;
264 return x;
265}
266
268 VarValue x;
269 x.type = Type::Float;
270 x.f = v;
271 return x;
272}
273
275 VarValue x;
276 x.type = Type::Bool;
277 x.b = v;
278 return x;
279}
280
282 VarValue x;
283 x.type = Type::String;
284 x.s = std::move(v);
285 return x;
286}
287
288std::string Dialogue::VarValue::typeName() const {
289 switch (type) {
290 case Type::Int:
291 return "int";
292 case Type::Float:
293 return "float";
294 case Type::Bool:
295 return "bool";
296 case Type::String:
297 return "string";
298 }
299 return "string";
300}
301
302std::string Dialogue::VarValue::toString() const {
303 switch (type) {
304 case Type::Int:
305 return std::to_string(i);
306 case Type::Float:
307 return floatToString(f);
308 case Type::Bool:
309 return b ? "true" : "false";
310 case Type::String:
311 return s;
312 }
313 return {};
314}
315
316Dialogue::Dialogue() { liveDialogues().push_back(this); }
317
319 liveDialogues().erase(std::remove(liveDialogues().begin(), liveDialogues().end(), this), liveDialogues().end());
320 if (vm_) {
321 for (auto &kv : predicates_) sq_release(vm_, &kv.second);
322 predicates_.clear();
323 }
324}
325
326// ---------------------------------------------------------------------------
327// Variables (global / scene)
328// ---------------------------------------------------------------------------
329
330std::unordered_map<std::string, Dialogue::VarValue> *Dialogue::varsForScope(
331 const std::string &scope) {
332 if (scope == "global") return &globalVars_;
333 if (scope == "scene") return &sceneVars_;
334 return nullptr;
335}
336
337const std::unordered_map<std::string, Dialogue::VarValue> *Dialogue::varsForScope(
338 const std::string &scope) const {
339 if (scope == "global") return &globalVars_;
340 if (scope == "scene") return &sceneVars_;
341 return nullptr;
342}
343
344bool Dialogue::setVarValue(const std::string &name, const VarValue &value,
345 const std::string &scope) {
346 auto *m = varsForScope(scope);
347 if (!m || name.empty()) return false;
348 (*m)[name] = value;
349 return true;
350}
351
352bool Dialogue::setVar(const std::string &name, ssq::Object value, const std::string &scope) {
353 if (!vm_) return false;
354 const SQInteger top = sq_gettop(vm_);
355 sq_pushobject(vm_, value.getRaw());
356 VarValue v;
357 const bool ok = squirrelToVarValueAt(vm_, -1, v);
358 sq_settop(vm_, top);
359 if (!ok) return false;
360 return setVarValue(name, v, scope);
361}
362
363Dialogue::VarValue Dialogue::getVarValue(const std::string &name, const std::string &scope) const {
364 const auto *m = varsForScope(scope);
365 if (!m) return VarValue::string("");
366 const auto it = m->find(name);
367 return it == m->end() ? VarValue::string("") : it->second;
368}
369
370std::string Dialogue::getVarType(const std::string &name, const std::string &scope) const {
371 return hasVar(name, scope) ? getVarValue(name, scope).typeName() : "";
372}
373
374int Dialogue::getVarInt(const std::string &name, int defaultValue, const std::string &scope) const {
375 const VarValue v = getVarValue(name, scope);
376 if (v.type == VarValue::Type::Int) return int(v.i);
377 if (v.type == VarValue::Type::Float) return int(v.f);
378 if (v.type == VarValue::Type::Bool) return v.b ? 1 : 0;
379 return defaultValue;
380}
381
382float Dialogue::getVarFloat(const std::string &name, float defaultValue,
383 const std::string &scope) const {
384 const VarValue v = getVarValue(name, scope);
385 if (v.type == VarValue::Type::Int) return float(v.i);
386 if (v.type == VarValue::Type::Float) return float(v.f);
387 if (v.type == VarValue::Type::Bool) return v.b ? 1.f : 0.f;
388 return defaultValue;
389}
390
391bool Dialogue::getVarBool(const std::string &name, bool defaultValue,
392 const std::string &scope) const {
393 const VarValue v = getVarValue(name, scope);
394 if (v.type == VarValue::Type::Int) return v.i != 0;
395 if (v.type == VarValue::Type::Float) return v.f != 0.0;
396 if (v.type == VarValue::Type::Bool) return v.b;
397 return defaultValue;
398}
399
400std::string Dialogue::getVarString(const std::string &name, const std::string &defaultValue,
401 const std::string &scope) const {
402 const VarValue v = getVarValue(name, scope);
403 return v.type == VarValue::Type::String ? v.s : defaultValue;
404}
405
406bool Dialogue::hasVar(const std::string &name, const std::string &scope) const {
407 const auto *m = varsForScope(scope);
408 return m && m->find(name) != m->end();
409}
410
411bool Dialogue::clearVar(const std::string &name, const std::string &scope) {
412 auto *m = varsForScope(scope);
413 return m && m->erase(name) > 0;
414}
415
416void Dialogue::clearVars(const std::string &scope) {
417 if (scope == "global") {
418 globalVars_.clear();
419 } else if (scope == "scene") {
420 sceneVars_.clear();
421 } else if (scope == "all") {
422 globalVars_.clear();
423 sceneVars_.clear();
424 }
425}
426
427// ---------------------------------------------------------------------------
428// Conditions (structured tables + script predicates)
429// ---------------------------------------------------------------------------
430
431bool Dialogue::registerCondition(const std::string &name, ssq::Object fn) {
432 if (!vm_ || name.empty()) return false;
433 HSQOBJECT raw = fn.getRaw();
434 if (raw._type == OT_NULL) return unregisterCondition(name);
435 if (raw._type != OT_CLOSURE && raw._type != OT_NATIVECLOSURE) return false;
436 const auto it = predicates_.find(name);
437 if (it != predicates_.end()) {
438 sq_release(vm_, &it->second);
439 predicates_.erase(it);
440 }
441 sq_addref(vm_, &raw);
442 predicates_[name] = raw;
443 return true;
444}
445
446bool Dialogue::unregisterCondition(const std::string &name) {
447 const auto it = predicates_.find(name);
448 if (it == predicates_.end()) return false;
449 if (vm_) sq_release(vm_, &it->second);
450 predicates_.erase(it);
451 return true;
452}
453
454bool Dialogue::evalCondition(ssq::Object table) {
455 DataValue v;
456 if (!objectToDataValue(vm_, table, v)) return false;
457 return evalConditionData(v);
458}
459
460bool Dialogue::parseCondition(const DataValue &v, Condition &out, std::string &error) {
461 if (v.kind == DataValue::Kind::Array) {
462 out.kind = Condition::Kind::All;
463 for (const auto &item : v.arr) {
464 Condition child;
465 if (!parseCondition(item, child, error)) return false;
466 out.children.push_back(std::move(child));
467 }
468 return true;
469 }
470 if (v.kind != DataValue::Kind::Object) {
471 error = "condition must be a table";
472 return false;
473 }
474 if (const DataValue *script = v.find("script")) {
475 if (script->kind != DataValue::Kind::String) {
476 error = "script condition name must be a string";
477 return false;
478 }
479 out.kind = Condition::Kind::Script;
480 out.script = script->s;
481 return true;
482 }
483 if (const DataValue *all = v.find("all")) {
484 if (all->kind != DataValue::Kind::Array) {
485 error = "all must be an array";
486 return false;
487 }
488 out.kind = Condition::Kind::All;
489 for (const auto &item : all->arr) {
490 Condition child;
491 if (!parseCondition(item, child, error)) return false;
492 out.children.push_back(std::move(child));
493 }
494 return true;
495 }
496 if (const DataValue *any = v.find("any")) {
497 if (any->kind != DataValue::Kind::Array) {
498 error = "any must be an array";
499 return false;
500 }
501 out.kind = Condition::Kind::Any;
502 for (const auto &item : any->arr) {
503 Condition child;
504 if (!parseCondition(item, child, error)) return false;
505 out.children.push_back(std::move(child));
506 }
507 return true;
508 }
509 if (const DataValue *notc = v.find("not")) {
510 out.kind = Condition::Kind::Not;
511 out.children.emplace_back();
512 return parseCondition(*notc, out.children.back(), error);
513 }
514 if (const DataValue *var = v.find("var")) {
515 const DataValue *op = v.find("op");
516 if (var->kind != DataValue::Kind::String || !op ||
517 op->kind != DataValue::Kind::String) {
518 error = "var/op condition requires string var and op";
519 return false;
520 }
521 static const char *kOps[] = {"eq", "ne", "gt", "ge", "lt", "le", "has", "missing"};
522 const std::string opStr = op->s;
523 if (std::find(std::begin(kOps), std::end(kOps), opStr) == std::end(kOps)) {
524 error = "unknown condition op: " + opStr;
525 return false;
526 }
527 out.kind = Condition::Kind::Cmp;
528 out.var = var->s;
529 out.op = opStr;
530 out.value = VarValue::string("");
531 if (const DataValue *val = v.find("value")) {
532 switch (val->kind) {
534 out.value = VarValue::integer(val->i);
535 break;
537 out.value = VarValue::number(val->f);
538 break;
540 out.value = VarValue::boolean(val->b);
541 break;
543 out.value = VarValue::string(val->s);
544 break;
545 default:
546 break;
547 }
548 }
549 return true;
550 }
551 error = "unrecognized condition table";
552 return false;
553}
554
555bool Dialogue::compareEq(const VarValue &a, const VarValue &b) const {
556 if (a.isNumeric() && b.isNumeric()) {
557 const double x = a.type == VarValue::Type::Int ? double(a.i) : a.f;
558 const double y = b.type == VarValue::Type::Int ? double(b.i) : b.f;
559 return x == y;
560 }
561 if (a.type == VarValue::Type::Bool && b.type == VarValue::Type::Bool) return a.b == b.b;
562 if (a.type == VarValue::Type::String && b.type == VarValue::Type::String)
563 return a.s == b.s;
564 return false;
565}
566
567bool Dialogue::compareOrder(const std::string &op, const VarValue &a, const VarValue &b) const {
568 if (!a.isNumeric() || !b.isNumeric()) return false;
569 const double x = a.type == VarValue::Type::Int ? double(a.i) : a.f;
570 const double y = b.type == VarValue::Type::Int ? double(b.i) : b.f;
571 if (op == "gt") return x > y;
572 if (op == "ge") return x >= y;
573 if (op == "lt") return x < y;
574 if (op == "le") return x <= y;
575 return false;
576}
577
578bool Dialogue::evalConditionInternal(const Condition &c,
579 const std::unordered_map<std::string, VarValue> &merged,
580 const std::unordered_map<std::string, VarValue> &params,
581 const std::string &lineId) const {
582 switch (c.kind) {
583 case Condition::Kind::Always:
584 return true;
585 case Condition::Kind::Cmp: {
586 const auto it = merged.find(c.var);
587 const bool present = it != merged.end();
588 if (c.op == "has") return present;
589 if (c.op == "missing") return !present;
590 if (!present) return false;
591 if (c.op == "eq") return compareEq(it->second, c.value);
592 if (c.op == "ne") return !compareEq(it->second, c.value);
593 return compareOrder(c.op, it->second, c.value);
594 }
595 case Condition::Kind::All:
596 for (const auto &child : c.children)
597 if (!evalConditionInternal(child, merged, params, lineId)) return false;
598 return true;
599 case Condition::Kind::Any:
600 for (const auto &child : c.children)
601 if (evalConditionInternal(child, merged, params, lineId)) return true;
602 return false;
603 case Condition::Kind::Not:
604 return c.children.empty() ||
605 !evalConditionInternal(c.children.front(), merged, params, lineId);
606 case Condition::Kind::Script:
607 return evalScriptPredicate(c.script, merged, params, lineId);
608 }
609 return false;
610}
611
613 Condition c;
614 std::string error;
615 if (!parseCondition(cond, c, error)) return false;
616 return evalConditionInternal(c, mergedVars({}), {}, "");
617}
618
619bool Dialogue::evalScriptPredicate(const std::string &name,
620 const std::unordered_map<std::string, VarValue> &merged,
621 const std::unordered_map<std::string, VarValue> &params,
622 const std::string &lineId) const {
623 if (!vm_) return false;
624 const auto it = predicates_.find(name);
625 if (it == predicates_.end()) return false;
626
627 const SQInteger top = sq_gettop(vm_);
628 sq_pushobject(vm_, it->second); // closure first; sq_call expects fn below its args
629 sq_newtable(vm_); // ctx
630
631 sq_pushstring(vm_, "vars", -1);
632 pushVarTable(vm_, merged);
633 sq_newslot(vm_, -3, SQFalse); // fn at -4, key at -2, var table at -1 -> slot into ctx(-3)
634
635 sq_pushstring(vm_, "params", -1);
636 pushVarTable(vm_, params);
637 sq_newslot(vm_, -3, SQFalse);
638
639 sq_pushstring(vm_, "lineId", -1);
640 sq_pushstring(vm_, lineId.c_str(), lineId.size());
641 sq_newslot(vm_, -3, SQFalse);
642
643 if (SQ_FAILED(sq_call(vm_, 1, SQTrue, SQTrue))) {
644 sq_settop(vm_, top);
645 return false;
646 }
647 SQBool result = SQFalse;
648 if (SQ_FAILED(sq_getbool(vm_, -1, &result))) {
649 sq_settop(vm_, top);
650 return false;
651 }
652 sq_settop(vm_, top);
653 return result != 0;
654}
655
656// ---------------------------------------------------------------------------
657// Content pools
658// ---------------------------------------------------------------------------
659
660int Dialogue::loadPoolsFromTable(ssq::Object table) {
661 DataValue root;
662 if (!objectToDataValue(vm_, table, root)) {
663 lastPoolsError_ = "pools root must be a table";
664 return 0;
665 }
666 return loadPoolsFromData(root);
667}
668
669bool Dialogue::parseLineData(const std::string &poolId, const DataValue &v, int index, Line &line,
670 std::string &error) {
671 if (v.kind != DataValue::Kind::Object) {
672 error = "line must be a table";
673 return false;
674 }
675 if (const DataValue *id = v.find("id"))
676 if (id->kind == DataValue::Kind::String) line.id = id->s;
677 if (line.id.empty()) line.id = poolId + "." + std::to_string(index);
678
679 if (const DataValue *sp = v.find("speaker"))
680 if (sp->kind == DataValue::Kind::String) line.speaker = sp->s;
681 if (const DataValue *tx = v.find("text"))
682 if (tx->kind == DataValue::Kind::String) line.text = tx->s;
683 if (const DataValue *ik = v.find("i18n"))
684 if (ik->kind == DataValue::Kind::String) line.i18nKey = ik->s;
685 if (line.text.empty() && line.i18nKey.empty()) {
686 error = "line '" + line.id + "': text or i18n required";
687 return false;
688 }
689
690 if (const DataValue *w = v.find("weight")) {
691 if (w->kind == DataValue::Kind::Int) line.weight = double(w->i);
692 else if (w->kind == DataValue::Kind::Float) line.weight = w->f;
693 else {
694 error = "line '" + line.id + "': weight must be a number";
695 return false;
696 }
697 }
698
699 if (const DataValue *when = v.find("when")) {
700 if (!parseCondition(*when, line.when, error)) {
701 error = "line '" + line.id + "': " + error;
702 return false;
703 }
704 }
705
706 if (const DataValue *meta = v.find("meta")) {
707 if (meta->kind != DataValue::Kind::Object) {
708 error = "line '" + line.id + "': meta must be a table";
709 return false;
710 }
711 for (const auto &mkv : meta->obj) {
712 std::string val;
713 switch (mkv.second.kind) {
715 val = mkv.second.s;
716 break;
718 val = std::to_string(mkv.second.i);
719 break;
721 val = floatToString(mkv.second.f);
722 break;
724 val = mkv.second.b ? "true" : "false";
725 break;
726 default:
727 break;
728 }
729 line.meta[mkv.first] = val;
730 }
731 }
732
733 if (const DataValue *tags = v.find("tags")) {
734 if (tags->kind != DataValue::Kind::Array) {
735 error = "line '" + line.id + "': tags must be an array";
736 return false;
737 }
738 for (const auto &t : tags->arr)
739 if (t.kind == DataValue::Kind::String) line.tags.push_back(t.s);
740 }
741 return true;
742}
743
745 lastPoolsError_.clear();
746 if (root.kind != DataValue::Kind::Object) {
747 lastPoolsError_ = "pools root must be an object";
748 return 0;
749 }
750 const DataValue *pools = root.find("pools");
751 if (!pools || pools->kind != DataValue::Kind::Object) {
752 lastPoolsError_ = "missing pools object";
753 return 0;
754 }
755
756 int registered = 0;
757 for (const auto &kv : pools->obj) {
758 Pool pool;
759 pool.id = kv.first;
760 if (kv.second.kind != DataValue::Kind::Object) {
761 lastPoolsError_ = "pool '" + kv.first + "': expected object";
762 continue;
763 }
764 if (const DataValue *nr = kv.second.find("noRepeat")) {
765 if (nr->kind == DataValue::Kind::Int) pool.noRepeat = int(nr->i);
766 else if (nr->kind == DataValue::Kind::Float) pool.noRepeat = int(nr->f);
767 if (pool.noRepeat < 0) pool.noRepeat = 0;
768 }
769 const DataValue *lines = kv.second.find("lines");
770 if (!lines || lines->kind != DataValue::Kind::Array) {
771 lastPoolsError_ = "pool '" + kv.first + "': missing lines array";
772 continue;
773 }
774 int lineIndex = 1;
775 for (const auto &lv : lines->arr) {
776 Line line;
777 std::string error;
778 if (!parseLineData(pool.id, lv, lineIndex, line, error)) {
779 if (lastPoolsError_.empty()) lastPoolsError_ = error;
780 continue;
781 }
782 pool.lines.push_back(std::move(line));
783 ++lineIndex;
784 }
785 if (Pool *existing = findPool(pool.id)) *existing = std::move(pool);
786 else pools_.push_back(std::move(pool));
787 ++registered;
788 }
789 return registered;
790}
791
792int Dialogue::loadPoolsFromDnut(const std::string &source, const std::string &path) {
793 DataValue root;
794 std::string error;
795 if (!parseDnut(source, path.empty() ? "<dnut>" : path, root, error)) {
796 lastPoolsError_ = error;
797 return 0;
798 }
799 return loadPoolsFromData(root);
800}
801
802int Dialogue::loadPoolsFromDnutFile(const std::string &path) {
803 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
804 if (!fs) fs = eve::filesystem::Filesystem::create();
805 eve::filesystem::FileData *fd = nullptr;
806 try {
807 fd = fs->read(path);
808 } catch (...) {
809 delete fd;
810 lastPoolsError_ = path + ": 读取失败";
811 return 0;
812 }
813 if (fd == nullptr || fd->getData() == nullptr || fd->getSize() == 0) {
814 delete fd;
815 lastPoolsError_ = path + ": 读取失败";
816 return 0;
817 }
818 const std::string text(static_cast<const char *>(fd->getData()), fd->getSize());
819 delete fd;
820 return loadPoolsFromDnut(text, path);
821}
822
823void Dialogue::clearPools() { pools_.clear(); }
824
825int Dialogue::getPoolCount() const { return int(pools_.size()); }
826
827std::string Dialogue::getPoolId(int index) const {
828 if (index < 0 || size_t(index) >= pools_.size()) return {};
829 return pools_[size_t(index)].id;
830}
831
832bool Dialogue::hasPool(const std::string &id) const { return findPool(id) != nullptr; }
833
834Dialogue::Pool *Dialogue::findPool(const std::string &id) {
835 for (auto &p : pools_)
836 if (p.id == id) return &p;
837 return nullptr;
838}
839
840const Dialogue::Pool *Dialogue::findPool(const std::string &id) const {
841 for (const auto &p : pools_)
842 if (p.id == id) return &p;
843 return nullptr;
844}
845
846Dialogue::Line *Dialogue::findLine(const std::string &id) {
847 for (auto &p : pools_)
848 for (auto &l : p.lines)
849 if (l.id == id) return &l;
850 return nullptr;
851}
852
853const Dialogue::Line *Dialogue::findLine(const std::string &id) const {
854 for (const auto &p : pools_)
855 for (const auto &l : p.lines)
856 if (l.id == id) return &l;
857 return nullptr;
858}
859
860// ---------------------------------------------------------------------------
861// Merged variable context / interpolation
862// ---------------------------------------------------------------------------
863
864std::unordered_map<std::string, Dialogue::VarValue> Dialogue::mergedVars(
865 const std::unordered_map<std::string, VarValue> &params) const {
866 std::unordered_map<std::string, VarValue> m = globalVars_;
867 for (const auto &kv : sceneVars_) m[kv.first] = kv.second;
868 for (const auto &kv : params) m[kv.first] = kv.second;
869 return m;
870}
871
872std::unordered_map<std::string, std::string> Dialogue::stringParams(
873 const std::unordered_map<std::string, VarValue> &vars) const {
874 std::unordered_map<std::string, std::string> out;
875 for (const auto &kv : vars) out[kv.first] = kv.second.toString();
876 return out;
877}
878
879std::string Dialogue::interpolate(const std::string &tpl,
880 const std::unordered_map<std::string, VarValue> &vars) const {
881 std::string out;
882 out.reserve(tpl.size());
883 for (size_t i = 0; i < tpl.size();) {
884 if (tpl[i] == '{') {
885 const size_t close = tpl.find('}', i + 1);
886 if (close != std::string::npos) {
887 const std::string name = tpl.substr(i + 1, close - i - 1);
888 const auto it = vars.find(name);
889 if (it != vars.end()) {
890 out += it->second.toString();
891 i = close + 1;
892 continue;
893 }
894 }
895 }
896 out += tpl[i++];
897 }
898 return out;
899}
900
901// ---------------------------------------------------------------------------
902// RNG / weighted selection / play
903// ---------------------------------------------------------------------------
904
905uint32_t Dialogue::nextRandom() {
906 uint32_t x = rngState_;
907 x ^= x << 13;
908 x ^= x >> 17;
909 x ^= x << 5;
910 rngState_ = x;
911 return x;
912}
913
914double Dialogue::nextUnit() { return (nextRandom() & 0xFFFFFFu) / 16777216.0; }
915
917 rngState_ = seed ? uint32_t(seed) : 1u;
918 for (auto &p : pools_) p.recent.clear();
919}
920
921std::string Dialogue::pickLine(const std::string &poolId, ssq::Object params) {
922 std::unordered_map<std::string, VarValue> p;
923 objectToVarParams(vm_, params, p);
924 return pickLineWithParams(poolId, p);
925}
926
928 const std::string &poolId, const std::unordered_map<std::string, VarValue> &params) {
929 Pool *pool = findPool(poolId);
930 if (!pool) return "";
931 const auto merged = mergedVars(params);
932
933 auto collect = [&](std::vector<size_t> &out, bool allowRecent) {
934 for (size_t i = 0; i < pool->lines.size(); ++i) {
935 const Line &line = pool->lines[i];
936 if (line.weight <= 0.0) continue;
937 if (!evalConditionInternal(line.when, merged, params, line.id)) continue;
938 if (!allowRecent && pool->noRepeat > 0 &&
939 std::find(pool->recent.begin(), pool->recent.end(), i) != pool->recent.end())
940 continue;
941 out.push_back(i);
942 }
943 };
944
945 std::vector<size_t> candidates;
946 collect(candidates, false);
947 if (candidates.empty()) collect(candidates, true); // noRepeat must not deadlock
948 if (candidates.empty()) return "";
949
950 double total = 0.0;
951 for (const size_t i : candidates) total += pool->lines[i].weight;
952 double roll = nextUnit() * total;
953 size_t picked = candidates.front();
954 double acc = 0.0;
955 for (const size_t i : candidates) {
956 acc += pool->lines[i].weight;
957 if (roll < acc) {
958 picked = i;
959 break;
960 }
961 }
962 if (pool->noRepeat > 0) {
963 pool->recent.push_back(picked);
964 if (pool->recent.size() > size_t(pool->noRepeat)) pool->recent.erase(pool->recent.begin());
965 }
966 return pool->lines[picked].id;
967}
968
969bool Dialogue::playLine(const std::string &lineId, ssq::Object params) {
970 std::unordered_map<std::string, VarValue> p;
971 objectToVarParams(vm_, params, p);
972 return playLineWithParams(lineId, p);
973}
974
975bool Dialogue::playLineWithParams(const std::string &lineId,
976 const std::unordered_map<std::string, VarValue> &params) {
977 Line *line = findLine(lineId);
978 if (!line) return false;
979 const auto merged = mergedVars(params);
980
981 std::string text;
982 if (!line->text.empty()) {
983 text = interpolate(line->text, merged);
984 } else {
985 auto *i18n = eve::ModuleManager::getInstance<eve::i18n::I18n>("I18n");
986 text = i18n ? i18n->getWithParams(line->i18nKey, stringParams(merged)) : line->i18nKey;
987 }
988
989 if (line->speaker.empty()) narrate(text);
990 else say(line->speaker, text);
991
992 currentLineId_ = line->id;
993 currentLineMeta_ = line->meta;
994 currentLineTags_ = line->tags;
995 applyLineMeta(*line);
996 return true;
997}
998
999bool Dialogue::playPool(const std::string &poolId, ssq::Object params) {
1000 std::unordered_map<std::string, VarValue> p;
1001 objectToVarParams(vm_, params, p);
1002 return playPoolWithParams(poolId, p);
1003}
1004
1005bool Dialogue::playPoolWithParams(const std::string &poolId,
1006 const std::unordered_map<std::string, VarValue> &params) {
1007 const std::string lineId = pickLineWithParams(poolId, params);
1008 return !lineId.empty() && playLineWithParams(lineId, params);
1009}
1010
1011std::string Dialogue::getCurrentLineMeta(const std::string &field) const {
1012 const auto it = currentLineMeta_.find(field);
1013 return it == currentLineMeta_.end() ? std::string{} : it->second;
1014}
1015
1016std::vector<std::string> Dialogue::getCurrentLineTags() const { return currentLineTags_; }
1017
1018void Dialogue::applyLineMeta(const Line &line) {
1019 if (line.speaker.empty()) return;
1020 const auto expr = line.meta.find("expression");
1021 const auto motion = line.meta.find("motion");
1022 if (expr == line.meta.end() && motion == line.meta.end()) return;
1023 Character *c = findCharacter(line.speaker);
1024 if (!c || !c->avatar) return;
1025 if (expr != line.meta.end()) c->avatar->setExpression(expr->second);
1026 if (motion != line.meta.end()) c->avatar->setMotion(motion->second);
1027}
1028
1029// ---------------------------------------------------------------------------
1030// Scene-scoped variables: clear when the selected Scene host changes
1031// ---------------------------------------------------------------------------
1032
1033void Dialogue::pollSceneChange() {
1034 auto *scn = eve::ModuleManager::getInstance<eve::scene::Scene>("Scene");
1035 if (!scn) return;
1036 eve::scene::SceneHost *host = scn->current();
1037 const std::string name = host ? host->getName() : "";
1038 if (!name.empty() && name != lastSceneName_) sceneVars_.clear();
1039 if (!name.empty()) lastSceneName_ = name;
1040}
1041
1042Dialogue::Character *Dialogue::findCharacter(const std::string &id) {
1043 for (auto &c : characters_)
1044 if (c.id == id) return &c;
1045 return nullptr;
1046}
1047
1048const Dialogue::Character *Dialogue::findCharacter(const std::string &id) const {
1049 for (const auto &c : characters_)
1050 if (c.id == id) return &c;
1051 return nullptr;
1052}
1053
1054bool Dialogue::registerCharacter(const std::string &id, const std::string &displayName) {
1055 if (id.empty()) return false;
1056 if (auto *c = findCharacter(id)) {
1057 c->displayName = displayName.empty() ? id : displayName;
1058 return true;
1059 }
1060 Character c;
1061 c.id = id;
1062 c.displayName = displayName.empty() ? id : displayName;
1063 characters_.push_back(c);
1064 return true;
1065}
1066
1067bool Dialogue::hasCharacter(const std::string &id) const { return findCharacter(id) != nullptr; }
1068
1069std::string Dialogue::getDisplayName(const std::string &id) const {
1070 const Character *c = findCharacter(id);
1071 return c ? c->displayName : std::string{};
1072}
1073
1074bool Dialogue::bindAvatar(const std::string &id, avatar::AvatarInstance *av) {
1075 Character *c = findCharacter(id);
1076 if (!c) return false;
1077 c->avatar = av;
1078 return true;
1079}
1080
1081avatar::AvatarInstance *Dialogue::getAvatar(const std::string &id) const {
1082 const Character *c = findCharacter(id);
1083 return c ? c->avatar : nullptr;
1084}
1085
1086int Dialogue::getCharacterCount() const { return int(characters_.size()); }
1087
1088std::string Dialogue::getCharacterId(int index) const {
1089 if (index < 0 || size_t(index) >= characters_.size()) return {};
1090 return characters_[size_t(index)].id;
1091}
1092
1093bool Dialogue::show(const std::string &id, const std::string &slot) {
1094 Character *c = findCharacter(id);
1095 if (!c) return false;
1096 c->shown = true;
1097 c->slot = slot.empty() ? "center" : slot;
1098 if (c->avatar) c->avatar->setVisible(true);
1099 return true;
1100}
1101
1102bool Dialogue::hide(const std::string &id) {
1103 Character *c = findCharacter(id);
1104 if (!c) return false;
1105 c->shown = false;
1106 if (c->avatar) c->avatar->setVisible(false);
1107 return true;
1108}
1109
1110bool Dialogue::isShown(const std::string &id) const {
1111 const Character *c = findCharacter(id);
1112 return c && c->shown;
1113}
1114
1115std::string Dialogue::getSlot(const std::string &id) const {
1116 const Character *c = findCharacter(id);
1117 return c ? c->slot : std::string{};
1118}
1119
1120void Dialogue::setSlotX(const std::string &slot, float xNorm) {
1121 if (slot.empty()) return;
1122 slotX_[slot] = xNorm;
1123}
1124
1125float Dialogue::getSlotX(const std::string &slot) const {
1126 auto it = slotX_.find(slot);
1127 if (it != slotX_.end()) return it->second;
1128 if (slot == "left") return 0.25f;
1129 if (slot == "right") return 0.75f;
1130 if (slot == "center") return 0.5f;
1131 return 0.5f;
1132}
1133
1134bool Dialogue::setExpression(const std::string &id, const std::string &expression) {
1135 Character *c = findCharacter(id);
1136 if (!c || !c->avatar) return false;
1137 c->avatar->setExpression(expression);
1138 return true;
1139}
1140
1141bool Dialogue::setMotion(const std::string &id, const std::string &motion) {
1142 Character *c = findCharacter(id);
1143 if (!c || !c->avatar) return false;
1144 c->avatar->setMotion(motion);
1145 return true;
1146}
1147
1148void Dialogue::syncStage(float stageWidth, float stageHeight) {
1149 (void)stageHeight;
1150 for (Character &c : characters_) {
1151 if (!c.avatar) continue;
1152 c.avatar->setVisible(c.shown);
1153 if (!c.shown) {
1154 c.avatar->sync();
1155 continue;
1156 }
1157 const float xn = getSlotX(c.slot);
1158 const float x = xn * stageWidth;
1159 // Keep current Y; only drive X from slot.
1160 c.avatar->setPosition(x, c.avatar->getY());
1161 c.avatar->sync();
1162 }
1163}
1164
1165void Dialogue::beginLine(const std::string &speakerId, const std::string &text) {
1166 speakerId_ = speakerId;
1167 fullText_ = text;
1168 typed_ = 0.f;
1169 selectedChoiceId_.clear();
1170 lipSyncTime_ = 0.f;
1171 if (typeSpeed_ <= 0.f) {
1172 typed_ = float(utf8_codepoint_count(fullText_));
1173 phase_ = Phase::WaitingAdvance;
1174 lipSyncValue_ = 0.f;
1175 } else {
1176 phase_ = Phase::Typing;
1177 }
1178}
1179
1180void Dialogue::say(const std::string &speakerId, const std::string &text) {
1181 beginLine(speakerId, text);
1182}
1183
1184void Dialogue::narrate(const std::string &text) { beginLine("", text); }
1185
1186void Dialogue::setTypeSpeed(float charsPerSecond) { typeSpeed_ = charsPerSecond; }
1187
1189 if (phase_ == Phase::Typing) {
1190 typed_ = float(utf8_codepoint_count(fullText_));
1191 phase_ = Phase::WaitingAdvance;
1192 }
1193}
1194
1195bool Dialogue::isTyping() const { return phase_ == Phase::Typing; }
1196
1197bool Dialogue::isWaitingAdvance() const { return phase_ == Phase::WaitingAdvance; }
1198
1199bool Dialogue::isIdle() const { return phase_ == Phase::Idle; }
1200
1202 if (phase_ == Phase::Typing) {
1203 skipTyping();
1204 return;
1205 }
1206 if (phase_ == Phase::WaitingAdvance) phase_ = Phase::Idle;
1207}
1208
1209std::string Dialogue::getSpeakerName() const {
1210 if (speakerId_.empty()) return {};
1211 return getDisplayName(speakerId_);
1212}
1213
1214std::string Dialogue::getVisibleText() const {
1215 if (fullText_.empty()) return {};
1216 const size_t total = utf8_codepoint_count(fullText_);
1217 size_t n = size_t(std::floor(typed_ + 1e-4f));
1218 if (n == 0) return {};
1219 if (n >= total) return fullText_;
1220 return fullText_.substr(0, utf8_byte_offset_for_codepoints(fullText_, n));
1221}
1222
1223std::string Dialogue::getPhase() const {
1224 switch (phase_) {
1225 case Phase::Idle:
1226 return "idle";
1227 case Phase::Typing:
1228 return "typing";
1229 case Phase::WaitingAdvance:
1230 return "waiting_advance";
1231 case Phase::WaitingChoice:
1232 return "waiting_choice";
1233 }
1234 return "idle";
1235}
1236
1238 choices_.clear();
1239 selectedChoiceId_.clear();
1240}
1241
1242bool Dialogue::addChoice(const std::string &id, const std::string &label) {
1243 if (id.empty()) return false;
1244 for (auto &ch : choices_) {
1245 if (ch.id == id) {
1246 ch.label = label;
1247 return true;
1248 }
1249 }
1250 choices_.push_back(Choice{id, label});
1251 return true;
1252}
1253
1255 if (choices_.empty()) {
1256 phase_ = Phase::Idle;
1257 return;
1258 }
1259 if (phase_ == Phase::Typing) skipTyping();
1260 phase_ = Phase::WaitingChoice;
1261}
1262
1263bool Dialogue::isWaitingChoice() const { return phase_ == Phase::WaitingChoice; }
1264
1265int Dialogue::getChoiceCount() const { return int(choices_.size()); }
1266
1267std::string Dialogue::getChoiceId(int index) const {
1268 if (index < 0 || size_t(index) >= choices_.size()) return {};
1269 return choices_[size_t(index)].id;
1270}
1271
1272std::string Dialogue::getChoiceLabel(int index) const {
1273 if (index < 0 || size_t(index) >= choices_.size()) return {};
1274 return choices_[size_t(index)].label;
1275}
1276
1277bool Dialogue::selectChoice(int index) {
1278 if (phase_ != Phase::WaitingChoice) return false;
1279 if (index < 0 || size_t(index) >= choices_.size()) return false;
1280 selectedChoiceId_ = choices_[size_t(index)].id;
1281 phase_ = Phase::Idle;
1282 return true;
1283}
1284
1285void Dialogue::setLipSyncEnabled(bool enabled) { lipSyncEnabled_ = enabled; }
1286
1287void Dialogue::setLipSyncParameter(const std::string &name) {
1288 if (!name.empty()) lipSyncParameter_ = name;
1289}
1290
1291void Dialogue::setLipSyncAmplitude(float amplitude) {
1292 if (amplitude < 0.f) amplitude = 0.f;
1293 if (amplitude > 2.f) amplitude = 2.f;
1294 lipSyncAmplitude_ = amplitude;
1295}
1296
1297void Dialogue::updateLipSync(float dt) {
1298 if (dt < 0.f) dt = 0.f;
1299 if (lipSyncEnabled_ && phase_ == Phase::Typing && !speakerId_.empty()) {
1300 lipSyncTime_ += dt;
1301 // Simple mouth envelope while characters appear (no audio dependency).
1302 const float wave = std::fabs(std::sin(lipSyncTime_ * 14.f));
1303 lipSyncValue_ = lipSyncAmplitude_ * (0.25f + 0.75f * wave);
1304 } else {
1305 // Ease shut when not typing.
1306 lipSyncValue_ *= std::max(0.f, 1.f - dt * 8.f);
1307 if (lipSyncValue_ < 0.01f) lipSyncValue_ = 0.f;
1308 }
1309 applyLipSyncToSpeaker();
1310}
1311
1312void Dialogue::applyLipSyncToSpeaker() {
1313 if (!lipSyncEnabled_ || speakerId_.empty() || lipSyncParameter_.empty()) return;
1314 Character *c = findCharacter(speakerId_);
1315 if (!c || !c->avatar) return;
1316 c->avatar->setParameter(lipSyncParameter_, lipSyncValue_);
1317}
1318
1319void Dialogue::update(float dt) {
1320 if (dt < 0.f) dt = 0.f;
1321 if (phase_ == Phase::Typing) {
1322 const float total = float(utf8_codepoint_count(fullText_));
1323 typed_ += typeSpeed_ * dt;
1324 if (typed_ >= total) {
1325 typed_ = total;
1326 phase_ = Phase::WaitingAdvance;
1327 }
1328 }
1329 updateLipSync(dt);
1330 pollSceneChange();
1331}
1332
1334 phase_ = Phase::Idle;
1335 speakerId_.clear();
1336 fullText_.clear();
1337 typed_ = 0.f;
1338 choices_.clear();
1339 selectedChoiceId_.clear();
1340 lipSyncValue_ = 0.f;
1341 lipSyncTime_ = 0.f;
1342 currentLineId_.clear();
1343 currentLineMeta_.clear();
1344 currentLineTags_.clear();
1345 for (Character &c : characters_) {
1346 c.shown = false;
1347 if (c.avatar) c.avatar->setVisible(false);
1348 }
1349}
1350
1352 out = StateValue::object();
1353 out.set("phase", StateValue::string(getPhase()));
1354 out.set("speakerId", StateValue::string(speakerId_));
1355 out.set("fullText", StateValue::string(fullText_));
1356 out.set("typed", StateValue::number(typed_));
1357 out.set("typeSpeed", StateValue::number(typeSpeed_));
1358 out.set("lipSyncEnabled", StateValue::boolean(lipSyncEnabled_));
1359 out.set("lipSyncParameter", StateValue::string(lipSyncParameter_));
1360 out.set("lipSyncAmplitude", StateValue::number(lipSyncAmplitude_));
1361 out.set("rngState", StateValue::integer(static_cast<int64_t>(rngState_)));
1362 out.set("currentLineId", StateValue::string(currentLineId_));
1363 out.set("selectedChoiceId", StateValue::string(selectedChoiceId_));
1364
1365 StateValue global = StateValue::object();
1366 for (const auto& kv : globalVars_) global.set(kv.first, varValueToState(kv.second));
1367 out.set("globalVars", std::move(global));
1368
1370 for (const auto& kv : sceneVars_) scene.set(kv.first, varValueToState(kv.second));
1371 out.set("sceneVars", std::move(scene));
1372
1373 StateValue choices = StateValue::array();
1374 for (const auto& c : choices_) {
1376 item.set("id", StateValue::string(c.id));
1377 item.set("label", StateValue::string(c.label));
1378 choices.pushBack(std::move(item));
1379 }
1380 out.set("choices", std::move(choices));
1381
1382 StateValue chars = StateValue::array();
1383 for (const auto& c : characters_) {
1385 item.set("id", StateValue::string(c.id));
1386 item.set("displayName", StateValue::string(c.displayName));
1387 item.set("slot", StateValue::string(c.slot));
1388 item.set("shown", StateValue::boolean(c.shown));
1389 chars.pushBack(std::move(item));
1390 }
1391 out.set("characters", std::move(chars));
1392
1394 for (const auto& kv : slotX_) slotX.set(kv.first, StateValue::number(kv.second));
1395 out.set("slotX", std::move(slotX));
1396 return true;
1397}
1398
1399bool Dialogue::restoreState(const StateValue& in, std::string* err) {
1400 if (!in.isObject()) {
1401 if (err) *err = "dialogue: state is not an object";
1402 return false;
1403 }
1404 const StateValue* phase = in.find("phase");
1405 if (!phase || !phase->isString()) {
1406 if (err) *err = "dialogue: missing phase";
1407 return false;
1408 }
1409 const std::string phaseName = phase->asString();
1410 if (phaseName == "idle")
1411 phase_ = Phase::Idle;
1412 else if (phaseName == "typing")
1413 phase_ = Phase::Typing;
1414 else if (phaseName == "waiting_advance")
1415 phase_ = Phase::WaitingAdvance;
1416 else if (phaseName == "waiting_choice")
1417 phase_ = Phase::WaitingChoice;
1418 else {
1419 if (err) *err = "dialogue: unknown phase '" + phaseName + "'";
1420 return false;
1421 }
1422
1423 if (const StateValue* v = in.find("speakerId"); v && v->isString()) speakerId_ = v->asString();
1424 if (const StateValue* v = in.find("fullText"); v && v->isString()) fullText_ = v->asString();
1425 if (const StateValue* v = in.find("typed"); v && (v->isInt() || v->isFloat()))
1426 typed_ = static_cast<float>(v->isInt() ? double(v->asInt()) : v->asDouble());
1427 if (const StateValue* v = in.find("typeSpeed"); v && (v->isInt() || v->isFloat()))
1428 typeSpeed_ = static_cast<float>(v->isInt() ? double(v->asInt()) : v->asDouble());
1429 if (const StateValue* v = in.find("lipSyncEnabled"); v && v->isBool()) lipSyncEnabled_ = v->asBool();
1430 if (const StateValue* v = in.find("lipSyncParameter"); v && v->isString()) lipSyncParameter_ = v->asString();
1431 if (const StateValue* v = in.find("lipSyncAmplitude"); v && (v->isInt() || v->isFloat()))
1432 lipSyncAmplitude_ = static_cast<float>(v->isInt() ? double(v->asInt()) : v->asDouble());
1433 if (const StateValue* v = in.find("rngState"); v && v->isInt()) rngState_ = static_cast<uint32_t>(v->asInt());
1434 if (const StateValue* v = in.find("currentLineId"); v && v->isString()) currentLineId_ = v->asString();
1435 if (const StateValue* v = in.find("selectedChoiceId"); v && v->isString()) selectedChoiceId_ = v->asString();
1436
1437 globalVars_.clear();
1438 if (const StateValue* vars = in.find("globalVars"); vars && vars->isObject()) {
1439 for (const auto& key : vars->keys()) {
1441 if (stateToVarValue(*vars->find(key), val)) globalVars_[key] = val;
1442 }
1443 }
1444 sceneVars_.clear();
1445 if (const StateValue* vars = in.find("sceneVars"); vars && vars->isObject()) {
1446 for (const auto& key : vars->keys()) {
1448 if (stateToVarValue(*vars->find(key), val)) sceneVars_[key] = val;
1449 }
1450 }
1451
1452 choices_.clear();
1453 if (const StateValue *choices = in.find("choices"); choices && choices->isArray()) {
1454 for (size_t i = 0; i < choices->arraySize(); ++i) {
1455 const StateValue &item = choices->at(i);
1456 const StateValue *id = item.find("id");
1457 const StateValue *label = item.find("label");
1458 if (id && id->isString() && label && label->isString()) {
1459 choices_.push_back(Choice{id->asString(), label->asString()});
1460 }
1461 }
1462 }
1463
1464 if (const StateValue *chars = in.find("characters"); chars && chars->isArray()) {
1465 for (size_t i = 0; i < chars->arraySize(); ++i) {
1466 const StateValue &item = chars->at(i);
1467 const StateValue *id = item.find("id");
1468 if (!id || !id->isString()) continue;
1469 Character *c = findCharacter(id->asString());
1470 if (!c) {
1471 characters_.push_back(Character{});
1472 c = &characters_.back();
1473 c->id = id->asString();
1474 if (const StateValue *name = item.find("displayName"); name && name->isString())
1475 c->displayName = name->asString();
1476 }
1477 if (const StateValue *slot = item.find("slot"); slot && slot->isString())
1478 c->slot = slot->asString();
1479 if (const StateValue *shown = item.find("shown"); shown && shown->isBool()) {
1480 c->shown = shown->asBool();
1481 if (c->avatar) c->avatar->setVisible(c->shown);
1482 }
1483 }
1484 }
1485
1486 slotX_.clear();
1487 if (const StateValue *slots = in.find("slotX"); slots && slots->isObject()) {
1488 for (const auto &key : slots->keys()) {
1489 const StateValue *v = slots->find(key);
1490 if (v && (v->isInt() || v->isFloat()))
1491 slotX_[key] = static_cast<float>(v->isInt() ? double(v->asInt()) : v->asDouble());
1492 }
1493 }
1494 return true;
1495}
1496
1498 reset();
1499 return true;
1500}
1501
1502void Dialogue::expose(ssq::Table &table) {
1503 if (Dialogue *self = Dialogue::create()) self->vm_ = table.getHandle();
1504 auto cls = table.addClass(name, Dialogue::create, false);
1505 expose(cls);
1506}
1507
1508void Dialogue::expose(ssq::Class &cls) {
1509 cls.addFunc("getName", &Dialogue::getName);
1510 cls.addFunc("registerCharacter", &Dialogue::registerCharacter);
1511 cls.addFunc("hasCharacter", &Dialogue::hasCharacter);
1512 cls.addFunc("getDisplayName", &Dialogue::getDisplayName);
1513 cls.addFunc("bindAvatar", &Dialogue::bindAvatar);
1514 cls.addFunc("getAvatar", &Dialogue::getAvatar);
1515 cls.addFunc("getCharacterCount", &Dialogue::getCharacterCount);
1516 cls.addFunc("getCharacterId", &Dialogue::getCharacterId);
1517
1518 cls.addFunc("show", &Dialogue::show);
1519 cls.addFunc("hide", &Dialogue::hide);
1520 cls.addFunc("isShown", &Dialogue::isShown);
1521 cls.addFunc("getSlot", &Dialogue::getSlot);
1522 cls.addFunc("setSlotX", &Dialogue::setSlotX);
1523 cls.addFunc("getSlotX", &Dialogue::getSlotX);
1524 cls.addFunc("setExpression", &Dialogue::setExpression);
1525 cls.addFunc("setMotion", &Dialogue::setMotion);
1526 cls.addFunc("syncStage", &Dialogue::syncStage);
1527
1528 cls.addFunc("say", &Dialogue::say);
1529 cls.addFunc("narrate", &Dialogue::narrate);
1530 cls.addFunc("setTypeSpeed", &Dialogue::setTypeSpeed);
1531 cls.addFunc("getTypeSpeed", &Dialogue::getTypeSpeed);
1532 cls.addFunc("skipTyping", &Dialogue::skipTyping);
1533 cls.addFunc("isTyping", &Dialogue::isTyping);
1534 cls.addFunc("isWaitingAdvance", &Dialogue::isWaitingAdvance);
1535 cls.addFunc("isIdle", &Dialogue::isIdle);
1536 cls.addFunc("advance", &Dialogue::advance);
1537 cls.addFunc("getSpeakerId", &Dialogue::getSpeakerId);
1538 cls.addFunc("getSpeakerName", &Dialogue::getSpeakerName);
1539 cls.addFunc("getFullText", &Dialogue::getFullText);
1540 cls.addFunc("getVisibleText", &Dialogue::getVisibleText);
1541 cls.addFunc("getPhase", &Dialogue::getPhase);
1542
1543 cls.addFunc("setLipSyncEnabled", &Dialogue::setLipSyncEnabled);
1544 cls.addFunc("isLipSyncEnabled", &Dialogue::isLipSyncEnabled);
1545 cls.addFunc("setLipSyncParameter", &Dialogue::setLipSyncParameter);
1546 cls.addFunc("getLipSyncParameter", &Dialogue::getLipSyncParameter);
1547 cls.addFunc("setLipSyncAmplitude", &Dialogue::setLipSyncAmplitude);
1548 cls.addFunc("getLipSyncAmplitude", &Dialogue::getLipSyncAmplitude);
1549 cls.addFunc("getLipSyncValue", &Dialogue::getLipSyncValue);
1550
1551 cls.addFunc("clearChoices", &Dialogue::clearChoices);
1552 cls.addFunc("addChoice", &Dialogue::addChoice);
1553 cls.addFunc("presentChoices", &Dialogue::presentChoices);
1554 cls.addFunc("isWaitingChoice", &Dialogue::isWaitingChoice);
1555 cls.addFunc("getChoiceCount", &Dialogue::getChoiceCount);
1556 cls.addFunc("getChoiceId", &Dialogue::getChoiceId);
1557 cls.addFunc("getChoiceLabel", &Dialogue::getChoiceLabel);
1558 cls.addFunc("selectChoice", &Dialogue::selectChoice);
1559 cls.addFunc("getSelectedChoiceId", &Dialogue::getSelectedChoiceId);
1560
1561 cls.addFunc("setVar", &Dialogue::setVar);
1562 cls.addFunc("getVarType", &Dialogue::getVarType);
1563 cls.addFunc("getVarInt", &Dialogue::getVarInt);
1564 cls.addFunc("getVarFloat", &Dialogue::getVarFloat);
1565 cls.addFunc("getVarBool", &Dialogue::getVarBool);
1566 cls.addFunc("getVarString", &Dialogue::getVarString);
1567 cls.addFunc("hasVar", &Dialogue::hasVar);
1568 cls.addFunc("clearVar", &Dialogue::clearVar);
1569 cls.addFunc("clearVars", &Dialogue::clearVars);
1570
1571 cls.addFunc("registerCondition", &Dialogue::registerCondition);
1572 cls.addFunc("unregisterCondition", &Dialogue::unregisterCondition);
1573 cls.addFunc("evalCondition", &Dialogue::evalCondition);
1574
1575 cls.addFunc("loadPoolsFromTable", &Dialogue::loadPoolsFromTable);
1576 cls.addFunc("loadPoolsFromDnut", &Dialogue::loadPoolsFromDnut);
1577 cls.addFunc("loadPoolsFromDnutFile", &Dialogue::loadPoolsFromDnutFile);
1578 cls.addFunc("clearPools", &Dialogue::clearPools);
1579 cls.addFunc("getPoolCount", &Dialogue::getPoolCount);
1580 cls.addFunc("getPoolId", &Dialogue::getPoolId);
1581 cls.addFunc("hasPool", &Dialogue::hasPool);
1582 cls.addFunc("getLastPoolsError", &Dialogue::getLastPoolsError);
1583
1584 cls.addFunc("setRandomSeed", &Dialogue::setRandomSeed);
1585 cls.addFunc("getRandomSeed", &Dialogue::getRandomSeed);
1586
1587 cls.addFunc("pickLine", &Dialogue::pickLine);
1588 cls.addFunc("playLine", &Dialogue::playLine);
1589 cls.addFunc("playPool", &Dialogue::playPool);
1590 cls.addFunc("getCurrentLineId", &Dialogue::getCurrentLineId);
1591 cls.addFunc("getCurrentLineMeta", &Dialogue::getCurrentLineMeta);
1592 cls.addFunc("getCurrentLineTags", &Dialogue::getCurrentLineTags);
1593
1594 cls.addFunc("update", &Dialogue::update);
1595 cls.addFunc("reset", &Dialogue::reset);
1596}
1597
1598} // namespace eve::dialogue
uint32_t seed
struct SQVM * HSQUIRRELVM
int line
Tok kind
std::string value
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::string type
std::string id
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int w
std::string error
JobScope scope
std::vector< std::string > vars
uint32_t a
uint32_t b
uint32_t c
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:24
int idx
float f
glm::vec4 p[6]
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
bool enabled
int d
int v
int children
Definition TreeMesh.cpp:177
float m[16]
uint32_t s
Definition Weather.cpp:28
virtual std::string getName() const =0
JSON-compatible state value tree used by state hot reload.
Definition StateValue.h:20
static StateValue object()
Empty object.
Definition StateValue.h:39
static StateValue boolean(bool v)
Boolean value.
static StateValue array()
Empty array.
Definition StateValue.h:37
static StateValue integer(int64_t v)
Integer value.
void pushBack(StateValue v)
Append an element; only valid on arrays.
bool isString() const
Definition StateValue.h:48
bool isBool() const
Definition StateValue.h:47
bool isObject() const
Definition StateValue.h:49
bool isArray() const
Definition StateValue.h:50
const std::string & asString() const
String payload; only valid when kind() == Kind::String.
Definition StateValue.h:59
static StateValue number(double v)
Floating-point value.
const StateValue * find(const std::string &key) const
Look up key; nullptr when absent.
static StateValue null()
Null value.
Definition StateValue.h:27
StateValue & at(size_t index)
Indexed element (bounds-checked); only valid on arrays.
Definition StateValue.h:66
bool isInt() const
Definition StateValue.h:45
static StateValue string(std::string v)
String value.
void set(const std::string &key, StateValue v)
Insert or replace key; only valid on objects.
Unified avatar instance. Kind is a string: "image" | "live2d" | "vroid". Script-facing API avoids ove...
Runtime-state serialization for state hot reload.
Visual-novel style dialogue stage. Script: dlg <- eve.Dialogue();
Definition Dialogue.h:92
std::string pickLineWithParams(const std::string &poolId, const std::unordered_map< std::string, VarValue > &params)
Definition Dialogue.cpp:927
bool setExpression(const std::string &id, const std::string &expression)
bool setMotion(const std::string &id, const std::string &motion)
std::string getSpeakerName() const
void reset()
重置舞台与台词状态。
std::string getLastPoolsError() const
Definition Dialogue.h:209
float getLipSyncAmplitude() const
Definition Dialogue.h:142
bool clearVar(const std::string &name, const std::string &scope)
Definition Dialogue.cpp:411
bool playLineWithParams(const std::string &lineId, const std::unordered_map< std::string, VarValue > &params)
Definition Dialogue.cpp:975
std::string getSpeakerId() const
Definition Dialogue.h:130
int getCharacterCount() const
bool captureState(StateValue &out) const
Serialize conversation state (vars, rng, phase, choices, stage).
bool restoreState(const StateValue &in, std::string *err=nullptr)
Restore conversation state captured by captureState().
std::string getCurrentLineMeta(const std::string &field) const
float getSlotX(const std::string &slot) const
int loadPoolsFromDnutFile(const std::string &path)
Definition Dialogue.cpp:802
bool evalCondition(ssq::Object table)
Definition Dialogue.cpp:454
std::string getCurrentLineId() const
Definition Dialogue.h:226
std::string getFullText() const
Definition Dialogue.h:132
bool getVarBool(const std::string &name, bool defaultValue, const std::string &scope) const
Definition Dialogue.cpp:391
bool registerCharacter(const std::string &id, const std::string &displayName)
角色:注册 / 查询 / 绑定 Avatar。
bool playPool(const std::string &poolId, ssq::Object params)
Definition Dialogue.cpp:999
std::vector< std::string > getCurrentLineTags() const
bool playLine(const std::string &lineId, ssq::Object params)
Definition Dialogue.cpp:969
void narrate(const std::string &text)
int loadPoolsFromData(const DataValue &root)
Definition Dialogue.cpp:744
void clearChoices()
选项:清空/添加/展示与选择。
int loadPoolsFromDnut(const std::string &source, const std::string &path)
Definition Dialogue.cpp:792
bool setVar(const std::string &name, ssq::Object value, const std::string &scope)
Definition Dialogue.cpp:352
bool selectChoice(int index)
bool hasCharacter(const std::string &id) const
std::string getCharacterId(int index) const
void setLipSyncParameter(const std::string &name)
bool addChoice(const std::string &id, const std::string &label)
VarValue getVarValue(const std::string &name, const std::string &scope) const
Definition Dialogue.cpp:363
bool show(const std::string &id, const std::string &slot)
舞台:显示/隐藏角色、槽位与表情/动作。
void setTypeSpeed(float charsPerSecond)
float getVarFloat(const std::string &name, float defaultValue, const std::string &scope) const
Definition Dialogue.cpp:382
int loadPoolsFromTable(ssq::Object table)
Definition Dialogue.cpp:660
bool hasPool(const std::string &id) const
Definition Dialogue.cpp:832
std::string getChoiceId(int index) const
avatar::AvatarInstance * getAvatar(const std::string &id) const
bool playPoolWithParams(const std::string &poolId, const std::unordered_map< std::string, VarValue > &params)
bool unregisterCondition(const std::string &name)
Definition Dialogue.cpp:446
void update(float dt)
推进打字机 / 口型同步 / 阶段机;每帧调用。
void say(const std::string &speakerId, const std::string &text)
台词:说话/旁白、打字机效果与推进。
bool evalConditionData(const DataValue &cond)
Definition Dialogue.cpp:612
bool isWaitingChoice() const
float getLipSyncValue() const
Definition Dialogue.h:143
std::string pickLine(const std::string &poolId, ssq::Object params)
Definition Dialogue.cpp:921
std::string getChoiceLabel(int index) const
bool bindAvatar(const std::string &id, avatar::AvatarInstance *av)
void setRandomSeed(int seed)
Definition Dialogue.cpp:916
bool resetToDefaults()
Reset stage and line state (restore fallback).
std::string getVarType(const std::string &name, const std::string &scope) const
Definition Dialogue.cpp:370
void setSlotX(const std::string &slot, float xNorm)
bool registerCondition(const std::string &name, ssq::Object fn)
Definition Dialogue.cpp:431
std::string getVarString(const std::string &name, const std::string &defaultValue, const std::string &scope) const
Definition Dialogue.cpp:400
int getRandomSeed() const
Definition Dialogue.h:214
bool isShown(const std::string &id) const
std::string getLipSyncParameter() const
Definition Dialogue.h:140
void syncStage(float stageWidth, float stageHeight)
bool setVarValue(const std::string &name, const VarValue &value, const std::string &scope)
Definition Dialogue.cpp:344
std::string getSlot(const std::string &id) const
void setLipSyncEnabled(bool enabled)
口型同步:打字时驱动说话者 Avatar 参数。
std::string getSelectedChoiceId() const
Definition Dialogue.h:154
int getVarInt(const std::string &name, int defaultValue, const std::string &scope) const
Definition Dialogue.cpp:374
void setLipSyncAmplitude(float amplitude)
std::string getPoolId(int index) const
Definition Dialogue.cpp:827
bool hide(const std::string &id)
void clearVars(const std::string &scope)
Definition Dialogue.cpp:416
bool isWaitingAdvance() const
bool isLipSyncEnabled() const
Definition Dialogue.h:138
std::string getDisplayName(const std::string &id) const
std::string getVisibleText() const
float getTypeSpeed() const
Definition Dialogue.h:123
bool hasVar(const std::string &name, const std::string &scope) const
Definition Dialogue.cpp:406
std::string getPhase() const
Data buffer paired with a filename (used for type identification).
Definition FileData.h:12
size_t getSize() const
Gets the size of the Data in bytes.
Definition FileData.h:24
void * getData() const
Gets a pointer to the data. This pointer will obviously not be valid if the Data object is destroyed.
Definition FileData.h:23
ECS mount point for one scene graph (full scene or nested subtree root). Isomorphic to eve::ui::UIHos...
Definition SceneHost.h:80
const std::string & getName()
I * query()
Definition Capability.h:77
bool parseDnut(const std::string &source, const std::string &path, DataValue &outRoot, std::string &error)
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Definition Widget.cpp:387
size_t utf8_codepoint_count(const std::string &s)
Count UTF-8 code points in a string. Invalid / truncated sequences stop the scan.
Definition utf8.cpp:23
size_t utf8_byte_offset_for_codepoints(const std::string &s, size_t codepoints)
Byte offset of the N-th UTF-8 code point (0-based count of code points). Returns s....
Definition utf8.cpp:37
Generic JSON-like value tree used by the Squirrel bridge: dialogue pools and conditions arrive as Squ...
Definition Dialogue.h:29
static DataValue null()
Definition Dialogue.h:40
static DataValue string(std::string v)
Definition Dialogue.h:59
std::vector< DataValue > arr
Definition Dialogue.h:37
static DataValue integer(long long v)
Definition Dialogue.h:41
static DataValue boolean(bool v)
Definition Dialogue.h:53
const DataValue * find(const std::string &key) const
Definition Dialogue.h:78
static DataValue array(std::vector< DataValue > v)
Definition Dialogue.h:65
static DataValue object(std::vector< std::pair< std::string, DataValue > > v)
Definition Dialogue.h:71
static DataValue number(double v)
Definition Dialogue.h:47
std::vector< std::pair< std::string, DataValue > > obj
Definition Dialogue.h:38
static VarValue number(double v)
Definition Dialogue.cpp:267
static VarValue boolean(bool v)
Definition Dialogue.cpp:274
static VarValue string(std::string v)
Definition Dialogue.cpp:281
static VarValue integer(long long v)
Definition Dialogue.cpp:260
std::vector< WidgetDesc > children
Definition Widget.h:74