9#include <unordered_map>
16std::unordered_map<std::string, IWeaponLogic*>& logicRegistry() {
17 static std::unordered_map<std::string, IWeaponLogic*> registry;
26void pushEvent(
const WeaponEvent& e) {
27 if (eventSink()) eventSink()(e);
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;
38class HitscanLogic :
public IWeaponLogic {
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 {}
47class ProjectileLogic :
public IWeaponLogic {
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);
54 void update(WeaponEntity&,
float)
override {}
57const WeaponDefinition* defOf(WeaponEntity&
w);
58void pushFireEvent(WeaponEntity&
w,
const WeaponDefinition* def,
const AttackRequest& req,
int ammo);
61class MeleeLogic :
public IWeaponLogic {
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);
68 pushFireEvent(
w, def, req,
static_cast<int>(
w.state()->resource.value));
70 void update(WeaponEntity&,
float)
override {}
74ProjectileLogic gProjectile;
77const WeaponDefinition* defOf(WeaponEntity&
w) {
return w.definition()->def; }
80bool usesStageMachine(
const WeaponDefinition* def) {
84void pushFireEvent(WeaponEntity&
w,
const WeaponDefinition* def,
const AttackRequest& req,
int ammo) {
87 e.weaponId =
w.identity()->id;
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;
105void updateFeel(WeaponEntity&
w,
const WeaponDefinition* def,
float dt) {
108 if (def->spreadRecover > 0.f &&
state->currentSpread > def->spreadMin) {
109 state->currentSpread = std::max(def->spreadMin,
state->currentSpread - def->spreadRecover * dt);
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);
121float applyBloom(WeaponEntity&
w,
const WeaponDefinition* def) {
123 if (def->spreadPerShot > 0.f && def->spreadMax > def->spreadMin) {
124 state->currentSpread = std::min(def->spreadMax,
state->currentSpread + def->spreadPerShot);
126 return state->currentSpread;
130int spawnPellets(WeaponEntity&
w,
const WeaponDefinition* def, AttackRequest&
r) {
131 const int n = std::max(1, def->projectile.pelletCount);
134 for (
int i = 0; i <
n; ++i) {
142void pushEmptyEvent(WeaponEntity&
w,
const WeaponDefinition* def) {
145 e.weaponId =
w.identity()->id;
151void pushReloadEnd(WeaponEntity&
w,
const WeaponDefinition* def,
int ammo) {
154 e.weaponId =
w.identity()->id;
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;
170 return logic ==
nullptr || logic->canFire(
w);
173bool tryFireRanged(WeaponEntity&
w,
const AttackRequest& req,
const WeaponDefinition* def) {
174 if (!canFireRanged(
w, def))
return false;
177 state->resource.value -= 1.f;
179 const float effSpread = applyBloom(
w, def);
180 state->recoilPitch += def->recoilPitch;
181 state->recoilYaw += def->recoilYaw;
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);
189 state->cooldown = def->cooldown;
191 state->burstRemaining = def->burstSize - 1;
192 state->burstTimer = def->burstInterval;
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);
201void updateRanged(WeaponEntity&
w,
const WeaponDefinition* def,
float dt) {
204 state->cooldown = std::max(0.f,
state->cooldown - dt);
205 updateFeel(
w, def, dt);
208 if (!
r.reloading &&
r.value <= 0.f &&
state->cooldown <= 0.f && !
state->jammed) {
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) {
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;
224 const int take = std::min(missing,
r.reserve);
225 r.value +=
static_cast<float>(take);
229 r.reloadProgress = 0.f;
230 pushReloadEnd(
w, def,
static_cast<int>(
r.value));
235 if (!
r.reloading &&
state->burstRemaining > 0) {
236 state->burstTimer -= dt;
237 if (
state->burstTimer <= 0.f) {
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;
251 req.pelletCount = pellets;
252 pushFireEvent(
w, def, req,
static_cast<int>(
r.value));
254 if (
r.value <= 0.f) {
255 state->burstRemaining = 0;
256 pushEmptyEvent(
w, def);
259 state->burstRemaining = 0;
272bool canAttackStage(WeaponEntity&
w,
const WeaponDefinition* def) {
273 const auto state =
w.state();
275 if (
state->jammed)
return false;
276 if (
state->cooldown > 0.f)
return false;
283 current.ignore(
"legacy bool canFire cannot expose weapon attribute diagnostics");
286 if (!
r.infinite &&
current.value() <
static_cast<double>(
r.cost))
return false;
287 }
else if (!
r.infinite &&
r.value <
r.cost) {
291 return logic ==
nullptr || logic->canFire(
w);
294bool tryAttackStage(WeaponEntity&
w,
const AttackRequest& req,
const WeaponDefinition* def) {
295 if (!canAttackStage(
w, def))
return false;
302 consumed.ignore(
"legacy bool tryFire cannot expose weapon attribute diagnostics");
305 }
else if (!
r.infinite) {
306 r.value = std::max(0.f,
r.value -
r.cost);
310 state->stageTimer = 0.f;
311 state->lastRequest = req;
316 e.weaponId =
w.identity()->id;
318 e.ammoLeft =
static_cast<int>(
r.value);
323void updateStage(WeaponEntity&
w,
const WeaponDefinition* def,
float dt) {
325 state->cooldown = std::max(0.f,
state->cooldown - dt);
326 updateFeel(
w, def, dt);
330 if (
r.regen > 0.f &&
r.value <
r.max)
r.value = std::min(
r.max,
r.value +
r.regen * dt);
332 const AttackStageSpec* stages =
state->stages !=
nullptr ?
state->stages : &def->stages;
333 state->stageTimer += dt;
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);
349 if (
state->stageTimer >= stages->activeTime) {
351 state->stageTimer = 0.f;
355 e.weaponId =
w.identity()->id;
357 e.ammoLeft =
static_cast<int>(
r.value);
362 state->stageTimer = 0.f;
372 if (logic ==
nullptr)
return;
373 logicRegistry()[logic->
name()] = logic;
377 auto it = logicRegistry().find(
name);
378 return it == logicRegistry().
end() ? nullptr : it->second;
389 if (def ==
nullptr)
return;
390 if (usesStageMachine(def)) {
391 updateStage(
w, def, dt);
393 updateRanged(
w, def, dt);
399 if (def ==
nullptr)
return false;
400 if (usesStageMachine(def))
return tryAttackStage(
w, req, def);
401 return tryFireRanged(
w, req, def);
406 if (def ==
nullptr)
return false;
407 if (usesStageMachine(def))
return canAttackStage(
w, def);
408 return canFireRanged(
w, def);
413 if (def ==
nullptr)
return;
416 if (
r.reloading ||
r.value >=
r.max)
return;
418 if (pool->state()->count <= 0)
return;
424 r.reloadProgress = 0.f;
425 state->burstRemaining = 0;
436 w.state()->resource.reloading =
false;
437 w.state()->resource.reloadProgress = 0.f;
442 if (aim->turnSpeed <= 0.f) {
443 aim->yaw = aim->desiredYaw;
444 aim->pitch = aim->desiredPitch;
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;
462struct BuiltinLogicRegistrar {
463 BuiltinLogicRegistrar() {
470BuiltinLogicRegistrar gBuiltinRegistrar;
HexCoordinates to
Cell the unit walks towards on this segment.
Selective canonical mana/stamina adapter for weapons.
共享弹药池实体:多把武器共用的备用弹药,装填时从池中取。
武器逻辑接口:注册到 WeaponSystem 的可插拔行为。
virtual void end(WeaponEntity &w, const AttackRequest &req)
阶段机进入 Recover 时调用(默认空实现)。
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 风格一致)。