docs: Mana 精实 MVP 实现计划(8 任务,MCP 运行时验证)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,448 @@
|
|||||||
|
# Mana 魔力系统(精实 MVP)实现计划
|
||||||
|
|
||||||
|
> **For agentic workers:** 用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现。步骤用 `- [ ]` 复选框跟踪。
|
||||||
|
|
||||||
|
**Goal:** 让法术施放消耗魔力、魔力随时间回复、蓝空停施,并有 HUD 蓝条——把 Mana 从 0% 实现变成可玩资源门槛。
|
||||||
|
|
||||||
|
**Architecture:** 蓝池状态放 `PlayerStats`(镜像 hp);回蓝由 `PlayerManager._physics_process` 驱动;扣蓝在 `SpellEvaluator.execute_compiled` 主循环 `deck.pop()` 后、`match` 前一次性完成(该处只会遇到真正要执行的节点,被跳过/被消耗的节点不会到达此处);HUD 加蓝条。数值取 `numerical_design §2`。
|
||||||
|
|
||||||
|
**Tech Stack:** Godot 4.6 (Mono) · GDScript · 纯 JSON 数据驱动 · 验证用 Godot MCP(`execute_game_script`),本项目无单元测试框架。
|
||||||
|
|
||||||
|
> **验证约定**:每个任务的"测试"用 MCP 运行时断言。前置:Godot 编辑器开着、MCP 已连;用 `play_scene(main)` 起游戏后 `execute_game_script` 直接调用 autoload(无需进战斗场景,除 Task 6 HUD 需 `SceneManager.start_new_game()`)。跑完 `stop_scene`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: CoreDefinition 加 mana 字段 + cores.json 数据 + WandPreset 读取
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scripts/domain/spell_system/core_definition.gd`(在 `feature_tags` 后加 2 字段)
|
||||||
|
- Modify: `scripts/autoloads/wand_preset.gd:26-38`(`_core_from_dict` 读入)
|
||||||
|
- Modify: `data/cores.json`(每 Core 加 mana_max/mana_regen)
|
||||||
|
|
||||||
|
- [ ] **Step 1: CoreDefinition 加字段**
|
||||||
|
|
||||||
|
在 `core_definition.gd` 第 28 行 `@export var feature_tags: int = 0` 之后插入:
|
||||||
|
```gdscript
|
||||||
|
|
||||||
|
## 魔力池(MVP)——由 WandPreset 从 cores.json 读入
|
||||||
|
@export var base_mana_max: float = 100.0
|
||||||
|
@export var base_mana_regen: float = 5.0 # 每秒回复
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: WandPreset 读取两字段**
|
||||||
|
|
||||||
|
在 `wand_preset.gd` `_core_from_dict()` 的 `c.edges = d.get("edges", [])`(第 37 行)之后、`return c` 之前插入:
|
||||||
|
```gdscript
|
||||||
|
c.base_mana_max = float(d.get("mana_max", 100.0))
|
||||||
|
c.base_mana_regen = float(d.get("mana_regen", 5.0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: cores.json 加数值**
|
||||||
|
|
||||||
|
把 `data/cores.json` 整体替换为(每 Core 加 `mana_max`/`mana_regen`):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"wand_basic": { "display_name": "小木法杯", "slot_count": 5, "topology": 0, "cpu_limit": 5, "cast_interval": 0.5, "feature_tags": 0, "mana_max": 100, "mana_regen": 5 },
|
||||||
|
"wand_fast": { "display_name": "高速法杯", "slot_count": 3, "topology": 0, "cpu_limit": 3, "cast_interval": 0.25, "feature_tags": 0, "mana_max": 60, "mana_regen": 4 },
|
||||||
|
"wand_memory": { "display_name": "记忆法杖", "slot_count": 6, "topology": 0, "cpu_limit": 6, "cast_interval": 0.5, "feature_tags": 1, "mana_max": 100, "mana_regen": 5 },
|
||||||
|
"matrix_board": { "display_name": "矩阵板", "slot_count": 8, "topology": 1, "cpu_limit": 8, "cast_interval": 0.5, "feature_tags": 0, "grid_rows": 2, "grid_cols": 4, "mana_max": 120, "mana_regen": 6 },
|
||||||
|
"circuit_fork": { "display_name": "分叉回路", "slot_count": 3, "topology": 2, "cpu_limit": 5, "cast_interval": 0.6, "feature_tags": 0, "edges": [{"from": 0, "to": 1}, {"from": 0, "to": 2}], "mana_max": 100, "mana_regen": 5 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 验证(MCP)**
|
||||||
|
|
||||||
|
`play_scene(main)` 后 `execute_game_script`:
|
||||||
|
```gdscript
|
||||||
|
var c = WandPreset.make_core_by_id("wand_basic")
|
||||||
|
_mcp_print("basic mana_max=%s regen=%s (expect 100/5)" % [str(c.base_mana_max), str(c.base_mana_regen)])
|
||||||
|
var f = WandPreset.make_core_by_id("wand_fast")
|
||||||
|
_mcp_print("fast mana_max=%s regen=%s (expect 60/4)" % [str(f.base_mana_max), str(f.base_mana_regen)])
|
||||||
|
```
|
||||||
|
Expected: `basic mana_max=100 regen=5`、`fast mana_max=60 regen=4`。`get_editor_errors` count=0。`stop_scene`。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
```bash
|
||||||
|
git add scripts/domain/spell_system/core_definition.gd scripts/autoloads/wand_preset.gd data/cores.json
|
||||||
|
git commit -m "feat(mana): CoreDefinition 加 base_mana_max/regen + cores.json 数据"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: PlayerStats 蓝池状态与操作
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scripts/autoloads/player_stats.gd`(加字段/方法、reset 集成)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 加字段**
|
||||||
|
|
||||||
|
在 `player_stats.gd` 第 23 行 `var resistance: float = 0.0` 之后插入:
|
||||||
|
```gdscript
|
||||||
|
|
||||||
|
# ── 魔力(MVP)─────────────────────────────────────────
|
||||||
|
var mana: float = 100.0
|
||||||
|
var mana_max: float = 100.0
|
||||||
|
var mana_regen: float = 5.0 # 每秒回复
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 加方法**
|
||||||
|
|
||||||
|
在 `player_stats.gd` `get_hp_percent()`(第 64-65 行)之后插入:
|
||||||
|
```gdscript
|
||||||
|
|
||||||
|
## 换杖时设定蓝池上限/回速,并把当前蓝夹到 [0, max]
|
||||||
|
func set_mana_pool(max_v: float, regen: float) -> void:
|
||||||
|
mana_max = max_v
|
||||||
|
mana_regen = regen
|
||||||
|
mana = clampf(mana, 0.0, mana_max)
|
||||||
|
stats_changed.emit()
|
||||||
|
|
||||||
|
## 花费魔力;够则扣除返回 true,否则不变返回 false
|
||||||
|
func spend_mana(cost: float) -> bool:
|
||||||
|
if mana < cost:
|
||||||
|
return false
|
||||||
|
mana -= cost
|
||||||
|
stats_changed.emit()
|
||||||
|
return true
|
||||||
|
|
||||||
|
## 每帧回蓝(PlayerManager 驱动)。不 emit stats_changed —— HUD 每帧直接读 mana,
|
||||||
|
## 避免回蓝过程每帧发信号触发商店刷新。
|
||||||
|
func regen_mana(delta: float) -> void:
|
||||||
|
if mana >= mana_max:
|
||||||
|
return
|
||||||
|
mana = min(mana_max, mana + mana_regen * delta)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: reset_for_run 置满蓝**
|
||||||
|
|
||||||
|
把 `player_stats.gd` `reset_for_run()`(第 70-76 行)改为(加 `mana = mana_max` 一行):
|
||||||
|
```gdscript
|
||||||
|
func reset_for_run() -> void:
|
||||||
|
hp = hp_max
|
||||||
|
mana = mana_max
|
||||||
|
gold = 0
|
||||||
|
xp = 0
|
||||||
|
level = 1
|
||||||
|
xp_to_next = xp_for_level(1)
|
||||||
|
stats_changed.emit()
|
||||||
|
```
|
||||||
|
(`get_save_data`/`load_save_data` 不动——mana 不入存档,续玩从满蓝开始。)
|
||||||
|
|
||||||
|
- [ ] **Step 4: 验证(MCP)**
|
||||||
|
```gdscript
|
||||||
|
PlayerStats.set_mana_pool(100.0, 5.0)
|
||||||
|
PlayerStats.mana = 100.0
|
||||||
|
_mcp_print("spend 30 -> %s, mana=%s (expect true/70)" % [str(PlayerStats.spend_mana(30.0)), str(PlayerStats.mana)])
|
||||||
|
_mcp_print("spend 80 -> %s, mana=%s (expect false/70)" % [str(PlayerStats.spend_mana(80.0)), str(PlayerStats.mana)])
|
||||||
|
PlayerStats.regen_mana(1.0)
|
||||||
|
_mcp_print("after regen 1s -> mana=%s (expect 75)" % str(PlayerStats.mana))
|
||||||
|
PlayerStats.regen_mana(100.0)
|
||||||
|
_mcp_print("regen big -> mana=%s (expect capped 100)" % str(PlayerStats.mana))
|
||||||
|
```
|
||||||
|
Expected: `true/70`、`false/70`、`75`、`100`。`get_editor_errors` count=0。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
```bash
|
||||||
|
git add scripts/autoloads/player_stats.gd
|
||||||
|
git commit -m "feat(mana): PlayerStats 蓝池状态 + spend/regen/set_pool + reset 置满"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: PlayerManager 换杖置池 + 每帧回蓝
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scripts/domain/player_manager.gd:30-33`(_physics_process 加回蓝)、`:83-87`(equip_wand 置池)
|
||||||
|
|
||||||
|
- [ ] **Step 1: equip 时置蓝池**
|
||||||
|
|
||||||
|
把 `player_manager.gd` `equip_wand()`(第 83-87 行)改为:
|
||||||
|
```gdscript
|
||||||
|
func equip_wand(core: CoreDefinition, compiled: CompiledDeck) -> void:
|
||||||
|
_equipped_core = core
|
||||||
|
_equipped_compiled = compiled
|
||||||
|
_cast_interval = core.cast_interval if core else 0.5
|
||||||
|
_cast_timer = 0.0
|
||||||
|
if core:
|
||||||
|
PlayerStats.set_mana_pool(core.base_mana_max, core.base_mana_regen)
|
||||||
|
else:
|
||||||
|
PlayerStats.set_mana_pool(100.0, 5.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 每帧回蓝**
|
||||||
|
|
||||||
|
把 `player_manager.gd` `_physics_process()`(第 30-33 行)改为(加一行):
|
||||||
|
```gdscript
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
PlayerStats.regen_mana(delta)
|
||||||
|
_handle_movement(delta)
|
||||||
|
_handle_charge_state(delta)
|
||||||
|
_handle_auto_cast(delta)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证(MCP)**
|
||||||
|
```gdscript
|
||||||
|
var core = WandPreset.make_core_by_id("matrix_board")
|
||||||
|
PlayerManager.equip_wand(core, SpellEvaluator.compile_wand(core, []))
|
||||||
|
_mcp_print("equipped matrix -> mana_max=%s (expect 120)" % str(PlayerStats.mana_max))
|
||||||
|
PlayerStats.mana = 10.0
|
||||||
|
_mcp_print("mana set to 10")
|
||||||
|
```
|
||||||
|
下一次 `execute_game_script`(间隔真实时间,_physics_process 已在跑回蓝):
|
||||||
|
```gdscript
|
||||||
|
_mcp_print("mana after wait -> %s (expect > 10, rising toward 120)" % str(PlayerStats.mana))
|
||||||
|
```
|
||||||
|
Expected: `mana_max=120`;第二次打印 mana 明显 > 10。`get_editor_errors` count=0。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
```bash
|
||||||
|
git add scripts/domain/player_manager.gd
|
||||||
|
git commit -m "feat(mana): PlayerManager 换杖置蓝池 + 每帧回蓝"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: spells.json 加 mana_cost(权威值 numerical §2)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `data/spells.json`(每法术 meta 加 `mana_cost`)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 逐法术加 mana_cost**
|
||||||
|
|
||||||
|
在每个法术的 `meta` 对象里加 `"mana_cost": N`(值见下表,取自 `numerical_design §2` + 类型/Tier 缺省规则):
|
||||||
|
|
||||||
|
| spell id | mana_cost | 依据 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| action_spark_bolt | 5 | §2 spark_bolt |
|
||||||
|
| action_energy_orb | 20 | §2 energy_orb |
|
||||||
|
| action_fire_bolt | 8 | Tier1 元素弹缺省 |
|
||||||
|
| action_poison_dart | 6 | Tier1 缺省 |
|
||||||
|
| action_laser_beam | 4 | 低伤高速缺省 |
|
||||||
|
| action_water_wave | 6 | 共鸣输入/Tier1 缺省 |
|
||||||
|
| action_chain_bolt | 60 | §2 chain_bolt |
|
||||||
|
| action_plasma_storm | 0 | 共鸣产物免蓝 |
|
||||||
|
| action_frost_bolt | 8 | Tier1-2 缺省 |
|
||||||
|
| action_poison_pool | 30 | zone 缺省 |
|
||||||
|
| action_summon_turret | 35 | summon 缺省 |
|
||||||
|
| modifier_damage_plus | 15 | §2 damage_plus |
|
||||||
|
| modifier_spread_mod | 0 | §2 spread_mod |
|
||||||
|
| modifier_double_cast | 2 | §2 double_cast |
|
||||||
|
| modifier_pierce_plus | 8 | 小修正缺省 |
|
||||||
|
| trigger_on_hit | 10 | §2 trigger_hit |
|
||||||
|
| logic_every_n_shots | 0 | LOGIC 缺省 |
|
||||||
|
| logic_if_hp_below | 0 | LOGIC 缺省 |
|
||||||
|
| logic_if_enemy_nearby | 0 | LOGIC 缺省 |
|
||||||
|
| logic_loop | 0 | LOGIC 缺省 |
|
||||||
|
|
||||||
|
示例(`action_spark_bolt` 一行改后):
|
||||||
|
```json
|
||||||
|
"action_spark_bolt": { "type": 0, "display_name": "Spark Bolt", "description": "发射一颗电光飞弹,造成 3 伤害。", "element_tags": [], "meta": { "base_damage": 3.0, "speed": 350.0, "lifetime": 4.0, "radius": 6.0, "damage_type": 0, "mana_cost": 5, "shop_cost": 15 } },
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 验证(MCP)**
|
||||||
|
```gdscript
|
||||||
|
for id in ["action_spark_bolt", "action_chain_bolt", "modifier_damage_plus", "action_plasma_storm", "logic_loop"]:
|
||||||
|
var s = SpellRegistry.get_spell(id)
|
||||||
|
_mcp_print("%s mana_cost=%s" % [id, str(s.meta.get("mana_cost", "MISSING"))])
|
||||||
|
```
|
||||||
|
Expected: spark=5、chain=60、damage_plus=15、plasma=0、loop=0(无 MISSING)。`get_editor_errors` count=0(JSON 合法)。
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
```bash
|
||||||
|
git add data/spells.json
|
||||||
|
git commit -m "feat(mana): spells.json 每法术加 mana_cost(numerical §2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: SpellEvaluator 主循环扣蓝门控
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scripts/domain/spell_system/spell_evaluator.gd:332`(`execute_compiled` 主循环,`deck.pop()` 后、`match` 前)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 插入扣蓝门控**
|
||||||
|
|
||||||
|
在 `execute_compiled()` 第 332 行 `var node: SpellNode = deck.pop()` 之后、第 333 行 `match node.type:` 之前,插入:
|
||||||
|
```gdscript
|
||||||
|
var mana_cost: float = float(node.meta.get("mana_cost", 0.0))
|
||||||
|
if mana_cost > 0.0 and not PlayerStats.spend_mana(mana_cost):
|
||||||
|
break # 蓝不足 → 停止本次施法(此节点不执行、剩余跳过)
|
||||||
|
```
|
||||||
|
> 注意缩进:该行处于 `while` 循环体内(与 `var node = deck.pop()` 同级,两个 Tab)。**只改 `execute_compiled`,不要动 `execute_sub`**(SubPayload on-hit 不扣蓝,符合 MVP)。LOOP 内部节点走 `_run_logic_loop` 不经此处,MVP 不扣蓝(已知简化)。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 验证 — 逐节点扣蓝(spec 测试 2)**
|
||||||
|
```gdscript
|
||||||
|
var core = WandPreset.make_core_by_id("wand_basic")
|
||||||
|
PlayerStats.set_mana_pool(100.0, 5.0); PlayerStats.mana = 100.0
|
||||||
|
var deck = [SpellRegistry.get_spell("modifier_damage_plus"), SpellRegistry.get_spell("action_spark_bolt")]
|
||||||
|
BulletManager.reset()
|
||||||
|
SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core, deck), 0, Vector2(100,100))
|
||||||
|
_mcp_print("[T2] mana=%s (expect 80), bullets=%d (expect 1)" % [str(PlayerStats.mana), BulletManager.get_active_count()])
|
||||||
|
```
|
||||||
|
Expected: `mana=80, bullets=1`(100 − 15 − 5)。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证 — 首节点付不起则不发射(spec 测试 3)**
|
||||||
|
```gdscript
|
||||||
|
var core = WandPreset.make_core_by_id("wand_basic")
|
||||||
|
PlayerStats.set_mana_pool(100.0, 5.0); PlayerStats.mana = 3.0
|
||||||
|
BulletManager.reset()
|
||||||
|
SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core, [SpellRegistry.get_spell("action_spark_bolt")]), 0, Vector2(100,100))
|
||||||
|
_mcp_print("[T3] mana=%s (expect 3), bullets=%d (expect 0)" % [str(PlayerStats.mana), BulletManager.get_active_count()])
|
||||||
|
```
|
||||||
|
Expected: `mana=3, bullets=0`(spark 花 5,付不起,不发射)。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 验证 — 部分施放(spec 测试 4)**
|
||||||
|
```gdscript
|
||||||
|
var core = WandPreset.make_core_by_id("wand_basic")
|
||||||
|
PlayerStats.set_mana_pool(100.0, 5.0); PlayerStats.mana = 8.0
|
||||||
|
var deck = [SpellRegistry.get_spell("modifier_double_cast"), SpellRegistry.get_spell("action_spark_bolt"), SpellRegistry.get_spell("action_spark_bolt")]
|
||||||
|
BulletManager.reset()
|
||||||
|
SpellEvaluator.execute_compiled(SpellEvaluator.compile_wand(core, deck), 0, Vector2(100,100))
|
||||||
|
_mcp_print("[T4] mana=%s (expect 1), bullets=%d (expect 1)" % [str(PlayerStats.mana), BulletManager.get_active_count()])
|
||||||
|
```
|
||||||
|
Expected: `mana=1, bullets=1`(double_cast 2→mana6;spark1 5→mana1,发射;spark2 付不起→停)。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 回归 — 持久内存不受影响**
|
||||||
|
```gdscript
|
||||||
|
var core = WandPreset.make_core_by_id("wand_memory")
|
||||||
|
PlayerStats.set_mana_pool(100.0, 5.0)
|
||||||
|
var deck = [SpellRegistry.get_spell("logic_every_n_shots"), SpellRegistry.get_spell("action_spark_bolt")]
|
||||||
|
var cA = SpellEvaluator.compile_wand(core, deck)
|
||||||
|
var r = []
|
||||||
|
for i in 4:
|
||||||
|
PlayerStats.mana = 100.0
|
||||||
|
BulletManager.reset()
|
||||||
|
SpellEvaluator.execute_compiled(cA, 0, Vector2(100,100))
|
||||||
|
r.append(BulletManager.get_active_count())
|
||||||
|
_mcp_print("[reg] every_n_shots = %s (expect [0,0,1,0])" % str(r))
|
||||||
|
```
|
||||||
|
Expected: `[0,0,1,0]`(P-S5-03 回归,logic cost 0 不影响)。`get_editor_errors` count=0。
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
```bash
|
||||||
|
git add scripts/domain/spell_system/spell_evaluator.gd
|
||||||
|
git commit -m "feat(mana): SpellEvaluator 主循环逐节点扣蓝 + 蓝空停施"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: HUD 蓝条
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `scenes/main/combat_s2.gd`(成员声明区加 `_mana_bar`;`_setup_hud` 建条;`_refresh_hud` 刷新)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 加成员**
|
||||||
|
|
||||||
|
在 `combat_s2.gd` HUD 成员声明区(`_hud_layer` 附近)加:
|
||||||
|
```gdscript
|
||||||
|
var _mana_bar: ProgressBar = null
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: _setup_hud 建蓝条**
|
||||||
|
|
||||||
|
在 `_setup_hud()` 第 231 行 `_lbl_statuses = make_label.call(...)` 之后插入:
|
||||||
|
```gdscript
|
||||||
|
_mana_bar = ProgressBar.new()
|
||||||
|
_mana_bar.position = Vector2(200, 86)
|
||||||
|
_mana_bar.custom_minimum_size = Vector2(170, 16)
|
||||||
|
_mana_bar.size = Vector2(170, 16)
|
||||||
|
_mana_bar.min_value = 0.0
|
||||||
|
_mana_bar.show_percentage = false
|
||||||
|
var mana_fill := StyleBoxFlat.new()
|
||||||
|
mana_fill.bg_color = Color(0.3, 0.55, 1.0)
|
||||||
|
_mana_bar.add_theme_stylebox_override("fill", mana_fill)
|
||||||
|
var mana_bg := StyleBoxFlat.new()
|
||||||
|
mana_bg.bg_color = Color(0.12, 0.12, 0.22)
|
||||||
|
_mana_bar.add_theme_stylebox_override("background", mana_bg)
|
||||||
|
_hud_layer.add_child(_mana_bar)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: _refresh_hud 刷新蓝条**
|
||||||
|
|
||||||
|
在 `_refresh_hud()`(第 316 行起)末尾加:
|
||||||
|
```gdscript
|
||||||
|
if _mana_bar:
|
||||||
|
_mana_bar.max_value = PlayerStats.mana_max
|
||||||
|
_mana_bar.value = PlayerStats.mana
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 验证(MCP,需进战斗场景)**
|
||||||
|
|
||||||
|
`play_scene(main)` → `execute_game_script`: `SceneManager.start_new_game()`。下一次调用:
|
||||||
|
```gdscript
|
||||||
|
var cs = get_tree().current_scene
|
||||||
|
_mcp_print("scene=%s bar=%s" % [cs.name, str(cs._mana_bar != null)])
|
||||||
|
PlayerStats.mana = PlayerStats.mana_max * 0.4
|
||||||
|
_mcp_print("mana set to 40%%: value=%s max=%s" % [str(cs._mana_bar.value), str(cs._mana_bar.max_value)])
|
||||||
|
```
|
||||||
|
然后 `get_game_screenshot` 确认 HUD 内有蓝色条、约 40% 充满。Expected: `bar=true`,蓝条可见。`get_editor_errors` count=0。`stop_scene`。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
```bash
|
||||||
|
git add scenes/main/combat_s2.gd
|
||||||
|
git commit -m "feat(mana): HUD 蓝条(ProgressBar,每帧刷新)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: 更新文档 Mana 现状 callout 为「已实现 MVP」
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/design/game_design.md`(§4.1 Mana callout)
|
||||||
|
- Modify: `docs_dev/doc_code_audit_2026-07-20.md`(Mana 未实现条目)
|
||||||
|
|
||||||
|
- [ ] **Step 1: game_design §4.1 callout**
|
||||||
|
|
||||||
|
把 `docs/design/game_design.md` §4.1 的现状 callout(含"Mana 魔力系统完全未实现")替换为:
|
||||||
|
```markdown
|
||||||
|
> **⚠️ 实现现状 (2026-07-20 审计 → MVP 已实现)**:Mana 精实 MVP 已落地——`PlayerStats` 蓝池(`mana`/`mana_max`/`mana_regen`),`CoreDefinition.base_mana_max/regen`(cores.json),每法术 `mana_cost`(numerical §2),`SpellEvaluator` 逐节点扣蓝 + 蓝空停施,HUD 蓝条。**暂缺**:递增蓝耗、`infinite_spells` 扣血、全局+法杖取 Min、cast_delay。详见 `docs_dev/specs/2026-07-20-mana-system-mvp-design.md`。
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 审计报告去掉 Mana 未实现**
|
||||||
|
|
||||||
|
在 `docs_dev/doc_code_audit_2026-07-20.md` 中,把「Mana 系统完全未实现」相关条目(C 节 🟠 未实现里的 Mana 行、G.2 未实现清单里的 Mana)标注为「✅ 已实现 MVP (2026-07-20)」。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 验证**
|
||||||
|
|
||||||
|
`git grep -n "Mana 魔力系统完全未实现" docs/` → 应无结果(已被替换)。
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
```bash
|
||||||
|
git add docs/design/game_design.md docs_dev/doc_code_audit_2026-07-20.md
|
||||||
|
git commit -m "docs(mana): Mana 现状 callout 更新为已实现(MVP)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 8: 全量回归验收
|
||||||
|
|
||||||
|
**Files:** 无(纯验证)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 重载 + 0 错误**
|
||||||
|
|
||||||
|
`reload_project` → `get_editor_errors` → count=0。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 跑完整 6 项 spec 测试**
|
||||||
|
|
||||||
|
`play_scene(main)`,逐条 `execute_game_script` 跑 Task 5 的 T2/T3/T4/回归 + Task 2 的 spend/regen + Task 3 的换杖置池,确认全部期望值。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 端到端手感**
|
||||||
|
|
||||||
|
`SceneManager.start_new_game()` 进战斗,观察:连打 spark_bolt 时蓝条下降、停火后回满;换 `wand_fast`(max 60)蓝条上限变小。`get_game_screenshot` 存证。`stop_scene`。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 完成定义核对(DoD,见 spec §8)**
|
||||||
|
|
||||||
|
逐条确认 spec §8 完成定义已满足。
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit(如有文档收尾)**
|
||||||
|
```bash
|
||||||
|
git commit --allow-empty -m "chore(mana): MVP 验收通过(Godot 4.6 运行时实测)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 自检结论
|
||||||
|
|
||||||
|
- **Spec 覆盖**:§1 范围→Task1-6;§2 组件→Task1(Core)/2(PlayerStats)/3(PlayerManager)/5(SpellEvaluator)/6(HUD);§4 数值→Task1/4;§5 边界→Task2(reset)/5(停施);§6 测试→Task5/8。无遗漏。
|
||||||
|
- **无占位**:所有步骤含精确文件/行号/完整代码/期望输出。
|
||||||
|
- **类型一致**:`base_mana_max`/`base_mana_regen`(Core)、`mana`/`mana_max`/`mana_regen` + `set_mana_pool`/`spend_mana`/`regen_mana`(PlayerStats)全程一致;`meta.mana_cost` 键名一致。
|
||||||
Reference in New Issue
Block a user