Files
spellforge/docs_dev/specs/2026-07-24-element-resistance-design.md
T
joywayerandClaude Opus 4.8 e87e453abf docs(spec): E1-① 元素抗性设计——apply_damage_from_context 查表 + enemies.json resistances + 弱点(负抗)
缺失功能路线图 E1 子计划①。元素链路已通,仅补敌人抗性数据+命中查表:
enemies.json 每型可选 resistances{元素名:-0.9~0.9},EnemyManager 按 ctx.damage_type
查表传入 calc_damage(0,res);护甲不动;物理无抗性;设计器加抗性网格。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:45:28 +08:00

110 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 元素抗性(Element Resistance / 1-Res)设计
> **日期**2026-07-24
> **Epic**:E1 战斗深度 · 子计划 ①(缺失功能路线图 `docs_dev/plans/2026-07-23-missing-features-roadmap.md`
> **优先级**P0 · 工作量 S
> **目标**:让敌人对不同元素有差异化抗性/弱点,激活当前的死代码抗性维度,使元素类词条/核心产生真实伤害差异。
---
## 1. 背景与现状(代码复核 2026-07-24
元素链路**已完整贯通**,仅缺敌人抗性数据 + 命中时查表:
- 法术 `meta.damage_type`(int) → 子弹 SoA slot+8`bullet_manager.gd:145`)→ 命中时 `DamageContextPool.acquire(b_dmg, b_mult, b_type, ...)``bullet_manager.gd:77`)→ `ctx.damage_type`
- `DamageContext.calc_damage(armor, resistance)``damage_context.gd:26`)公式**已含** `(1resistance)``max(0, base×mult×(1res) armor×(1pierce))`
- **断点**`enemy_manager.apply_damage_from_context``enemy_manager.gd:253`)调 `ctx.calc_damage()` **不传参** → resistance 恒 0,抗性维度是死代码。
- 护甲在 `apply_damage``enemy_manager.gd:268`)内单独应用(`_ARMOR` 查表),与本设计**互不干扰**。
`DamageType`autoload):PHYSICAL=0, FIRE=1, ICE=2, LIGHTNING=3, POISON=4, ARCANE=5。
## 2. 设计
### 2.1 数据(enemies.json
每个敌型可选新增 `resistances`**元素名键**,值域 **-0.9 ~ 0.9**(负=弱点加伤,正=减抗),缺失键=0:
```json
"1": { "name": "快速 Fast", "speed": 140, ..., "resistances": { "fire": 0.5, "ice": -0.5 } }
```
- 仅覆盖 5 种元素:`fire` `ice` `lightning` `poison` `arcane`。**物理无抗性**(归护甲域)。
- 全 0 抗性的敌型可省略 `resistances` 键(保持 JSON 简洁)。
### 2.2 逻辑(EnemyManager
- 新增成员 `var _RESIST: Dictionary = {}` —— `type_int → PackedFloat32Array`(长度 6index=damage_type`[0]` 物理恒 0)。
- 元素名→int 映射常量(就近 DamageType):
```gdscript
const _RESIST_ELEMENTS := {"fire": 1, "ice": 2, "lightning": 3, "poison": 4, "arcane": 5}
```
- 在 `_load_json_archetypes()` 的每型循环内追加加载:
```gdscript
var rf := PackedFloat32Array([0, 0, 0, 0, 0, 0])
var res_d: Dictionary = d.get("resistances", {})
for ename in _RESIST_ELEMENTS:
if res_d.has(ename):
rf[_RESIST_ELEMENTS[ename]] = clampf(float(res_d[ename]), -0.9, 0.9)
_RESIST[t] = rf
```
- 新增查表辅助(保底 0,越界安全):
```gdscript
func _resist_for(etype: int, dtype: int) -> float:
var rf: PackedFloat32Array = _RESIST.get(etype, PackedFloat32Array())
if dtype < 0 or dtype >= rf.size():
return 0.0
return rf[dtype]
```
- 改写 `apply_damage_from_context`:解析 entity→slot→敌型,查抗性,传入 `calc_damage`
```gdscript
func apply_damage_from_context(entity_id: int, ctx_id: int) -> bool:
var ctx: DamageContext = DamageContextPool.get_context(ctx_id)
if ctx == null:
return false
var slot: int = _entity_index_map.get(entity_id, -1)
var res: float = 0.0
if slot >= 0:
var etype: int = int(_data[slot * ENEMY_STRIDE + 6])
res = _resist_for(etype, ctx.damage_type)
var final_dmg: float = ctx.calc_damage(0.0, res)
return apply_damage(entity_id, final_dmg, ctx.owner_id, true)
```
护甲仍由 `apply_damage` 内的 `_ARMOR` 处理,**不动**。次序:`base×mult×(1res)` → `apply_damage` 内 `×combo armor`floor 1)。物理弹 res=0 → 行为完全不变。
### 2.3 设计器(enemy_tab
- 主表(速度/护甲/尺寸/颜色)之后加一张 `HSeparator` + 第二个 `GridContainer`columns=6):
- 表头:`原型` + `火 Fire` / `冰 Ice` / `雷 Lightning` / `毒 Poison` / `奥 Arcane`(中英双语中文在前)。
- 6 行 × 5 元素 `UI.spin(-0.9, 0.9, 0.05, 初值)`,初值读 `_data[str(i)].resistances.get(ename, 0.0)`。
- 保存到 `_rows[i]["resist"]`Dictionary ename→SpinBox)。
- `_save()`:每型汇总非零抗性写入 `resistances`(全 0 则省略该键):
```gdscript
var rd := {}
for ename in ELEM_KEYS:
var v: float = r["resist"][ename].value
if absf(v) > 0.0001:
rd[ename] = v
# out[str(i)] 内:仅当 rd 非空时加 "resistances": rd
```
## 3. 影响文件
| 文件 | 改动 |
| :-- | :-- |
| `data/enemies.json` | 可选加 `resistances`(种子:给 1-2 型示例抗性/弱点,供实测) |
| `scripts/autoloads/enemy_manager.gd` | `_RESIST` + `_RESIST_ELEMENTS` + 加载 + `_resist_for` + 改写 `apply_damage_from_context` |
| `addons/game_designer/enemy_tab.gd` | 抗性网格 UI + `_save` 写 `resistances` |
## 4. 验收标准
1. 抗火 0.5 的敌人被**火弹**(damage_type=1)受伤减半;被**物理弹**(0)受伤不变(MCP:同 base_damage 两种 damage_type 对比 HP 降幅)。
2. 负抗性 `-0.5` 的敌人被对应元素放大 1.5×(实测)。
3. 抗性与护甲叠加正确:`final = max(1, base×mult×(1res)×combo armor)`。
4. `resistances` 缺失/未列元素 → 该元素抗性 0(回退安全,无报错)。
5. 设计器抗性网格读写往返 `FAILS=0`(含负值、全 0 省略键)。
6. `validate_script` 通过;纯数据驱动;越界 damage_type 不崩。
## 5. 非目标(YAGNI
- 伤害飘字变色/抵抗提示(E7 DamageNumber 未建)。
- 状态削抗 / 催化引爆 / 元素反应(E2)。
- 物理抗性(护甲已覆盖物理减免)。
- Boss 分阶段动态抗性(后续可扩,本期静态按原型)。