载入中...
搜索中...
未找到
StoryEvent.cpp
浏览该文件的文档.
1#include "rpg/StoryEvent.h"
2
3#include "common/Json.h"
4#include "rpg/GameState.h"
5
6#include <cmath>
7#include <unordered_map>
8#include <unordered_set>
9#include <utility>
10
11namespace eve::rpg {
12namespace {
13
14std::unordered_map<std::string, StoryEventDefinition>& definitions() {
15 static std::unordered_map<std::string, StoryEventDefinition> value;
16 return value;
17}
18
19bool validText(const std::string& value, bool allowEmpty = false) {
20 if (value.empty()) return allowEmpty;
21 if (value.size() > 1024) return false;
22 for (unsigned char ch : value)
23 if (ch < 0x20 || ch == 0x7f) return false;
24 return true;
25}
26
27bool parseKind(const std::string& value, StoryEventStepKind& kind) {
28 if (value == "dialogue")
30 else if (value == "message")
32 else if (value == "wait")
34 else if (value == "move")
36 else if (value == "camera")
38 else
39 return false;
40 return true;
41}
42
43std::string kindName(StoryEventStepKind kind) {
44 switch (kind) {
45 case StoryEventStepKind::Dialogue: return "dialogue";
46 case StoryEventStepKind::Message: return "message";
47 case StoryEventStepKind::Wait: return "wait";
48 case StoryEventStepKind::Move: return "move";
49 case StoryEventStepKind::Camera: return "camera";
50 }
51 return {};
52}
53
54std::string eventScope(const std::string& eventId) { return "story.event:" + eventId; }
55
56} // namespace
57
59 std::string parseError;
60 const auto document = eve::json::Document::parse(json, &parseError);
61 if (!document.valid())
63 parseError.empty() ? "invalid JSON" : parseError, "$",
64 {}, "rpg.story-event"));
65 const auto root = document.root();
66 if (!root.isObject())
68 "story-event document root must be an object", "$", {},
69 "rpg.story-event"));
70 const std::unordered_set<std::string> rootFields = {"schema", "version", "events"};
71 for (const auto& key : root.keys())
72 if (!rootFields.contains(key))
74 "story-event document contains an unknown field",
75 "$." + key, {}, "rpg.story-event"));
76 const auto schema = root.get("schema");
77 const auto version = root.get("version");
78 const auto events = root.get("events");
79 if (!schema.isString() || schema.asString() != "eve.rpg.story-events")
81 "story-event schema id is invalid", "$.schema", {},
82 "rpg.story-event"));
83 if (!version.isInt64() || version.asInt64() != 1)
85 "unsupported story-event schema version", "$.version",
86 {}, "rpg.story-event"));
87 if (!events.isArray() || events.size() == 0 || events.size() > 256)
89 "events must contain between 1 and 256 entries",
90 "$.events", {}, "rpg.story-event"));
91
92 const std::unordered_set<std::string> eventFields = {"id", "repeatable", "steps"};
93 const std::unordered_set<std::string> stepFields = {"kind", "reference", "actorId", "x", "y", "duration"};
94 std::unordered_map<std::string, StoryEventDefinition> proposed;
95 for (std::size_t index = 0; index < events.size(); ++index) {
96 const auto object = events.at(index);
97 const std::string path = "$.events[" + std::to_string(index) + "]";
98 if (!object.isObject())
100 eve::DiagnosticCode::InvalidArgument, "story event must be an object", path, {}, "rpg.story-event"));
101 for (const auto& key : object.keys())
102 if (!eventFields.contains(key))
104 "story event contains an unknown field",
105 path + "." + key, {}, "rpg.story-event"));
106 const auto id = object.get("id");
107 const auto repeatable = object.get("repeatable");
108 const auto steps = object.get("steps");
109 if (!id.isString() || !validText(id.asString()))
111 "story event id must be a stable non-empty id",
112 path + ".id", {}, "rpg.story-event"));
113 if (proposed.contains(id.asString()))
115 eve::DiagnosticCode::AlreadyExists, "duplicate story event id", path + ".id", {}, "rpg.story-event"));
116 if (!repeatable.isBool())
118 "repeatable must be boolean", path + ".repeatable",
119 {}, "rpg.story-event"));
120 if (!steps.isArray() || steps.size() == 0 || steps.size() > 128)
122 "steps must contain between 1 and 128 entries",
123 path + ".steps", {}, "rpg.story-event"));
124 StoryEventDefinition definition;
125 definition.id = id.asString();
126 definition.repeatable = repeatable.asBool();
127 for (std::size_t stepIndex = 0; stepIndex < steps.size(); ++stepIndex) {
128 const auto value = steps.at(stepIndex);
129 const std::string stepPath = path + ".steps[" + std::to_string(stepIndex) + "]";
130 if (!value.isObject())
132 "story-event step must be an object", stepPath,
133 {}, "rpg.story-event"));
134 for (const auto& key : value.keys())
135 if (!stepFields.contains(key))
137 eve::DiagnosticCode::InvalidArgument, "story-event step contains an unknown field",
138 stepPath + "." + key, {}, "rpg.story-event"));
139 const auto kindValue = value.get("kind");
140 const auto reference = value.get("reference");
141 const auto actorId = value.get("actorId");
142 const auto x = value.get("x");
143 const auto y = value.get("y");
144 const auto duration = value.get("duration");
145 StoryEventStep parsed;
146 if (!kindValue.isString() || !parseKind(kindValue.asString(), parsed.kind))
148 "story-event step kind is invalid",
149 stepPath + ".kind", {}, "rpg.story-event"));
150 if (!reference.isString() || !actorId.isString() || !validText(reference.asString(), true) ||
151 !validText(actorId.asString(), true) || !x.isNumber() || !y.isNumber() || !duration.isNumber() ||
152 !std::isfinite(x.asDouble()) || !std::isfinite(y.asDouble()) || !std::isfinite(duration.asDouble()) ||
153 duration.asDouble() < 0.0)
155 "story-event step fields are invalid", stepPath,
156 {}, "rpg.story-event"));
157 const bool needsReference =
159 const bool needsActor = parsed.kind == StoryEventStepKind::Move;
160 const bool needsDuration = parsed.kind == StoryEventStepKind::Wait;
161 if (needsReference != !reference.asString().empty())
163 eve::DiagnosticCode::InvalidArgument, "reference presence does not match step kind",
164 stepPath + ".reference", {}, "rpg.story-event"));
165 if (needsActor != !actorId.asString().empty())
167 "actorId presence does not match step kind",
168 stepPath + ".actorId", {}, "rpg.story-event"));
169 if (needsDuration && duration.asDouble() <= 0.0)
171 "wait duration must be positive",
172 stepPath + ".duration", {}, "rpg.story-event"));
173 parsed.reference = reference.asString();
174 parsed.actorId = actorId.asString();
175 parsed.x = x.asDouble();
176 parsed.y = y.asDouble();
177 parsed.duration = duration.asDouble();
178 definition.steps.push_back(std::move(parsed));
179 }
180 proposed.emplace(definition.id, std::move(definition));
181 }
182 definitions() = std::move(proposed);
183 return eve::Result<int>::success(static_cast<int>(definitions().size()));
184}
185
187int StoryEventCatalogue::count() { return static_cast<int>(definitions().size()); }
188bool StoryEventCatalogue::contains(const std::string& eventId) { return definitions().contains(eventId); }
189const StoryEventDefinition* StoryEventCatalogue::find(const std::string& eventId) {
190 const auto found = definitions().find(eventId);
191 return found == definitions().end() ? nullptr : &found->second;
192}
193
194eve::Result<void> StoryEventSession::begin(const std::string& eventId, GameState* gameState) {
195 if (!gameState)
197 "begin requires a GameState owner", "gameState", {},
198 "rpg.story-event"));
199 const auto* definition = StoryEventCatalogue::find(eventId);
200 if (!definition)
202 eve::DiagnosticCode::NotFound, "story event is not registered", "eventId", {}, "rpg.story-event"));
203 const std::string scope = eventScope(eventId);
204 const bool hasCompleted = gameState->hasSelfVariable(scope, "completed");
205 const double completedValue = hasCompleted ? gameState->getSelfVariable(scope, "completed") : 0.0;
206 if (hasCompleted && completedValue != 0.0 && completedValue != 1.0)
208 "persisted story-event completion flag is invalid",
209 scope + ".completed", {}, "rpg.story-event"));
210 const bool completed = completedValue == 1.0;
211 if (completed && !definition->repeatable)
213 "non-repeatable story event is already complete",
214 "eventId", {}, "rpg.story-event"));
215 double storedCursor =
216 gameState->hasSelfVariable(scope, "cursor") ? gameState->getSelfVariable(scope, "cursor") : 0.0;
217 if (completed && definition->repeatable) storedCursor = 0.0;
218 if (!std::isfinite(storedCursor) || std::floor(storedCursor) != storedCursor || storedCursor < 0.0 ||
219 storedCursor >= static_cast<double>(definition->steps.size()))
221 "persisted story-event cursor is invalid",
222 scope + ".cursor", {}, "rpg.story-event"));
223
224 StoryEventDefinition candidate = *definition;
225 definition_ = std::move(candidate);
226 cursor_ = static_cast<int>(storedCursor);
227 active_ = true;
228 finished_ = false;
229 if (completed) {
230 gameState->setSelfVariable(scope, "cursor", 0.0);
231 gameState->setSelfVariable(scope, "completed", 0.0);
232 }
234}
235
237 if (!gameState)
239 "advance requires a GameState owner", "gameState", {},
240 "rpg.story-event"));
241 if (!active_ || !current())
243 "story event has no active step", "session", {},
244 "rpg.story-event"));
245 const std::string scope = eventScope(definition_.id);
246 const double persistedCursor =
247 gameState->hasSelfVariable(scope, "cursor") ? gameState->getSelfVariable(scope, "cursor") : 0.0;
248 const double persistedCompleted =
249 gameState->hasSelfVariable(scope, "completed") ? gameState->getSelfVariable(scope, "completed") : 0.0;
250 if (!std::isfinite(persistedCursor) || persistedCursor != static_cast<double>(cursor_) || persistedCompleted != 0.0)
252 eve::DiagnosticCode::Conflict, "story-event cursor changed since this step was presented",
253 scope + ".cursor", {}, "rpg.story-event"));
254 const int next = cursor_ + 1;
255 gameState->setSelfVariable(scope, "cursor", static_cast<double>(next));
256 if (next == static_cast<int>(definition_.steps.size())) {
257 gameState->setSelfVariable(scope, "completed", 1.0);
258 cursor_ = next;
259 active_ = false;
260 finished_ = true;
262 }
263 cursor_ = next;
264 return eve::Result<int>::success(static_cast<int>(definition_.steps.size()) - cursor_,
266}
267
268const StoryEventStep* StoryEventSession::current() const noexcept {
269 if (!active_ || cursor_ < 0 || static_cast<std::size_t>(cursor_) >= definition_.steps.size()) return nullptr;
270 return &definition_.steps[static_cast<std::size_t>(cursor_)];
271}
272
273bool StoryEventSession::isActive() const { return active_; }
274bool StoryEventSession::isFinished() const { return finished_; }
275int StoryEventSession::getStepIndex() const { return cursor_; }
276int StoryEventSession::getStepCount() const { return static_cast<int>(definition_.steps.size()); }
277std::string StoryEventSession::getEventId() const { return definition_.id; }
279 const auto* step = current();
280 return step ? kindName(step->kind) : std::string{};
281}
283 const auto* step = current();
284 return step ? step->reference : std::string{};
285}
286std::string StoryEventSession::getActorId() const {
287 const auto* step = current();
288 return step ? step->actorId : std::string{};
289}
291 const auto* step = current();
292 return step ? step->x : 0.0;
293}
295 const auto* step = current();
296 return step ? step->y : 0.0;
297}
299 const auto* step = current();
300 return step ? step->duration : 0.0;
301}
302
303} // namespace eve::rpg
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float duration
int root
Definition AnimSmr.cpp:119
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
全局游戏状态:开关(bool)、变量(数字)、独立变量(按作用域)。
std::uint32_t key
JobScope scope
TokenKind kind
std::string path
Definition PlayHost.cpp:110
int steps
bool found
Versioned, resumable presentation-event sequencing for RPG games.
Battle::Events events
float step
Definition TreeMesh.cpp:314
float size
Definition TreeMesh.cpp:156
uint32_t index
const std::map< std::string, Value > & definitions
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Definition Json.cpp:581
一份游戏状态(开关/变量/独立变量)。
Definition GameState.h:25
double getSelfVariable(const std::string &scope, const std::string &name) const
Returns the self variable.
Definition GameState.cpp:75
bool hasSelfVariable(const std::string &scope, const std::string &name) const
True when self variable.
Definition GameState.cpp:82
void setSelfVariable(const std::string &scope, const std::string &name, double value)
独立变量:scope(如 "map:1:event:3")下按名字存数字。
Definition GameState.cpp:71
static bool contains(const std::string &eventId)
Return whether an exact stable event id exists in the current catalogue.
static int count()
Return the committed definition count on the owning content thread.
static eve::Result< int > replaceFromJsonStrict(const std::string &json)
Validate and atomically replace every story-event definition.
static void clear()
Clear all definitions on the owning content thread without invoking callbacks.
static const StoryEventDefinition * find(const std::string &eventId)
Find one definition for immediate synchronous inspection.
int getStepIndex() const
Return the zero-based current cursor, or the step count after completion.
std::string getReference() const
Return an owning copy of the current dialogue/message reference.
double getDuration() const
Return the current wait/motion duration in seconds, or zero while inactive.
bool isActive() const
Return whether a current step is awaiting acknowledgement.
eve::Result< int > advance(GameState *gameState)
Acknowledge the current presentation command and persist the next cursor atomically.
double getY() const
Return the current move/camera Y coordinate, or zero while inactive.
bool isFinished() const
Return whether this session acknowledged its final step.
std::string getEventId() const
Return an owning copy of the current event id.
double getX() const
Return the current move/camera X coordinate, or zero while inactive.
std::string getActorId() const
Return an owning copy of the current move actor id.
eve::Result< void > begin(const std::string &eventId, GameState *gameState)
Begin or resume an event from its authoritative GameState cursor.
int getStepCount() const
Return the copied definition's total step count.
std::string getStepKind() const
Return the current command kind, or an empty string while inactive.
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
StoryEventStepKind
Closed set of UI-neutral presentation commands emitted by a story event.
Definition StoryEvent.h:17
One immutable, ordered and resumable story-event definition.
Definition StoryEvent.h:30
std::vector< StoryEventStep > steps
Definition StoryEvent.h:33
One immutable presentation command owned by a story-event definition.
Definition StoryEvent.h:20
StoryEventStepKind kind
Definition StoryEvent.h:21