载入中...
搜索中...
未找到
Party.cpp
浏览该文件的文档.
1#include "rpg/Party.h"
2
3#include "rpg/Battle.h"
4#include "rpg/VitalsSystem.h"
5#include "rpg/RPGActor.h"
6
7#include "common/Value.h"
8
9#include <algorithm>
10#include <cmath>
11#include <utility>
12
13namespace eve::rpg {
14
15namespace {
16
17bool validId(const std::string &value) {
18 return !value.empty() && value.size() <= 256 &&
19 std::none_of(value.begin(), value.end(), [](unsigned char ch) {
20 return ch < 0x20u || ch == 0x7fu;
21 });
22}
23
24bool sameHandle(const ecs::EntityHandle &left, const ecs::EntityHandle &right) noexcept {
25 return left.table == right.table && left.type == right.type && left.id == right.id &&
26 left.generation == right.generation;
27}
28
29RPGActor *resolve(const ecs::EntityHandle &handle) noexcept {
30 return dynamic_cast<RPGActor *>(ecs::try_get(handle));
31}
32
33} // namespace
34
35eve::Result<void> Party::addMember(const std::string &memberId, RPGActor *actor) {
36 if (!validId(memberId))
38 eve::DiagnosticCode::InvalidArgument, "party member id must be stable, non-empty, and free of controls",
39 "memberId", {}, "rpg.party"));
40 if (!actor)
42 eve::DiagnosticCode::InvalidArgument, "party member actor must not be null", "actor", {}, "rpg.party"));
43 const auto handle = ecs::handle_of(actor);
44 if (!resolve(handle))
46 eve::DiagnosticCode::StaleHandle, "party member actor is not live", "actor", {}, "rpg.party"));
47 for (const auto &member : members_) {
48 if (member.id == memberId)
50 eve::DiagnosticCode::AlreadyExists, "party member id is already present", "memberId", {}, "rpg.party"));
51 if (sameHandle(member.actor, handle))
53 eve::DiagnosticCode::AlreadyExists, "actor is already linked to the party", "actor", {}, "rpg.party"));
54 }
55 members_.push_back(Member{memberId, handle});
57}
58
59eve::Result<void> Party::removeMember(const std::string &memberId) {
60 const auto found = std::find_if(members_.begin(), members_.end(), [&](const Member &member) {
61 return member.id == memberId;
62 });
63 if (found == members_.end())
65 eve::DiagnosticCode::NotFound, "party member id is not present", "memberId", {}, "rpg.party"));
66 members_.erase(found);
68}
69
70void Party::clear() { members_.clear(); }
71int Party::count() const { return static_cast<int>(members_.size()); }
72
73bool Party::contains(const std::string &memberId) const {
74 return std::any_of(members_.begin(), members_.end(), [&](const Member &member) {
75 return member.id == memberId;
76 });
77}
78
80 return std::any_of(members_.begin(), members_.end(), [](const Member &member) {
81 return resolve(member.actor) == nullptr;
82 });
83}
84
85std::string Party::getMemberId(int index) const {
86 return index < 0 || static_cast<std::size_t>(index) >= members_.size()
87 ? std::string{}
88 : members_[static_cast<std::size_t>(index)].id;
89}
90
92 return index < 0 || static_cast<std::size_t>(index) >= members_.size()
93 ? nullptr
94 : resolve(members_[static_cast<std::size_t>(index)].actor);
95}
96
97RPGActor *Party::findMemberActor(const std::string &memberId) const {
98 const auto found = std::find_if(members_.begin(), members_.end(), [&](const Member &member) {
99 return member.id == memberId;
100 });
101 return found == members_.end() ? nullptr : resolve(found->actor);
102}
103
105 if (!battle)
107 "battle must not be null", "battle", {}, "rpg.party"));
108 if (members_.empty())
110 "party must contain at least one member", "party", {},
111 "rpg.party"));
112 std::vector<RPGActor *> resolved;
113 resolved.reserve(members_.size());
114 for (std::size_t index = 0; index < members_.size(); ++index) {
115 auto *actor = resolve(members_[index].actor);
116 if (!actor)
118 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "party contains a stale member link",
119 "party.members[" + std::to_string(index) + "]", {}, "rpg.party"));
120 resolved.push_back(actor);
121 }
122 for (auto *actor : resolved) battle->addActor(actor, side);
123 return eve::Result<int>::success(static_cast<int>(resolved.size()));
124}
125
126eve::Result<int> Party::recoverAtCheckpoint(const std::string &healthResource,
127 double healthRatio,
128 const std::string &secondaryResource,
129 double secondaryRatio) {
130 if (members_.empty())
132 "party recovery requires at least one member", "party",
133 {}, "rpg.party"));
134 if (healthResource.empty() || !std::isfinite(healthRatio) || healthRatio <= 0.0 ||
135 healthRatio > 1.0)
137 eve::DiagnosticCode::InvalidArgument, "party recovery requires a health resource and ratio in (0, 1]",
138 "healthRatio", {}, "rpg.party"));
139 if ((!secondaryResource.empty() && secondaryResource == healthResource) ||
140 !std::isfinite(secondaryRatio) || secondaryRatio < 0.0 || secondaryRatio > 1.0)
142 eve::DiagnosticCode::InvalidArgument, "secondary recovery resource must be distinct and ratio in [0, 1]",
143 "secondaryRatio", {}, "rpg.party"));
144 std::vector<RPGActor *> actors;
145 std::vector<double> healthValues;
146 std::vector<double> secondaryValues;
147 actors.reserve(members_.size());
148 healthValues.reserve(members_.size());
149 secondaryValues.reserve(members_.size());
150 for (std::size_t index = 0; index < members_.size(); ++index) {
151 auto *actor = resolve(members_[index].actor);
152 if (!actor)
154 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "party recovery found a stale member",
155 "members[" + std::to_string(index) + "]", {}, "rpg.party"));
156 const double healthMax = actor->getMax(healthResource);
157 if (!std::isfinite(healthMax) || healthMax <= 0.0)
159 eve::DiagnosticCode::PreconditionViolation, "party member has no positive finite health maximum",
160 "members[" + std::to_string(index) + "]." + healthResource, {}, "rpg.party"));
161 double secondaryMax = 0.0;
162 if (!secondaryResource.empty()) {
163 secondaryMax = actor->getMax(secondaryResource);
164 if (!std::isfinite(secondaryMax) || secondaryMax < 0.0)
167 "party member has an invalid secondary resource maximum",
168 "members[" + std::to_string(index) + "]." + secondaryResource, {}, "rpg.party"));
169 }
170 actors.push_back(actor);
171 healthValues.push_back(healthMax * healthRatio);
172 secondaryValues.push_back(secondaryMax * secondaryRatio);
173 }
174 for (std::size_t index = 0; index < actors.size(); ++index) {
175 actors[index]->revive(healthResource, healthValues[index]);
176 if (!secondaryResource.empty())
177 actors[index]->setCurrent(secondaryResource, secondaryValues[index]);
178 }
179 return eve::Result<int>::success(static_cast<int>(actors.size()));
180}
181
183 if (members_.empty())
185 "party checkpoint requires at least one member",
186 "party", {}, "rpg.party"));
187 eve::Value::Array memberValues;
188 memberValues.reserve(members_.size());
189 for (std::size_t index = 0; index < members_.size(); ++index) {
190 auto *actor = resolve(members_[index].actor);
191 if (!actor)
193 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "party checkpoint found a stale member",
194 "members[" + std::to_string(index) + "]", {}, "rpg.party"));
195 auto checkpoint = actor->checkpointJson();
196 if (!checkpoint.ok()) return eve::Result<std::string>::failure(checkpoint.status());
197 auto actorValue = eve::Value::fromJson(checkpoint.value());
198 if (!actorValue.ok()) return eve::Result<std::string>::failure(actorValue.status());
200 member.emplace("actor", std::move(actorValue).takeValue());
201 member.emplace("id", eve::Value(members_[index].id));
202 memberValues.emplace_back(eve::Value(std::move(member)));
203 }
205 root.emplace("members", eve::Value(std::move(memberValues)));
206 root.emplace("schema", eve::Value("eve.rpg.party"));
207 root.emplace("version", eve::Value(std::int64_t(1)));
208 return eve::Value(std::move(root)).toJson();
209}
210
211eve::Result<Party::CheckpointCandidate> Party::prepareCheckpointJson(std::string_view json) const {
212 auto parsed = eve::Value::fromJson(json);
213 if (!parsed.ok()) return eve::Result<CheckpointCandidate>::failure(parsed.status());
214 const auto &root = parsed.value();
215 const auto *schema = root.isObject() ? root.find("schema") : nullptr;
216 const auto *version = root.isObject() ? root.find("version") : nullptr;
217 const auto *members = root.isObject() ? root.find("members") : nullptr;
218 if (!schema || !schema->isString() || schema->asString() != "eve.rpg.party")
220 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "checkpoint does not belong to RPG Party",
221 "schema", {}, "rpg.party"));
222 if (!version || !version->isInt64() || version->asInt() != 1)
224 eve::DiagnosticCode::UnknownVersion, "unsupported party checkpoint version", "version", {}, "rpg.party"));
225 if (!members || !members->isArray() || members->arraySize() != members_.size())
228 "checkpoint roster size does not match the live party", "members", {}, "rpg.party"));
229 CheckpointCandidate candidate;
230 candidate.actors.reserve(members_.size());
231 for (std::size_t index = 0; index < members_.size(); ++index) {
232 const auto &member = members->at(index);
233 const auto *id = member.isObject() ? member.find("id") : nullptr;
234 const auto *actorValue = member.isObject() ? member.find("actor") : nullptr;
235 if (!id || !id->isString() || id->asString() != members_[index].id)
237 eve::DiagnosticCode::Conflict, "checkpoint member ID/order does not match the live party",
238 "members[" + std::to_string(index) + "].id", {}, "rpg.party"));
239 if (!actorValue || !actorValue->isObject())
241 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "checkpoint member actor must be an object",
242 "members[" + std::to_string(index) + "].actor", {}, "rpg.party"));
243 auto *actor = resolve(members_[index].actor);
244 if (!actor)
246 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "live party contains a stale member",
247 "members[" + std::to_string(index) + "]", {}, "rpg.party"));
248 auto actorJson = actorValue->toJson();
249 if (!actorJson.ok()) return eve::Result<CheckpointCandidate>::failure(actorJson.status());
250 auto prepared = actor->prepareCheckpointJson(actorJson.value());
251 if (!prepared.ok()) return eve::Result<CheckpointCandidate>::failure(prepared.status());
252 candidate.actors.push_back(std::move(prepared).takeValue());
253 }
254 return eve::Result<CheckpointCandidate>::success(std::move(candidate));
255}
256
257void Party::commitCheckpoint(CheckpointCandidate candidate) noexcept {
258 for (std::size_t index = 0; index < members_.size(); ++index) {
259 if (auto *actor = resolve(members_[index].actor))
260 actor->commitCheckpoint(std::move(candidate.actors[index]));
261 }
262}
263
265 auto prepared = prepareCheckpointJson(json);
266 if (!prepared.ok()) return eve::Result<void>::failure(prepared.status());
267 commitCheckpoint(std::move(prepared).takeValue());
269}
270
271} // namespace eve::rpg
double value
int root
Definition AnimSmr.cpp:119
回合制战斗对象:参战者、行动顺序、目标选取、行动执行。
HexVec3 left
HexVec3 right
Generation-safe ordered RPG party relationships.
std::string id
Definition PlayHost.cpp:108
PrimitiveHandle handle
bool found
ecs::EntityHandle side
float(ui::Theme::* member)[4]
std::map< std::string, std::span< const std::uint8_t > > members
uint32_t index
Owning, renderer-independent dynamic values.
当前资源(hp/mana...)的受伤 / 治疗 / 复活 / 死亡。
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
一场回合制战斗(任意阵营数)。 @ownership 由调用方创建/销毁;Battle 不持有参与者的所有权(参与者为借用 ECS actor)。 @thread 在单一模拟线程上驱动整场战斗。
Definition Battle.h:54
void addActor(RPGActor *actor, int side)
加入参战者;side 为任意阵营 id。
Definition Battle.cpp:17
RPGActor * getMemberActor(int index) const
Resolve a member by ordered index.
Definition Party.cpp:91
eve::Result< int > recoverAtCheckpoint(const std::string &healthResource, double healthRatio, const std::string &secondaryResource={}, double secondaryRatio=0.0)
Atomically restore every live member at a safe checkpoint.
Definition Party.cpp:126
eve::Result< void > restoreCheckpointJson(std::string_view json)
Validate and atomically restore every existing party member actor.
Definition Party.cpp:264
eve::Result< int > addToBattle(Battle *battle, int side) const
Add every live roster member to a battle side after validating the complete roster.
Definition Party.cpp:104
RPGActor * findMemberActor(const std::string &memberId) const
Resolve a member by stable ID.
Definition Party.cpp:97
void clear()
Clear every relationship without destroying actors.
Definition Party.cpp:70
eve::Result< void > removeMember(const std::string &memberId)
Remove one member ID without destroying its actor.
Definition Party.cpp:59
int count() const
Returns the number of .
Definition Party.cpp:71
bool contains(const std::string &memberId) const
True if active.
Definition Party.cpp:73
eve::Result< std::string > checkpointJson() const
Capture ordered member IDs and every actor safe checkpoint into one versioned document.
Definition Party.cpp:182
std::string getMemberId(int index) const
Returns the member id.
Definition Party.cpp:85
bool hasStaleMembers() const
True when stale members.
Definition Party.cpp:79
eve::Result< void > addMember(const std::string &memberId, RPGActor *actor)
Append one unique stable member ID and live actor as one atomic roster mutation.
Definition Party.cpp:35
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。