载入中...
搜索中...
未找到
StatusSystem.cpp
浏览该文件的文档.
1#include "rpg/StatusSystem.h"
2#include "rpg/RPGActor.h"
3#include "rpg/Effect.h"
5
6#include <algorithm>
7#include <limits>
8#include <unordered_map>
9
10namespace eve::rpg {
11
12namespace {
13
14std::vector<StatusTickEvent> &tickQueue() {
15 static std::vector<StatusTickEvent> q;
16 return q;
17}
18
19std::vector<StatusChangeEvent> &changeQueue() {
20 static std::vector<StatusChangeEvent> q;
21 return q;
22}
23
24void unapplyInstance(RPGActor *actor, StatusInstance &inst) {
25 for (auto &kv : inst.appliedModifiers)
26 AttributeSystem::removeModifier(actor, kv.first, kv.second);
27 inst.appliedModifiers.clear();
28}
29
31void applyModifiersForInstance(RPGActor *actor, const EffectDefinition &def, StatusInstance &inst) {
32 unapplyInstance(actor, inst);
33 const std::string source = "effect:" + def.id + "#" + std::to_string(inst.instanceId);
34 for (const auto &spec : def.modifiers) {
35 const double value = spec.value * double(inst.stacks);
36 std::string modId =
37 AttributeSystem::addModifier(actor, spec.attribute, source, spec.op, value, spec.priority);
38 inst.appliedModifiers.emplace_back(spec.attribute, modId);
39 }
40}
41
42bool isBuiltinStackPolicy(const std::string &sp) {
43 return sp == "none" || sp == "refresh" || sp == "extend" || sp == "stack";
44}
45
46} // namespace
47
48std::unordered_map<std::string, StatusSystem::ApplyCondition> &StatusSystem::applyConditions() {
49 static std::unordered_map<std::string, ApplyCondition> t;
50 return t;
51}
52
53std::unordered_map<std::string, StatusSystem::StackPolicyFn> &StatusSystem::stackPolicies() {
54 static std::unordered_map<std::string, StackPolicyFn> t;
55 return t;
56}
57
58std::unordered_map<std::string, StatusSystem::LifecycleHook> &StatusSystem::lifecycleHooks() {
59 static std::unordered_map<std::string, LifecycleHook> t;
60 return t;
61}
62
64 if (!name.empty()) applyConditions()[name] = std::move(fn);
65}
66
68 applyConditions().erase(name);
69}
70
71bool StatusSystem::hasApplyCondition(const std::string &name) {
72 return applyConditions().count(name) > 0;
73}
74
75void StatusSystem::clearApplyConditions() { applyConditions().clear(); }
76
78 if (!name.empty()) stackPolicies()[name] = std::move(fn);
79}
80
81void StatusSystem::unregisterStackPolicy(const std::string &name) { stackPolicies().erase(name); }
82
83bool StatusSystem::hasStackPolicy(const std::string &name) {
84 return stackPolicies().count(name) > 0;
85}
86
87void StatusSystem::clearStackPolicies() { stackPolicies().clear(); }
88
90 if (!name.empty()) lifecycleHooks()[name] = std::move(fn);
91}
92
94 lifecycleHooks().erase(name);
95}
96
97bool StatusSystem::hasLifecycleHook(const std::string &name) {
98 return lifecycleHooks().count(name) > 0;
99}
100
101void StatusSystem::clearLifecycleHooks() { lifecycleHooks().clear(); }
102
103void StatusSystem::emitChange(StatusChangeEvent ev) {
104 changeQueue().push_back(ev);
105 for (auto &kv : lifecycleHooks()) {
106 if (kv.second) kv.second(ev);
107 }
108}
109
110StatusInstance *StatusSystem::findByInstanceId(RPGActor *actor, int instanceId) {
111 if (!actor) return nullptr;
112 auto &active = actor->statuses()->active;
113 auto it = std::find_if(active.begin(), active.end(),
114 [&](const StatusInstance &s) { return s.instanceId == instanceId; });
115 return it == active.end() ? nullptr : &(*it);
116}
117
118int StatusSystem::apply(RPGActor *actor, const std::string &effectId, const std::string &source) {
119 if (!actor) return -1;
120 const EffectDefinition *def = EffectRegistry::find(effectId);
121 if (!def) return -1;
122
123 for (auto &kv : applyConditions()) {
124 if (!kv.second) continue;
125 std::string reason;
126 if (!kv.second(actor, *def, source, reason)) {
127 StatusChangeEvent reject;
128 reject.actor = actor;
129 reject.effectId = effectId;
130 reject.source = source;
131 reject.action = "reject";
132 reject.reason = reason.empty() ? kv.first : reason;
133 emitChange(std::move(reject));
134 return -1;
135 }
136 }
137
138 if (def->durationPolicy == "instant") {
139 // One-shot: nudge base values directly; nothing to track/undo later.
140 for (const auto &spec : def->modifiers) AttributeSystem::modifyBase(actor, spec.attribute, spec.value);
141 StatusChangeEvent applied;
142 applied.actor = actor;
143 applied.effectId = effectId;
144 applied.source = source;
145 applied.action = "apply";
146 applied.stacks = 0;
147 emitChange(std::move(applied));
148 return 0;
149 }
150
151 auto &active = actor->statuses()->active;
152 auto it = std::find_if(active.begin(), active.end(),
153 [&](const StatusInstance &s) { return s.effectId == effectId; });
154
155 if (it != active.end()) {
156 const std::string &sp = def->stackPolicy;
157
158 auto customIt = stackPolicies().find(sp);
159 if (customIt != stackPolicies().end() && customIt->second) {
160 const int result = customIt->second(actor, *it, *def, source);
161 if (result > 0) {
162 // Refresh modifiers if the instance is still present and non-periodic.
163 auto *live = findByInstanceId(actor, result);
164 if (live && def->period <= 0.f) applyModifiersForInstance(actor, *def, *live);
165 StatusChangeEvent stacked;
166 stacked.actor = actor;
167 stacked.instanceId = result;
168 stacked.effectId = effectId;
169 stacked.source = source;
170 stacked.action = "stack";
171 stacked.stacks = live ? live->stacks : it->stacks;
172 emitChange(std::move(stacked));
173 } else if (result < 0) {
174 StatusChangeEvent reject;
175 reject.actor = actor;
176 reject.instanceId = it->instanceId;
177 reject.effectId = effectId;
178 reject.source = source;
179 reject.action = "reject";
180 reject.stacks = it->stacks;
181 reject.reason = "stackPolicy:" + sp;
182 emitChange(std::move(reject));
183 }
184 return result;
185 }
186
187 if (sp == "refresh") {
188 if (def->durationPolicy == "duration") it->remaining = def->duration;
189 StatusChangeEvent refreshed;
190 refreshed.actor = actor;
191 refreshed.instanceId = it->instanceId;
192 refreshed.effectId = effectId;
193 refreshed.source = source;
194 refreshed.action = "refresh";
195 refreshed.stacks = it->stacks;
196 emitChange(std::move(refreshed));
197 return it->instanceId;
198 }
199 if (sp == "extend") {
200 if (def->durationPolicy == "duration" && it->remaining >= 0.f) it->remaining += def->duration;
201 StatusChangeEvent extended;
202 extended.actor = actor;
203 extended.instanceId = it->instanceId;
204 extended.effectId = effectId;
205 extended.source = source;
206 extended.action = "extend";
207 extended.stacks = it->stacks;
208 emitChange(std::move(extended));
209 return it->instanceId;
210 }
211 if (sp == "stack") {
212 const int cap = def->maxStacks > 0 ? def->maxStacks : std::numeric_limits<int>::max();
213 if (it->stacks < cap) it->stacks += 1;
214 if (def->durationPolicy == "duration") it->remaining = def->duration;
215 if (def->period <= 0.f) applyModifiersForInstance(actor, *def, *it);
216 StatusChangeEvent stacked;
217 stacked.actor = actor;
218 stacked.instanceId = it->instanceId;
219 stacked.effectId = effectId;
220 stacked.source = source;
221 stacked.action = "stack";
222 stacked.stacks = it->stacks;
223 emitChange(std::move(stacked));
224 return it->instanceId;
225 }
226 // "none" (or unknown unregistered policy) rejects duplicate application.
227 StatusChangeEvent reject;
228 reject.actor = actor;
229 reject.instanceId = it->instanceId;
230 reject.effectId = effectId;
231 reject.source = source;
232 reject.action = "reject";
233 reject.stacks = it->stacks;
234 reject.reason = isBuiltinStackPolicy(sp) ? "stackPolicy:none" : ("unknownStackPolicy:" + sp);
235 emitChange(std::move(reject));
236 return -1;
237 }
238
239 StatusInstance inst;
240 inst.instanceId = actor->statuses()->nextInstanceId++;
241 inst.effectId = effectId;
242 inst.source = source;
243 inst.stacks = 1;
244 inst.remaining = (def->durationPolicy == "duration") ? def->duration : -1.f;
245 inst.periodAccum = 0.f;
246
247 if (def->period <= 0.f) applyModifiersForInstance(actor, *def, inst);
248
249 const int id = inst.instanceId;
250 const int stacks = inst.stacks;
251 active.push_back(std::move(inst));
252
253 StatusChangeEvent applied;
254 applied.actor = actor;
255 applied.instanceId = id;
256 applied.effectId = effectId;
257 applied.source = source;
258 applied.action = "apply";
259 applied.stacks = stacks;
260 emitChange(std::move(applied));
261 return id;
262}
263
264bool StatusSystem::remove(RPGActor *actor, int instanceId) {
265 if (!actor) return false;
266 auto &active = actor->statuses()->active;
267 auto it = std::find_if(active.begin(), active.end(),
268 [&](const StatusInstance &s) { return s.instanceId == instanceId; });
269 if (it == active.end()) return false;
270
272 removed.actor = actor;
273 removed.instanceId = it->instanceId;
274 removed.effectId = it->effectId;
275 removed.source = it->source;
276 removed.action = "remove";
277 removed.stacks = it->stacks;
278
279 unapplyInstance(actor, *it);
280 active.erase(it);
281 emitChange(std::move(removed));
282 return true;
283}
284
285int StatusSystem::removeByEffect(RPGActor *actor, const std::string &effectId) {
286 if (!actor) return 0;
287 auto &active = actor->statuses()->active;
288 int count = 0;
289 for (auto it = active.begin(); it != active.end();) {
290 if (it->effectId == effectId) {
292 removed.actor = actor;
293 removed.instanceId = it->instanceId;
294 removed.effectId = it->effectId;
295 removed.source = it->source;
296 removed.action = "remove";
297 removed.stacks = it->stacks;
298 unapplyInstance(actor, *it);
299 it = active.erase(it);
300 emitChange(std::move(removed));
301 ++count;
302 } else {
303 ++it;
304 }
305 }
306 return count;
307}
308
309int StatusSystem::removeBySource(RPGActor *actor, const std::string &source) {
310 if (!actor) return 0;
311 auto &active = actor->statuses()->active;
312 int count = 0;
313 for (auto it = active.begin(); it != active.end();) {
314 if (it->source == source) {
316 removed.actor = actor;
317 removed.instanceId = it->instanceId;
318 removed.effectId = it->effectId;
319 removed.source = it->source;
320 removed.action = "remove";
321 removed.stacks = it->stacks;
322 unapplyInstance(actor, *it);
323 it = active.erase(it);
324 emitChange(std::move(removed));
325 ++count;
326 } else {
327 ++it;
328 }
329 }
330 return count;
331}
332
333int StatusSystem::removeByTag(RPGActor *actor, const std::string &tag) {
334 if (!actor) return 0;
335 auto &active = actor->statuses()->active;
336 int count = 0;
337 for (auto it = active.begin(); it != active.end();) {
338 const EffectDefinition *def = EffectRegistry::find(it->effectId);
339 if (def && def->hasTag(tag)) {
341 removed.actor = actor;
342 removed.instanceId = it->instanceId;
343 removed.effectId = it->effectId;
344 removed.source = it->source;
345 removed.action = "remove";
346 removed.stacks = it->stacks;
347 unapplyInstance(actor, *it);
348 it = active.erase(it);
349 emitChange(std::move(removed));
350 ++count;
351 } else {
352 ++it;
353 }
354 }
355 return count;
356}
357
358bool StatusSystem::hasEffect(RPGActor *actor, const std::string &effectId) {
359 if (!actor) return false;
360 auto &active = actor->statuses()->active;
361 return std::find_if(active.begin(), active.end(), [&](const StatusInstance &s) {
362 return s.effectId == effectId;
363 }) != active.end();
364}
365
366bool StatusSystem::hasTag(RPGActor *actor, const std::string &tag) {
367 if (!actor) return false;
368 for (const auto &inst : actor->statuses()->active) {
369 const EffectDefinition *def = EffectRegistry::find(inst.effectId);
370 if (def && def->hasTag(tag)) return true;
371 }
372 return false;
373}
374
376 if (!actor) return 0;
377 return int(actor->statuses()->active.size());
378}
379
380std::string StatusSystem::getActiveEffectId(RPGActor *actor, int index) {
381 if (!actor) return {};
382 auto &active = actor->statuses()->active;
383 if (index < 0 || size_t(index) >= active.size()) return {};
384 return active[size_t(index)].effectId;
385}
386
388 if (!actor) return 0;
389 auto &active = actor->statuses()->active;
390 if (index < 0 || size_t(index) >= active.size()) return 0;
391 return active[size_t(index)].stacks;
392}
393
395 if (!actor) return 0.f;
396 auto &active = actor->statuses()->active;
397 if (index < 0 || size_t(index) >= active.size()) return 0.f;
398 return active[size_t(index)].remaining;
399}
400
402 if (!actor) return 0;
403 auto &active = actor->statuses()->active;
404 if (index < 0 || size_t(index) >= active.size()) return 0;
405 return active[size_t(index)].instanceId;
406}
407
408std::string StatusSystem::getActiveSource(RPGActor *actor, int index) {
409 if (!actor) return {};
410 auto &active = actor->statuses()->active;
411 if (index < 0 || size_t(index) >= active.size()) return {};
412 return active[size_t(index)].source;
413}
414
415std::string StatusSystem::getProp(RPGActor *actor, int instanceId, const std::string &key,
416 const std::string &fallback) {
417 StatusInstance *inst = findByInstanceId(actor, instanceId);
418 if (!inst) return fallback;
419 auto it = inst->props.find(key);
420 return it == inst->props.end() ? fallback : it->second;
421}
422
423bool StatusSystem::setProp(RPGActor *actor, int instanceId, const std::string &key,
424 const std::string &value) {
425 StatusInstance *inst = findByInstanceId(actor, instanceId);
426 if (!inst) return false;
427 inst->props[key] = value;
428 return true;
429}
430
431void StatusSystem::update(float dt) {
432 for (RPGActor *actor : RPGActor::liveActors()) {
433 auto &active = actor->statuses()->active;
434 for (auto it = active.begin(); it != active.end();) {
435 const EffectDefinition *def = EffectRegistry::find(it->effectId);
436 bool expired = false;
437
438 if (it->remaining >= 0.f) {
439 it->remaining -= dt;
440 if (it->remaining <= 0.f) expired = true;
441 }
442
443 if (def && def->period > 0.f) {
444 it->periodAccum += dt;
445 while (it->periodAccum >= def->period) {
446 it->periodAccum -= def->period;
447 StatusTickEvent evt;
448 evt.actor = actor;
449 evt.instanceId = it->instanceId;
450 evt.effectId = it->effectId;
451 evt.source = it->source;
452 evt.stacks = it->stacks;
453 tickQueue().push_back(std::move(evt));
454 }
455 }
456
457 if (expired) {
458 StatusChangeEvent expiredEv;
459 expiredEv.actor = actor;
460 expiredEv.instanceId = it->instanceId;
461 expiredEv.effectId = it->effectId;
462 expiredEv.source = it->source;
463 expiredEv.action = "expire";
464 expiredEv.stacks = it->stacks;
465 unapplyInstance(actor, *it);
466 it = active.erase(it);
467 emitChange(std::move(expiredEv));
468 } else {
469 ++it;
470 }
471 }
472 }
473}
474
475void StatusSystem::pollTicks(std::vector<StatusTickEvent> &out) {
476 auto &q = tickQueue();
477 for (auto &e : q) out.push_back(std::move(e));
478 q.clear();
479}
480
481void StatusSystem::pollChanges(std::vector<StatusChangeEvent> &out) {
482 auto &q = changeQueue();
483 for (auto &e : q) out.push_back(std::move(e));
484 q.clear();
485}
486
487} // namespace eve::rpg
bool active
Definition CardTypes.cpp:34
std::string value
bool removed
std::string id
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
uint32_t s
Definition Weather.cpp:28
static std::string addModifier(RPGActor *actor, const std::string &attribute, const std::string &source, const std::string &op, double value, int priority=0)
添加一条修改器,返回自动生成的唯一 id(用于之后精确移除)。 空字符串 id 表示失败(actor 为空)。
static void modifyBase(RPGActor *actor, const std::string &attribute, double delta)
增量修改基础值(如自然回蓝、升级加点)。
static const EffectDefinition * find(const std::string &id)
Definition Effect.cpp:30
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:28
static const std::vector< RPGActor * > & liveActors()
返回所有通过 createActor() 创建、且当前仍存活的 actor。 StatusSystem::update / SkillSystem::update 用它遍历所有 actor 逐帧推进。 ...
Definition RPGActor.cpp:33
static bool setProp(RPGActor *actor, int instanceId, const std::string &key, const std::string &value)
static void clearStackPolicies()
static std::string getActiveSource(RPGActor *actor, int index)
static void unregisterLifecycleHook(const std::string &name)
static int apply(RPGActor *actor, const std::string &effectId, const std::string &source="")
施加一个效果。返回值: -1 — 失败(actor 为空 / 效果不存在 / 条件拒绝 / stackPolicy 拒绝重复) 0 — durationPolicy=="instant":已立即生效,无...
std::function< bool(RPGActor *actor, const EffectDefinition &def, const std::string &source, std::string &outReason)> ApplyCondition
施加前条件:返回 false 表示拒绝施加。 全部已注册条件按注册表遍历(AND);任一失败则 apply 返回 -1 并产生 action="reject" 事件。
std::function< int(RPGActor *actor, StatusInstance &existing, const EffectDefinition &def, const std::string &source)> StackPolicyFn
自定义叠加策略:当已存在同 effectId 实例且 stackPolicy 名命中本注册表时调用。 返回值约定与 apply() 相同(-1 拒绝 / >0 实例 id)。策略内部可改写 stacks...
static void update(float dt)
遍历 RPGActor::liveActors():倒计时长、到期移除、周期效果产生 tick 事件。
static int getActiveStacks(RPGActor *actor, int index)
static bool hasApplyCondition(const std::string &name)
static bool hasStackPolicy(const std::string &name)
static void pollChanges(std::vector< StatusChangeEvent > &out)
取出并清空自上次调用以来累积的生命周期变更事件。
static bool hasLifecycleHook(const std::string &name)
static int getActiveInstanceId(RPGActor *actor, int index)
static bool hasEffect(RPGActor *actor, const std::string &effectId)
static void unregisterApplyCondition(const std::string &name)
static void clearLifecycleHooks()
static void pollTicks(std::vector< StatusTickEvent > &out)
取出并清空自上次调用以来累积的周期 tick 事件(push/poll 风格,见 event 模块)。
static bool hasTag(RPGActor *actor, const std::string &tag)
是否存在任一效果定义带有该 tag 的活动实例。
static int removeBySource(RPGActor *actor, const std::string &source)
移除该 actor 身上所有 source 匹配的实例;返回移除数量。
static bool remove(RPGActor *actor, int instanceId)
按实例 id 精确移除(撤销其属性修改器);返回是否命中。
static void registerApplyCondition(const std::string &name, ApplyCondition fn)
static void registerStackPolicy(const std::string &name, StackPolicyFn fn)
static int getActiveCount(RPGActor *actor)
static std::string getProp(RPGActor *actor, int instanceId, const std::string &key, const std::string &fallback={})
按实例 id 读写 props;找不到实例时 get 返回 fallback,set 返回 false。
static void registerLifecycleHook(const std::string &name, LifecycleHook fn)
static int removeByEffect(RPGActor *actor, const std::string &effectId)
移除该 actor 身上所有 effectId 匹配的实例;返回移除数量。
static void unregisterStackPolicy(const std::string &name)
static void clearApplyConditions()
std::function< void(const StatusChangeEvent &ev)> LifecycleHook
生命周期钩子:每次产生 StatusChangeEvent 时同步回调(在事件入队之后)。
static int removeByTag(RPGActor *actor, const std::string &tag)
移除该 actor 身上所有 tag 匹配(效果定义带该 tag)的实例;返回移除数量。
static std::string getActiveEffectId(RPGActor *actor, int index)
static float getActiveRemaining(RPGActor *actor, int index)
RPG 模块入口:属性 / 效果 / 状态 / 技能 / 结算五套系统的脚本绑定与帧调度点。
float duration
durationPolicy == "duration" 时使用(秒)
Definition Effect.h:37
float period
> 0 时为周期效果:每隔 period 秒产生一次 StatusTickEvent。
Definition Effect.h:40
std::string durationPolicy
"instant" | "duration" | "infinite"(未知值按 "instant" 处理)。
Definition Effect.h:36
bool hasTag(const std::string &tag) const
Definition Effect.cpp:11
std::string stackPolicy
"none" | "refresh" | "extend" | "stack",或 StatusSystem::registerStackPolicy 注册的名字。 未知且未注册时按 "none" 处理...
Definition Effect.h:46
std::vector< EffectModifierSpec > modifiers
durationPolicy 为 duration/infinite 且 period<=0 时,apply 会直接写入这些修改器。
Definition Effect.h:50
状态生命周期变更事件(施加 / 刷新 / 叠层 / 延长 / 移除 / 到期 / 拒绝)。 与 StatusTickEvent 分开:tick 是周期性数值触发,change 是实例结构变化。
Definition StatusTypes.h:59
std::string reason
reject / 自定义条件失败时的说明;其它情况可空
Definition StatusTypes.h:66
状态实例:某个 Effect 施加到某个 actor 后的运行时记录。
Definition StatusTypes.h:20
float remaining
剩余时间(秒);-1 表示 infinite(永久,直到手动移除)
Definition StatusTypes.h:25
std::string source
施加来源标签(施法者 id / 技能 id / 自定义),供查询与批量移除
Definition StatusTypes.h:23
std::unordered_map< std::string, std::string > props
运行时自定义键值(图标路径、UI 着色、脚本标记……)。 与 EffectDefinition::extra(定义侧)互补:extra 是模板数据,props 是实例数据。
Definition StatusTypes.h:35
float periodAccum
周期效果:距离上次 tick 的累积时间
Definition StatusTypes.h:26
周期性状态触发的 tick 事件。周期效果(period > 0)不会自动修改属性, 而是每个周期产生一个 tick 事件交给上层(脚本或 C++ 结算系统)处理—— 这样伤害/治疗类周期效果可以完整地...
Definition StatusTypes.h:44
class RPGActor * actor
Definition StatusTypes.h:45