载入中...
搜索中...
未找到
WeaponSystem.cpp
浏览该文件的文档.
2
3#include "common/Capability.h"
6
7#include <algorithm>
8#include <cmath>
9#include <unordered_map>
10#include <vector>
11
12namespace eve::weapon {
13
14namespace {
15
16std::unordered_map<std::string, IWeaponLogic*>& logicRegistry() {
17 static std::unordered_map<std::string, IWeaponLogic*> registry;
18 return registry;
19}
20
21WeaponEventSink& eventSink() {
22 static WeaponEventSink sink;
23 return sink;
24}
25
26void pushEvent(const WeaponEvent& e) {
27 if (eventSink()) eventSink()(e);
28}
29
31float angleDelta(float from, float to) {
32 float d = std::fmod(to - from + 180.f, 360.f);
33 if (d < 0.f) d += 360.f;
34 return d - 180.f;
35}
36
38class HitscanLogic : public IWeaponLogic {
39public:
40 const char* name() const override { return "hitscan"; }
41 bool canFire(const WeaponEntity&) const override { return true; }
42 void fire(WeaponEntity&, const AttackRequest&) override {}
43 void update(WeaponEntity&, float) override {}
44};
45
47class ProjectileLogic : public IWeaponLogic {
48public:
49 const char* name() const override { return "projectile"; }
50 bool canFire(const WeaponEntity&) const override { return true; }
51 void fire(WeaponEntity& w, const AttackRequest& req) override {
52 if (IProjectileService* svc = eve::cap::query<IProjectileService>()) svc->spawnProjectile(w, req);
53 }
54 void update(WeaponEntity&, float) override {}
55};
56
57const WeaponDefinition* defOf(WeaponEntity& w);
58void pushFireEvent(WeaponEntity& w, const WeaponDefinition* def, const AttackRequest& req, int ammo);
59
61class MeleeLogic : public IWeaponLogic {
62public:
63 const char* name() const override { return "melee"; }
64 bool canFire(const WeaponEntity&) const override { return true; }
65 void fire(WeaponEntity& w, const AttackRequest& req) override {
66 const WeaponDefinition* def = defOf(w);
67 if (!def) return;
68 pushFireEvent(w, def, req, static_cast<int>(w.state()->resource.value));
69 }
70 void update(WeaponEntity&, float) override {}
71};
72
73HitscanLogic gHitscan;
74ProjectileLogic gProjectile;
75MeleeLogic gMelee;
76
77const WeaponDefinition* defOf(WeaponEntity& w) { return w.definition()->def; }
78
80bool usesStageMachine(const WeaponDefinition* def) {
81 return def != nullptr && def->kind != WeaponKind::Ranged;
82}
83
84void pushFireEvent(WeaponEntity& w, const WeaponDefinition* def, const AttackRequest& req, int ammo) {
85 WeaponEvent e;
86 e.type = WeaponEventType::Fire;
87 e.weaponId = w.identity()->id;
88 e.defId = def->id;
89 e.ammoLeft = ammo;
90 e.x = req.muzzleX;
91 e.y = req.muzzleY;
92 e.z = req.muzzleZ;
93 e.arc = req.arcAngle;
94 e.aoe = req.aoeRadius;
95 e.spread = req.spread;
96 e.pellets = std::max(1, req.pelletCount);
97 e.recoilPitch = def->recoilPitch;
98 e.recoilYaw = def->recoilYaw;
99 e.damageType = def->damageType;
100 e.element = def->element;
101 pushEvent(e);
102}
103
105void updateFeel(WeaponEntity& w, const WeaponDefinition* def, float dt) {
106 auto state = w.state();
107 // 散布回稳:从当前连射散布衰减回 spreadMin。
108 if (def->spreadRecover > 0.f && state->currentSpread > def->spreadMin) {
109 state->currentSpread = std::max(def->spreadMin, state->currentSpread - def->spreadRecover * dt);
110 }
111 // 后坐回正:向 0 收敛。
112 if (def->recoilRecover > 0.f) {
113 const float p = std::min(std::fabs(state->recoilPitch), def->recoilRecover * dt);
114 state->recoilPitch += state->recoilPitch > 0.f ? -p : p;
115 const float y = std::min(std::fabs(state->recoilYaw), def->recoilRecover * dt);
116 state->recoilYaw += state->recoilYaw > 0.f ? -y : y;
117 }
118}
119
121float applyBloom(WeaponEntity& w, const WeaponDefinition* def) {
122 auto state = w.state();
123 if (def->spreadPerShot > 0.f && def->spreadMax > def->spreadMin) {
124 state->currentSpread = std::min(def->spreadMax, state->currentSpread + def->spreadPerShot);
125 }
126 return state->currentSpread;
127}
128
130int spawnPellets(WeaponEntity& w, const WeaponDefinition* def, AttackRequest& r) {
131 const int n = std::max(1, def->projectile.pelletCount);
132 r.pelletCount = n;
133 if (IWeaponLogic* logic = WeaponSystem::findLogic(def->logic)) {
134 for (int i = 0; i < n; ++i) {
135 r.pelletIndex = i;
136 logic->fire(w, r);
137 }
138 }
139 return n;
140}
141
142void pushEmptyEvent(WeaponEntity& w, const WeaponDefinition* def) {
143 WeaponEvent e;
144 e.type = WeaponEventType::Empty;
145 e.weaponId = w.identity()->id;
146 e.defId = def->id;
147 e.ammoLeft = 0;
148 pushEvent(e);
149}
150
151void pushReloadEnd(WeaponEntity& w, const WeaponDefinition* def, int ammo) {
152 WeaponEvent e;
154 e.weaponId = w.identity()->id;
155 e.defId = def->id;
156 e.ammoLeft = ammo;
157 pushEvent(e);
158}
159
160// ---------------------------------------------------------------------------
161// 热武器(ranged):成熟管线
162// ---------------------------------------------------------------------------
163
164bool canFireRanged(WeaponEntity& w, const WeaponDefinition* def) {
165 const auto state = w.state();
166 if (state->resource.reloading || state->jammed) return false;
167 if (state->cooldown > 0.f) return false;
168 if (state->resource.value <= 0.f) return false;
169 const IWeaponLogic* logic = WeaponSystem::findLogic(def->logic);
170 return logic == nullptr || logic->canFire(w);
171}
172
173bool tryFireRanged(WeaponEntity& w, const AttackRequest& req, const WeaponDefinition* def) {
174 if (!canFireRanged(w, def)) return false;
175
176 auto state = w.state();
177 state->resource.value -= 1.f;
178
179 const float effSpread = applyBloom(w, def);
180 state->recoilPitch += def->recoilPitch;
181 state->recoilYaw += def->recoilYaw;
182
183 AttackRequest r = req;
184 r.yaw = w.aim()->yaw;
185 r.pitch = w.aim()->pitch;
186 r.spread = effSpread;
187 const int pellets = spawnPellets(w, def, r);
188
189 state->cooldown = def->cooldown;
190 if (state->selector == FireMode::Burst && def->burstSize > 1) {
191 state->burstRemaining = def->burstSize - 1;
192 state->burstTimer = def->burstInterval;
193 }
194
195 r.pelletCount = pellets;
196 pushFireEvent(w, def, r, static_cast<int>(state->resource.value));
197 if (state->resource.value <= 0.f) pushEmptyEvent(w, def);
198 return true;
199}
200
201void updateRanged(WeaponEntity& w, const WeaponDefinition* def, float dt) {
202 auto state = w.state();
203 Resource& r = state->resource;
204 state->cooldown = std::max(0.f, state->cooldown - dt);
205 updateFeel(w, def, dt);
206
207 // 自动装填:弹匣打空、冷却结束且有备弹时自动开始(事件照发,游戏侧可覆盖/取消)。
208 if (!r.reloading && r.value <= 0.f && state->cooldown <= 0.f && !state->jammed) {
210 }
211
212 // 装填推进
213 if (r.reloading) {
214 r.reloadProgress += dt;
215 if (r.reloadProgress >= def->reloadTime) {
216 const int missing = static_cast<int>(r.max) - static_cast<int>(r.value);
217 if (def->reserveSize < 0) {
218 r.value = r.max;
219 } else if (AmmoPoolEntity* pool = state->ammoPool) {
220 const int take = std::min(missing, pool->state()->count);
221 r.value += static_cast<float>(take);
222 pool->state()->count -= take;
223 } else {
224 const int take = std::min(missing, r.reserve);
225 r.value += static_cast<float>(take);
226 r.reserve -= take;
227 }
228 r.reloading = false;
229 r.reloadProgress = 0.f;
230 pushReloadEnd(w, def, static_cast<int>(r.value));
231 }
232 }
233
234 // 连发续射(第一次扣弹在 tryFire,续发在这里)
235 if (!r.reloading && state->burstRemaining > 0) {
236 state->burstTimer -= dt;
237 if (state->burstTimer <= 0.f) {
238 if (r.value > 0.f) {
239 r.value -= 1.f;
240 AttackRequest req;
241 req.yaw = w.aim()->yaw;
242 req.pitch = w.aim()->pitch;
243 req.spread = applyBloom(w, def);
244 state->recoilPitch += def->recoilPitch;
245 state->recoilYaw += def->recoilYaw;
246 const int pellets = spawnPellets(w, def, req);
247 state->cooldown = def->cooldown;
248 state->burstRemaining--;
249 state->burstTimer = def->burstInterval;
250
251 req.pelletCount = pellets;
252 pushFireEvent(w, def, req, static_cast<int>(r.value));
253
254 if (r.value <= 0.f) {
255 state->burstRemaining = 0;
256 pushEmptyEvent(w, def);
257 }
258 } else {
259 state->burstRemaining = 0;
260 }
261 }
262 }
263
264 if (IWeaponLogic* logic = WeaponSystem::findLogic(def->logic)) logic->update(w, dt);
266}
267
268// ---------------------------------------------------------------------------
269// 阶段机(近战 / 法杖 / 导弹等非热武器形态)
270// ---------------------------------------------------------------------------
271
272bool canAttackStage(WeaponEntity& w, const WeaponDefinition* def) {
273 const auto state = w.state();
274 if (state->stage != AttackStage::Idle) return false;
275 if (state->jammed) return false;
276 if (state->cooldown > 0.f) return false;
277 const Resource& r = state->resource;
278 if (r.kind == ResourceKind::Mana || r.kind == ResourceKind::Stamina) {
279 const auto attribute = r.kind == ResourceKind::Mana ? WeaponAttributeAdapter::manaAttribute
281 auto current = WeaponAttributeAdapter::read(w, attribute);
282 if (!current) {
283 current.ignore("legacy bool canFire cannot expose weapon attribute diagnostics");
284 return false;
285 }
286 if (!r.infinite && current.value() < static_cast<double>(r.cost)) return false;
287 } else if (!r.infinite && r.value < r.cost) {
288 return false;
289 }
290 const IWeaponLogic* logic = WeaponSystem::findLogic(def->logic);
291 return logic == nullptr || logic->canFire(w);
292}
293
294bool tryAttackStage(WeaponEntity& w, const AttackRequest& req, const WeaponDefinition* def) {
295 if (!canAttackStage(w, def)) return false;
296
297 auto state = w.state();
298 Resource& r = state->resource;
299 if (r.kind == ResourceKind::Mana || r.kind == ResourceKind::Stamina) {
301 if (!consumed) {
302 consumed.ignore("legacy bool tryFire cannot expose weapon attribute diagnostics");
303 return false;
304 }
305 } else if (!r.infinite) {
306 r.value = std::max(0.f, r.value - r.cost);
307 }
308
309 state->stage = AttackStage::Windup;
310 state->stageTimer = 0.f;
311 state->lastRequest = req;
312 if (IWeaponLogic* logic = WeaponSystem::findLogic(def->logic)) logic->begin(w, req);
313
314 WeaponEvent e;
316 e.weaponId = w.identity()->id;
317 e.defId = def->id;
318 e.ammoLeft = static_cast<int>(r.value);
319 pushEvent(e);
320 return true;
321}
322
323void updateStage(WeaponEntity& w, const WeaponDefinition* def, float dt) {
324 auto state = w.state();
325 state->cooldown = std::max(0.f, state->cooldown - dt);
326 updateFeel(w, def, dt);
327
328 // 资源回复(法力 / 充能)
329 Resource& r = state->resource;
330 if (r.regen > 0.f && r.value < r.max) r.value = std::min(r.max, r.value + r.regen * dt);
331
332 const AttackStageSpec* stages = state->stages != nullptr ? state->stages : &def->stages;
333 state->stageTimer += dt;
334
335 if (state->stage == AttackStage::Windup && state->stageTimer >= stages->windupTime) {
336 // Windup → Active:命中/生效时刻
337 state->stage = AttackStage::Active;
338 state->stageTimer = 0.f;
339 AttackRequest req = state->lastRequest;
340 req.yaw = w.aim()->yaw;
341 req.pitch = w.aim()->pitch;
342 req.spread = applyBloom(w, def);
343 const int pellets = spawnPellets(w, def, req);
344 req.pelletCount = pellets;
345 pushFireEvent(w, def, req, static_cast<int>(r.value));
346 if (!r.infinite && r.value <= 0.f) pushEmptyEvent(w, def);
347 } else if (state->stage == AttackStage::Active) {
348 if (IWeaponLogic* logic = WeaponSystem::findLogic(def->logic)) logic->channel(w, state->lastRequest, dt);
349 if (state->stageTimer >= stages->activeTime) {
351 state->stageTimer = 0.f;
352 if (IWeaponLogic* logic = WeaponSystem::findLogic(def->logic)) logic->end(w, state->lastRequest);
353 WeaponEvent e;
355 e.weaponId = w.identity()->id;
356 e.defId = def->id;
357 e.ammoLeft = static_cast<int>(r.value);
358 pushEvent(e);
359 }
360 } else if (state->stage == AttackStage::Recover && state->stageTimer >= stages->recoverTime) {
361 state->stage = AttackStage::Idle;
362 state->stageTimer = 0.f;
363 }
364
365 if (IWeaponLogic* logic = WeaponSystem::findLogic(def->logic)) logic->update(w, dt);
367}
368
369} // namespace
370
372 if (logic == nullptr) return;
373 logicRegistry()[logic->name()] = logic;
374}
375
377 auto it = logicRegistry().find(name);
378 return it == logicRegistry().end() ? nullptr : it->second;
379}
380
381int WeaponSystem::logicCount() { return static_cast<int>(logicRegistry().size()); }
382
383void WeaponSystem::setEventSink(WeaponEventSink sink) { eventSink() = std::move(sink); }
384
385void WeaponSystem::emitEvent(const WeaponEvent& e) { pushEvent(e); }
386
388 const WeaponDefinition* def = defOf(w);
389 if (def == nullptr) return;
390 if (usesStageMachine(def)) {
391 updateStage(w, def, dt);
392 } else {
393 updateRanged(w, def, dt);
394 }
395}
396
398 const WeaponDefinition* def = defOf(w);
399 if (def == nullptr) return false;
400 if (usesStageMachine(def)) return tryAttackStage(w, req, def);
401 return tryFireRanged(w, req, def);
402}
403
405 const WeaponDefinition* def = defOf(w);
406 if (def == nullptr) return false;
407 if (usesStageMachine(def)) return canAttackStage(w, def);
408 return canFireRanged(w, def);
409}
410
412 const WeaponDefinition* def = defOf(w);
413 if (def == nullptr) return;
414 auto state = w.state();
415 Resource& r = state->resource;
416 if (r.reloading || r.value >= r.max) return;
417 if (AmmoPoolEntity* pool = state->ammoPool) {
418 if (pool->state()->count <= 0) return;
419 } else if (def->reserveSize == 0 || r.reserve <= 0) {
420 return;
421 }
422
423 r.reloading = true;
424 r.reloadProgress = 0.f;
425 state->burstRemaining = 0;
426
427 WeaponEvent e;
429 e.weaponId = w.identity()->id;
430 e.defId = def->id;
431 e.ammoLeft = static_cast<int>(r.value);
432 pushEvent(e);
433}
434
436 w.state()->resource.reloading = false;
437 w.state()->resource.reloadProgress = 0.f;
438}
439
441 auto aim = w.aim();
442 if (aim->turnSpeed <= 0.f) {
443 aim->yaw = aim->desiredYaw;
444 aim->pitch = aim->desiredPitch;
445 return true;
446 }
447 const float step = aim->turnSpeed * dt;
448 const float dyaw = angleDelta(aim->yaw, aim->desiredYaw);
449 const float dpitch = aim->desiredPitch - aim->pitch;
450 aim->yaw += (std::fabs(dyaw) <= step) ? dyaw : (dyaw > 0.f ? step : -step);
451 aim->pitch += (std::fabs(dpitch) <= step) ? dpitch : (dpitch > 0.f ? step : -step);
452 return std::fabs(angleDelta(aim->yaw, aim->desiredYaw)) < 0.01f &&
453 std::fabs(aim->pitch - aim->desiredPitch) < 0.01f;
454}
455
456} // namespace eve::weapon
457
458// 静态注册内置逻辑(链接期执行,模块加载即生效)。
459namespace eve::weapon {
460namespace {
461
462struct BuiltinLogicRegistrar {
463 BuiltinLogicRegistrar() {
465 WeaponSystem::registerLogic(&gProjectile);
467 }
468};
469
470BuiltinLogicRegistrar gBuiltinRegistrar;
471
472} // namespace
473} // namespace eve::weapon
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
std::string from
glm::vec4 p[6]
glm::vec3 n
Definition Grass.cpp:63
double r
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string name
float d
double current
float step
Definition TreeMesh.cpp:314
Selective canonical mana/stamina adapter for weapons.
共享弹药池实体:多把武器共用的备用弹药,装填时从池中取。
武器逻辑接口:注册到 WeaponSystem 的可插拔行为。
Definition WeaponLogic.h:22
virtual void end(WeaponEntity &w, const AttackRequest &req)
阶段机进入 Recover 时调用(默认空实现)。
Definition WeaponLogic.h:53
virtual const char * name() const =0
稳定名字("hitscan" / "projectile" / "melee" / 自定义名)。
static eve::Result< double > read(WeaponEntity &weapon, std::string_view attribute)
Read a selected final mana/stamina attribute.
static constexpr std::string_view manaAttribute
static constexpr std::string_view staminaAttribute
static eve::Result< void > consumeTriggerResource(WeaponEntity &weapon)
Consume the current weapon's canonical Mana/Stamina trigger cost.
武器实体:数据全部在组件里,行为在 WeaponSystem。
static void update(WeaponEntity &w, float dt)
每帧推进:冷却 / 连发 / 装填 / 炮口旋转 / 逻辑自身。
static IWeaponLogic * findLogic(const std::string &name)
按名字取逻辑;未注册返回 nullptr。
static bool canFire(WeaponEntity &w)
是否可开火(未装填/未卡壳/冷却完毕/有弹药/逻辑放行)。
static void registerLogic(IWeaponLogic *logic)
注册武器逻辑实现;同名替换。
static void setEventSink(WeaponEventSink sink)
注册事件汇(替换旧的;传 nullptr 清空)。
static void cancelReload(WeaponEntity &w)
打断装填。
static bool tryFire(WeaponEntity &w, const FireRequest &req)
尝试开火;满足条件时扣弹药、调逻辑、推事件并返回 true。
static void startReload(WeaponEntity &w)
开始装填(有备用弹药且弹匣不满时生效)。
static void emitEvent(const WeaponEvent &e)
向事件汇推一条事件(模块/脚本便捷入口)。
static bool updateAim(WeaponEntity &w, float dt)
炮口朝目标旋转;返回本帧是否到位。
static int logicCount()
已注册逻辑数量。
载具模块入口(eve.Vehicle):定义注册 / 实体工厂 / RTS 命令 / 帧调度。 设计文档:docs/dev/通用载具系统设计.md
std::function< void(const WeaponEvent &)> WeaponEventSink
事件汇:由 Weapon 模块(或测试)注册,把事件写入自己的队列。
一次攻击请求(C++ 系统 / 脚本发起)。角度均为度。 旧名 FireRequest 保留为别名,向后兼容。
运行时触发资源(实体组件)。
武器模板(registerWeaponsFromJson 注册,进程级注册表)。
武器事件(帧缓存,脚本轮询,与 building/rpg 风格一致)。
bool consumed
Definition Graphics.cpp:184