载入中...
搜索中...
未找到
EncounterCatalogue.cpp
浏览该文件的文档.
2
3#include "common/Json.h"
4#include "rpg/Quest.h"
5#include "rpg/RPGActor.h"
6#include "rpg/Skill.h"
7
8#include <climits>
9#include <cmath>
10#include <unordered_map>
11#include <unordered_set>
12#include <utility>
13
14namespace eve::rpg {
15namespace {
16
17std::unordered_map<std::string, EncounterDefinition> &definitions() {
18 static std::unordered_map<std::string, EncounterDefinition> value;
19 return value;
20}
21
22bool validId(const std::string &value, bool allowEmpty = false) {
23 if (value.empty()) return allowEmpty;
24 if (value.size() > 256) return false;
25 for (unsigned char ch : value)
26 if (ch < 0x20 || ch == 0x7f) return false;
27 return true;
28}
29
30eve::Result<int> failure(std::string message, std::string path) {
33 "rpg.encounter-catalogue"));
34}
35
36const std::unordered_set<std::string> &knownFields() {
37 static const std::unordered_set<std::string> fields = {
38 "id", "displayName", "members", "xpReward", "xpGrowth", "goldReward",
39 "requiredQuestId", "notifyTopic", "notifyTarget",
40 "notifyAmount", "defeatCounterId", "defeatCounterAmount", "levelPointAttributeId",
41 "pointsPerLevel"};
42 return fields;
43}
44
45const std::unordered_set<std::string> &knownMemberFields() {
46 static const std::unordered_set<std::string> fields = {
47 "id", "displayName", "skillId", "attack", "defense", "maxHp", "speed"};
48 return fields;
49}
50
51bool finiteNumber(const eve::json::Value &value, double minimum, bool strictMinimum = false) {
52 if (!value.isNumber() || !std::isfinite(value.asDouble())) return false;
53 return strictMinimum ? value.asDouble() > minimum : value.asDouble() >= minimum;
54}
55
56bool validPositiveInt(const eve::json::Value &value, bool allowZero = false) {
57 return value.isInt64() && value.asInt64() <= INT_MAX &&
58 (allowZero ? value.asInt64() >= 0 : value.asInt64() > 0);
59}
60
61} // namespace
62
64 std::string parseError;
65 const auto document = eve::json::Document::parse(json, &parseError);
66 if (!document.valid()) return failure(parseError.empty() ? "invalid JSON" : parseError, "$");
67 const auto root = document.root();
68 if (!root.isArray() || root.size() == 0)
69 return failure("encounter catalogue must be a non-empty array", "$");
70
71 std::unordered_map<std::string, EncounterDefinition> proposed;
72 for (std::size_t index = 0; index < root.size(); ++index) {
73 const auto object = root.at(index);
74 const std::string path = "$[" + std::to_string(index) + "]";
75 if (!object.isObject()) return failure("encounter definition must be an object", path);
76 for (const auto &key : object.keys())
77 if (knownFields().count(key) == 0)
78 return failure("encounter definition contains an unknown field", path + "." + key);
79 const auto id = object.get("id");
80 const auto displayName = object.get("displayName");
81 if (!id.isString() || !validId(id.asString()))
82 return failure("encounter id must be a stable non-empty id", path + ".id");
83 if (proposed.count(id.asString()) != 0) return failure("duplicate encounter id", path + ".id");
84 if (!displayName.isString() || displayName.asString().empty() || displayName.asString().size() > 512)
85 return failure("displayName must be a non-empty string of at most 512 bytes",
86 path + ".displayName");
87 const auto members = object.get("members");
88 if (!members.isArray() || members.size() == 0 || members.size() > 32)
89 return failure("members must be a non-empty array with at most 32 entries",
90 path + ".members");
91 const auto xpReward = object.get("xpReward");
92 const auto xpGrowth = object.get("xpGrowth");
93 const auto goldReward = object.get("goldReward");
94 if (!finiteNumber(xpReward, 0.0, true) || !finiteNumber(xpGrowth, 1.0) ||
95 !finiteNumber(goldReward, 0.0))
96 return failure("rewards must be finite and within their valid ranges", path);
97 const auto requiredQuestId = object.get("requiredQuestId");
98 const auto notifyTopic = object.get("notifyTopic");
99 const auto notifyTarget = object.get("notifyTarget");
100 const auto defeatCounterId = object.get("defeatCounterId");
101 const auto levelPointAttributeId = object.get("levelPointAttributeId");
102 if (!requiredQuestId.isString() || !validId(requiredQuestId.asString()) ||
103 !QuestRegistry::find(requiredQuestId.asString()))
104 return failure("requiredQuestId must reference a registered quest", path + ".requiredQuestId");
105 if (!notifyTopic.isString() || !validId(notifyTopic.asString()) ||
106 !notifyTarget.isString() || !validId(notifyTarget.asString()) ||
107 !defeatCounterId.isString() || !validId(defeatCounterId.asString()) ||
108 !levelPointAttributeId.isString() || !validId(levelPointAttributeId.asString()))
109 return failure("notification and counter ids must be stable non-empty ids", path);
110 const auto notifyAmount = object.get("notifyAmount");
111 const auto defeatCounterAmount = object.get("defeatCounterAmount");
112 const auto pointsPerLevel = object.get("pointsPerLevel");
113 if (!validPositiveInt(notifyAmount) || !validPositiveInt(defeatCounterAmount) ||
114 !validPositiveInt(pointsPerLevel, true))
115 return failure("notification/counter amounts must be positive and pointsPerLevel non-negative", path);
116
117 EncounterDefinition definition;
118 definition.id = id.asString();
119 definition.displayName = displayName.asString();
120 std::unordered_set<std::string> memberIds;
121 for (std::size_t memberIndex = 0; memberIndex < members.size(); ++memberIndex) {
122 const auto member = members.at(memberIndex);
123 const std::string memberPath = path + ".members[" + std::to_string(memberIndex) + "]";
124 if (!member.isObject()) return failure("encounter member must be an object", memberPath);
125 for (const auto &key : member.keys())
126 if (knownMemberFields().count(key) == 0)
127 return failure("encounter member contains an unknown field", memberPath + "." + key);
128 const auto memberId = member.get("id");
129 const auto memberName = member.get("displayName");
130 const auto memberSkill = member.get("skillId");
131 const auto attack = member.get("attack");
132 const auto defense = member.get("defense");
133 const auto maxHp = member.get("maxHp");
134 const auto speed = member.get("speed");
135 if (!memberId.isString() || !validId(memberId.asString()) ||
136 !memberIds.insert(memberId.asString()).second)
137 return failure("member id must be stable and unique within the encounter",
138 memberPath + ".id");
139 if (!memberName.isString() || memberName.asString().empty() ||
140 memberName.asString().size() > 512)
141 return failure("member displayName must be a non-empty string of at most 512 bytes",
142 memberPath + ".displayName");
143 if (!memberSkill.isString() || !validId(memberSkill.asString()) ||
144 !SkillRegistry::find(memberSkill.asString()))
145 return failure("member skillId must reference a registered skill",
146 memberPath + ".skillId");
147 if (!finiteNumber(attack, 0.0) || !finiteNumber(defense, 0.0) ||
148 !finiteNumber(maxHp, 0.0, true) || !finiteNumber(speed, 0.0))
149 return failure("member combat stats must be finite and within their valid ranges",
150 memberPath);
151 EncounterMemberDefinition parsedMember;
152 parsedMember.id = memberId.asString();
153 parsedMember.displayName = memberName.asString();
154 parsedMember.skillId = memberSkill.asString();
155 parsedMember.attack = attack.asDouble();
156 parsedMember.defense = defense.asDouble();
157 parsedMember.maxHp = maxHp.asDouble();
158 parsedMember.speed = speed.asDouble();
159 definition.members.push_back(std::move(parsedMember));
160 }
161 definition.xpReward = xpReward.asDouble();
162 definition.xpGrowth = xpGrowth.asDouble();
163 definition.goldReward = goldReward.asDouble();
164 definition.requiredQuestId = requiredQuestId.asString();
165 definition.notifyTopic = notifyTopic.asString();
166 definition.notifyTarget = notifyTarget.asString();
167 definition.notifyAmount = static_cast<int>(notifyAmount.asInt64());
168 definition.defeatCounterId = defeatCounterId.asString();
169 definition.defeatCounterAmount = static_cast<int>(defeatCounterAmount.asInt64());
170 definition.levelPointAttributeId = levelPointAttributeId.asString();
171 definition.pointsPerLevel = static_cast<int>(pointsPerLevel.asInt64());
172 proposed.emplace(definition.id, std::move(definition));
173 }
174 definitions() = std::move(proposed);
175 return eve::Result<int>::success(static_cast<int>(definitions().size()));
176}
177
179int EncounterCatalogue::count() { return static_cast<int>(definitions().size()); }
180
181const EncounterDefinition *EncounterCatalogue::find(const std::string &id) {
182 const auto found = definitions().find(id);
183 return found == definitions().end() ? nullptr : &found->second;
184}
185
186int EncounterCatalogue::memberCount(const std::string &id) {
187 const auto *definition = find(id);
188 return definition ? static_cast<int>(definition->members.size()) : 0;
189}
190
192 return createMemberActor(id, 0);
193}
194
195RPGActor *EncounterCatalogue::createMemberActor(const std::string &id, int memberIndex) {
196 const auto *definition = find(id);
197 if (!definition || memberIndex < 0 ||
198 static_cast<std::size_t>(memberIndex) >= definition->members.size())
199 return nullptr;
200 const auto &member = definition->members[static_cast<std::size_t>(memberIndex)];
202 if (!actor) return nullptr;
203 actor->setBaseAttribute("attack", member.attack);
204 actor->setBaseAttribute("defense", member.defense);
205 actor->setBaseAttribute("hp", member.maxHp);
206 actor->setBaseAttribute("speed", member.speed);
207 actor->setCurrent("hp", member.maxHp);
208 actor->learnSkill(member.skillId);
209 return actor;
210}
211
212} // namespace eve::rpg
double value
int root
Definition AnimSmr.cpp:119
Strict RPG encounter definitions and settlement projection.
std::string message
float minimum[3]
std::uint32_t key
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
任务 / 成就 / 教程的通用定义模板与进程级注册表。
bool found
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
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 Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Definition Json.cpp:581
EVENGINE_API_FOUNDATION public API.
Definition Json.h:38
static int memberCount(const std::string &id)
Return the number of ordered enemy members in an encounter, or zero when unknown.
static eve::Result< int > replaceFromJsonStrict(const std::string &json)
Validate and atomically replace every encounter definition.
static RPGActor * createMemberActor(const std::string &id, int memberIndex)
Create one indexed member of an encounter composition.
static int count()
Returns the number of .
static const EncounterDefinition * find(const std::string &id)
Finds .
static RPGActor * createActor(const std::string &id)
Create and initialize an ECS-owned enemy actor from one definition.
static const QuestDefinition * find(const std::string &id)
Find a registered quest definition by id.
Definition Quest.cpp:160
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
static RPGActor * createActor()
创建一个空白 actor(三张表均为空,按需惰性写入),并纳入 liveActors() 跟踪。
Definition RPGActor.cpp:28
void learnSkill(const std::string &skillId)
技能:学习 / 查询 / 冷却。
Definition RPGActor.cpp:197
void setBaseAttribute(const std::string &attribute, double value)
便捷成员方法(转发到 AttributeSystem / StatusSystem / SkillSystem)。 提供这一层薄封装主要是为了让脚本可以写 actor....
Definition RPGActor.cpp:59
void setCurrent(const std::string &resource, double value)
Definition RPGActor.cpp:255
static const SkillDefinition * find(const std::string &id)
Find a registered skill definition by id.
Definition Skill.cpp:90
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
One immutable, fully specified encounter composition and settlement definition.
One immutable enemy member inside an encounter composition.