载入中...
搜索中...
未找到
SkillConditionCodec.cpp
浏览该文件的文档.
2
3#include <set>
4#include <string>
5#include <utility>
6
7namespace eve::rpg {
8namespace {
9
10const eve::Value* field(const eve::Value::Object& object, std::string_view name) {
11 const auto it = object.find(std::string(name));
12 return it == object.end() ? nullptr : &it->second;
13}
14
15bool exactFields(const eve::Value::Object& object, std::initializer_list<std::string_view> allowed) {
16 const std::set<std::string_view> fields(allowed.begin(), allowed.end());
17 for (const auto& [name, unused] : object) {
18 (void)unused;
19 if (!fields.contains(name)) return false;
20 }
21 return true;
22}
23
24eve::Result<std::string> requiredString(const eve::Value::Object& object, std::string_view name) {
25 const auto* value = field(object, name);
26 const auto* text = value == nullptr ? nullptr : value->getIf<std::string>();
27 if (text == nullptr || text->empty())
29 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "condition field must be a non-empty string",
30 std::string(name), {}, "rpg.skill.condition_codec"));
32}
33
35 const auto* text = value.getIf<std::string>();
36 if (text == nullptr)
38 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "condition kind must be a string", "kind", {},
39 "rpg.skill.condition_codec"));
41 static const std::pair<std::string_view, K> names[] = {
42 {"all", K::All},
43 {"any", K::Any},
44 {"not", K::Not},
45 {"compare", K::Compare},
46 {"has_tag", K::HasTag},
47 {"has_attribute", K::HasAttribute},
48 {"has_resource", K::HasResource},
49 {"state_equals", K::StateEquals},
50 {"authority_check", K::AuthorityCheck},
51 {"policy_call", K::PolicyCall},
52 };
53 for (const auto& [name, kind] : names)
54 if (*text == name) return eve::Result<K>::success(kind);
56 eve::DiagnosticCode::InvalidArgument, "unknown condition kind", "kind", {}, "rpg.skill.condition_codec"));
57}
58
60 const auto* text = value.getIf<std::string>();
61 if (text == nullptr)
63 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "condition operator must be a string",
64 "operator", {}, "rpg.skill.condition_codec"));
66 static const std::pair<std::string_view, O> names[] = {
67 {"eq", O::Equal}, {"ne", O::NotEqual}, {"lt", O::Less},
68 {"le", O::LessEqual}, {"gt", O::Greater}, {"ge", O::GreaterEqual},
69 };
70 for (const auto& [name, op] : names)
71 if (*text == name) return eve::Result<O>::success(op);
73 "unknown condition operator", "operator", {},
74 "rpg.skill.condition_codec"));
75}
76
78 const auto* text = value.getIf<std::string>();
79 if (text == nullptr)
82 "scriptDeclaration.determinism", {}, "rpg.skill.condition_codec"));
84 static const std::pair<std::string_view, D> names[] = {
85 {"bit_exact", D::BitExact},
86 {"tick_deterministic", D::TickDeterministic},
87 {"tolerance_bounded", D::ToleranceBounded},
88 {"explicitly_nondeterministic", D::ExplicitlyNondeterministic},
89 };
90 for (const auto& [name, level] : names)
91 if (*text == name) return eve::Result<D>::success(level);
93 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unknown condition determinism level",
94 "scriptDeclaration.determinism", {}, "rpg.skill.condition_codec"));
95}
96
98
100 const auto* value = field(object, "children");
101 const auto* array = value == nullptr ? nullptr : value->getIf<eve::Value::Array>();
102 if (array == nullptr)
104 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "condition children must be an array",
105 "children", {}, "rpg.skill.condition_codec"));
106 std::vector<eve::decision::Condition> result;
107 result.reserve(array->size());
108 for (const auto& child : *array) {
109 auto decoded = decodeNode(child);
110 if (!decoded) return eve::Result<std::vector<eve::decision::Condition>>::failure(decoded.status());
111 result.push_back(std::move(decoded).takeValue());
112 }
113 return eve::Result<std::vector<eve::decision::Condition>>::success(std::move(result));
114}
115
117 const auto* object = value.getIf<eve::Value::Object>();
118 if (object == nullptr)
120 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "condition node must be an object", {}, {},
121 "rpg.skill.condition_codec"));
122 const auto* kindValue = field(*object, "kind");
123 if (kindValue == nullptr)
125 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "condition node requires kind", "kind", {},
126 "rpg.skill.condition_codec"));
127 auto kind = parseKind(*kindValue);
129 const auto parsedKind = kind.value();
131 if (parsedKind == K::All || parsedKind == K::Any || parsedKind == K::Not) {
132 if (!exactFields(*object, {"kind", "children"}))
134 eve::DiagnosticCode::InvalidArgument, "logical condition contains an unknown field", {}, {},
135 "rpg.skill.condition_codec"));
136 auto children = decodeChildren(*object);
138 auto values = std::move(children).takeValue();
139 if (parsedKind == K::Not && values.size() != 1)
141 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "not condition requires one child",
142 "children", {}, "rpg.skill.condition_codec"));
143 if (parsedKind == K::All)
145 if (parsedKind == K::Any)
148 eve::decision::Condition::not_(std::move(values.front())));
149 }
150 if (parsedKind == K::Compare) {
151 if (!exactFields(*object, {"kind", "key", "operator", "expected"}))
153 eve::DiagnosticCode::InvalidArgument, "compare condition contains an unknown field", {}, {},
154 "rpg.skill.condition_codec"));
155 auto key = requiredString(*object, "key");
156 const auto* op = field(*object, "operator");
157 const auto* expected = field(*object, "expected");
158 if (!key || op == nullptr || expected == nullptr)
160 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "compare condition is incomplete", {}, {},
161 "rpg.skill.condition_codec"));
162 auto parsedOp = parseOperator(*op);
163 if (!parsedOp) return eve::Result<eve::decision::Condition>::failure(parsedOp.status());
165 eve::decision::Condition::compare(std::move(key).takeValue(), parsedOp.value(), *expected));
166 }
167 if (parsedKind == K::StateEquals) {
168 if (!exactFields(*object, {"kind", "key", "expected"}))
170 eve::DiagnosticCode::InvalidArgument, "state condition contains an unknown field", {}, {},
171 "rpg.skill.condition_codec"));
172 auto key = requiredString(*object, "key");
173 const auto* expected = field(*object, "expected");
174 if (!key || expected == nullptr)
176 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "state condition is incomplete", {}, {},
177 "rpg.skill.condition_codec"));
179 eve::decision::Condition::stateEquals(std::move(key).takeValue(), *expected));
180 }
181 if (parsedKind == K::PolicyCall) {
182 if (!exactFields(*object, {"kind", "key", "arguments", "scriptDeclaration"}))
184 eve::DiagnosticCode::InvalidArgument, "policy condition contains an unknown field", {}, {},
185 "rpg.skill.condition_codec"));
186 auto key = requiredString(*object, "key");
188 eve::Value arguments = eve::Value::Object{};
189 if (const auto* value = field(*object, "arguments")) arguments = *value;
190 std::optional<eve::decision::ScriptConditionDeclaration> declaration;
191 if (const auto* value = field(*object, "scriptDeclaration")) {
192 const auto* declarationObject = value->getIf<eve::Value::Object>();
193 if (declarationObject == nullptr ||
194 !exactFields(*declarationObject, {"name", "dependencies", "determinism"}))
196 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "script declaration is invalid",
197 "scriptDeclaration", {}, "rpg.skill.condition_codec"));
198 auto name = requiredString(*declarationObject, "name");
199 const auto* dependencyValue = field(*declarationObject, "dependencies");
200 const auto* dependencies =
201 dependencyValue == nullptr ? nullptr : dependencyValue->getIf<eve::Value::Array>();
202 const auto* determinism = field(*declarationObject, "determinism");
203 if (!name || dependencies == nullptr || determinism == nullptr)
205 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "script declaration is incomplete",
206 "scriptDeclaration", {}, "rpg.skill.condition_codec"));
207 auto level = parseDeterminism(*determinism);
209 eve::decision::ScriptConditionDeclaration parsed{std::move(name).takeValue(), {}, level.value()};
210 for (const auto& dependency : *dependencies) {
211 const auto* text = dependency.getIf<std::string>();
212 if (text == nullptr || text->empty())
214 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "script dependency is invalid",
215 "scriptDeclaration.dependencies", {}, "rpg.skill.condition_codec"));
216 parsed.dependencies.push_back(*text);
217 }
218 declaration = std::move(parsed);
219 }
221 std::move(key).takeValue(), std::move(arguments), std::move(declaration)));
222 }
223 if (!exactFields(*object, {"kind", "key"}))
225 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "leaf condition contains an unknown field", {},
226 {}, "rpg.skill.condition_codec"));
227 auto key = requiredString(*object, "key");
229 switch (parsedKind) {
230 case K::HasTag:
232 eve::decision::Condition::hasTag(std::move(key).takeValue()));
233 case K::HasAttribute:
235 eve::decision::Condition::hasAttribute(std::move(key).takeValue()));
236 case K::HasResource:
238 eve::decision::Condition::hasResource(std::move(key).takeValue()));
239 case K::AuthorityCheck:
241 eve::decision::Condition::authorityCheck(std::move(key).takeValue()));
242 default:
244 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unsupported condition node kind", {}, {},
245 "rpg.skill.condition_codec"));
246 }
247}
248
249} // namespace
250
252 if (!condition.isValid())
254 eve::DiagnosticCode::InvalidArgument, "skill condition is invalid", {}, {}, "rpg.skill.condition_codec"));
257 if (condition.kind() == K::All || condition.kind() == K::Any || condition.kind() == K::Not) {
259 for (const auto& child : condition.children()) {
260 auto encoded = encodeSkillCondition(child);
261 if (!encoded) return eve::Result<eve::Value>::failure(encoded.status());
262 children.push_back(std::move(encoded).takeValue());
263 }
264 object.emplace("children", eve::Value(std::move(children)));
265 } else if (condition.kind() == K::Compare) {
266 object.emplace("key", eve::Value(condition.key()));
267 object.emplace("operator", eve::Value(eve::decision::compareOperatorName(condition.compareOperator())));
268 object.emplace("expected", condition.expected());
269 } else if (condition.kind() == K::StateEquals) {
270 object.emplace("key", eve::Value(condition.key()));
271 object.emplace("expected", condition.expected());
272 } else {
273 object.emplace("key", eve::Value(condition.key()));
274 if (condition.kind() == K::PolicyCall) {
275 object.emplace("arguments", condition.arguments());
276 if (condition.scriptDeclaration()) {
277 const auto& declaration = *condition.scriptDeclaration();
278 eve::Value::Array dependencies;
279 for (const auto& dependency : declaration.dependencies) dependencies.emplace_back(dependency);
280 std::string determinism;
281 switch (declaration.determinism) {
282 case eve::decision::DeterminismLevel::BitExact: determinism = "bit_exact"; break;
283 case eve::decision::DeterminismLevel::TickDeterministic: determinism = "tick_deterministic"; break;
284 case eve::decision::DeterminismLevel::ToleranceBounded: determinism = "tolerance_bounded"; break;
286 determinism = "explicitly_nondeterministic";
287 break;
288 }
289 object.emplace("scriptDeclaration",
290 eve::Value(eve::Value::Object{{"name", eve::Value(declaration.name)},
291 {"dependencies", eve::Value(std::move(dependencies))},
292 {"determinism", eve::Value(std::move(determinism))}}));
293 }
294 }
295 }
296 return eve::Result<eve::Value>::success(eve::Value(std::move(object)));
297}
298
300
301} // namespace eve::rpg
double value
eve::Value condition
std::map< std::string, Var > values
std::uint32_t key
std::string text
TokenKind kind
std::string name
int level
std::vector< std::string > fields
Definition PlayHost.cpp:111
RPG-owned Value codec for the shared decision condition tree.
Json object
int children
Definition TreeMesh.cpp:295
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
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Type kind() const noexcept
Compatibility spelling for the former dialogue data tree.
Definition Value.h:85
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Immutable condition AST node.
Definition Condition.h:195
static Condition hasAttribute(std::string attribute)
Require an attribute to exist.
static Condition all(std::vector< Condition > children)
Construct a conjunction of child conditions.
static Condition not_(Condition child)
Construct a logical negation of one child condition.
static Condition hasTag(std::string tag)
Require a tag to be present.
static Condition stateEquals(std::string key, Value expected)
Require a named state value to equal the expected value.
static Condition policyCall(std::string name, Value arguments=Value::Object{}, std::optional< ScriptConditionDeclaration > scriptDeclaration=std::nullopt)
Call a named read-only policy and use its ConditionResult.
static Condition compare(std::string key, CompareOperator op, Value expected)
Compare a context value against an expected scalar value.
static Condition authorityCheck(std::string scope)
Require read-only authority for a scope.
static Condition any(std::vector< Condition > children)
Construct a disjunction of child conditions.
static Condition hasResource(std::string resource)
Require a resource to exist.
CompareOperator
Scalar comparison used by a Compare node.
Definition Condition.h:36
const char * conditionKindName(ConditionKind kind) noexcept
Return the stable lowercase spelling of a condition node kind. @ownership Borrowed process-static tex...
ConditionKind
Node kind in the side-effect-free condition AST.
Definition Condition.h:22
DeterminismLevel
Determinism contract declared by a script-backed condition policy.
Definition Condition.h:46
@ ToleranceBounded
Results may vary within a documented numeric tolerance.
@ TickDeterministic
Deterministic when evaluated at the injected simulation tick.
@ BitExact
Same inputs produce the same bits without an external clock.
@ ExplicitlyNondeterministic
Result is intentionally not suitable for replay or lockstep.
const char * compareOperatorName(CompareOperator op) noexcept
Return the stable lowercase spelling of a comparison operator. @ownership Borrowed process-static tex...
eve::Result< T > Result
const EditorValue * field(const EditorValue &value, const char *name)
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
eve::Result< eve::decision::Condition > decodeSkillCondition(const eve::Value &value)
Decode one Skill condition tree from an owning Value.
eve::Result< eve::Value > encodeSkillCondition(const eve::decision::Condition &condition)
Encode one validated Skill condition tree as an owning Value.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Metadata a script policy must declare before it is used by a condition.
Definition Condition.h:64