146 lines
7.1 KiB
GDScript
146 lines
7.1 KiB
GDScript
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||
# 实现 GDD:design/ux/battle.md(四区呈现装配器)——读核心只读面(战斗结算 呈现快照 / runner)→
|
||
# 组装文案/投影 → 驱动各视图 setter。
|
||
# 高内聚边界:"核心状态 → 呈现文案"的全部翻译集中于此;视图保持零核心依赖;
|
||
# 静态无状态——呈现不持久化任何核心句柄。
|
||
# E1 封装收口(2026-09-13):核心状态读取统一经 战斗结算.presentation_snapshot() 成文快照面——
|
||
# 禁再链式穿透 settlement.enemy_system.hero / settlement.enemy_system.enemies / settlement.status_effects.query_active 一类路径(战斗结算 协作对象已私有化,
|
||
# 敌人含死亡残位的投影由快照 enemies[] 承载,含 alive/pending_*/effects 字段——2026-09-16 敌人技能池批批 2 定稿)。
|
||
# 更新触发注记(battle.md Data Requirements):旗/拔/移动成功反馈零事件外发(排雷玩法 发射面封闭)——
|
||
# 经 ActionOutcome 返回值命令式驱动;AP/RAP 无推送信号——组合根每次调用返回后重查;
|
||
# 增益状态 纯被动容器零信号——状态栏刷新绑 战斗结算 结算编排完成时点。
|
||
# Date: 2026-09-12(战斗屏改造批;基础 = 2026-09-11 HUD 装配器;2026-09-13 快照面改造)
|
||
class_name HudPresenter
|
||
extends RefCounted
|
||
|
||
|
||
# 每次结算行/轮次/节点变化/动作返回后调用(组合根全量重查装配)
|
||
# 2026-09-26 C 区精简批:钱包 GP/钥石投喂退役(中/大循环资源迁出战斗屏——信息归属拍板);
|
||
# HP/状态效果单投喂 B 区站桩(原 B/C 双投喂为重复展示——信息归属拍板单一展示面原则)
|
||
static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApBarView,
|
||
settlement, runner, level_active: bool, db, refresh_remaining: int) -> void:
|
||
if settlement == null:
|
||
_refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
|
||
return
|
||
var s: Dictionary = settlement.presentation_snapshot()
|
||
var hero: Dictionary = s.hero
|
||
if hero.is_empty() or not bool(hero.get("alive", false)):
|
||
_refresh_out_of_battle(stage, info, ap_bar, runner, level_active, db)
|
||
return
|
||
_refresh_hero(stage, info, s)
|
||
_refresh_economy(ap_bar, s, db, refresh_remaining)
|
||
stage.set_enemies(s.enemies)
|
||
|
||
|
||
# ---- B 区主角站桩(HP/状态唯一展示面)+ C 区数据框(攻击/AC) ----
|
||
|
||
static func _refresh_hero(stage: BattleStageView, info: HeroInfoPanelView, s: Dictionary) -> void:
|
||
var hero: Dictionary = s.hero
|
||
var hp: int = int(hero.hp)
|
||
var max_hp: int = int(hero.max_hp)
|
||
var atk: int = int(hero.atk)
|
||
# 修正 = attack_up 池有效值(hero_offense);来源角标 = 效果 display_name(快照 hero_effects 投影)
|
||
var mod_total := 0
|
||
var sources: Array = []
|
||
for eff in s.hero_effects:
|
||
if String(eff.effect_id) == "attack_up":
|
||
mod_total += int(eff.effective_value)
|
||
sources.append("%s×%d" % [String(eff.display_name), int(eff.layers)])
|
||
var mod_text := ""
|
||
if mod_total != 0:
|
||
mod_text = "(%+d%% %s)" % [mod_total, "·".join(sources)]
|
||
# B 区站桩 = HP 与状态效果唯一展示面(2026-09-26 信息归属拍板)——C 区不再重复投喂
|
||
stage.hero_standee.set_hero(hp, max_hp)
|
||
stage.hero_standee.set_effects(s.hero_effects)
|
||
info.set_attack(atk, mod_text)
|
||
# 我方 AC 唯一展示面 = C 区(2026-09-26 二轮拍板补入)——快照 armor_class 含战斗内 ac_up 折算
|
||
info.set_armor(int(hero.get("armor_class", 0)))
|
||
|
||
|
||
# ---- 行动经济(AP 点阵 / RAP / 弃图刷新按钮态——RAP 读数 2026-09-26 自 C 区迁入本条) ----
|
||
|
||
static func _refresh_economy(ap_bar: ApBarView, s: Dictionary, db, refresh_remaining: int) -> void:
|
||
ap_bar.set_ap(int(s.ap), int(db.action_economy.ap_per_round))
|
||
ap_bar.set_rap(int(s.rap))
|
||
var digging := String(s.phase) == ActionEconomy.PHASE_ACTIVE
|
||
ap_bar.set_refresh_state(refresh_remaining,
|
||
bool(s.can_refresh) and digging) # Waiting 代理已随 OQ12 退役——UI 仅挖掘期受理
|
||
ap_bar.set_stop_enabled(bool(s.hero.get("alive", false)) and digging)
|
||
|
||
|
||
# 战斗外(节点间隙/关卡结束)——HP 读 runner 携带值
|
||
static func _refresh_out_of_battle(stage: BattleStageView, info: HeroInfoPanelView,
|
||
ap_bar: ApBarView, runner, level_active: bool, db) -> void:
|
||
if not level_active:
|
||
return
|
||
if runner != null:
|
||
info.set_attack(runner.effective_atk(), "(关卡推进中)")
|
||
info.set_armor(runner.effective_ac())
|
||
stage.hero_standee.set_hero(runner.hero_hp, runner.effective_max_hp())
|
||
stage.hero_standee.set_effects([])
|
||
stage.clear_enemies() # 节点间隙清敌方站桩残位(死亡瞬间的灰化呈现已在结算行驱动完成)
|
||
ap_bar.set_rap(0) # battle_over RAP 清零反馈(行动经济 义务——载体 2026-09-26 迁 AP 条)
|
||
|
||
|
||
# 三源拒绝可感知(排雷玩法 reject_reason/sub_reason → 玩家文案;pattern 1 双源拒绝反馈)
|
||
static func outcome_text(outcome: Dictionary) -> String:
|
||
if outcome.accepted:
|
||
return "执行完成。"
|
||
match String(outcome.get("reject_reason", "")):
|
||
"board_face":
|
||
var sub := String(outcome.get("sub_reason", ""))
|
||
var texts := {
|
||
"not_adjacent": "不能执行:目标与小人不相邻",
|
||
"target_revealed": "不能执行:目标已开",
|
||
"target_concealed": "不能移动:目标未开",
|
||
"flag_present": "不能插旗:此格已有旗(旗为一次性承诺——相邻时左键直接挖开)",
|
||
"no_path": "不能移动:已探明区不连通",
|
||
"out_of_catalog": "无效目标",
|
||
}
|
||
return texts.get(sub, "棋盘面拒绝: " + sub)
|
||
"resource_face":
|
||
return "AP 不足。"
|
||
"phase":
|
||
return "结算中,非行动阶段。"
|
||
"board_invalid":
|
||
return "棋盘数据非法(fail-fast)。"
|
||
_:
|
||
return "动作被拒。"
|
||
|
||
|
||
# ---- 调整板批量模拟报告装配(文案归 presenter——视图只 set_text;code-review 2026-09-12 批) ----
|
||
|
||
static func tune_sim_report(n: int, atk_pct: int, hp_pct: int, wins: int, dmg_all: Array, hp_left_win: Array, hp_boss: Array, rounds_all: Array, death_nodes: Dictionary, base_hp: int) -> String:
|
||
var lines: Array = []
|
||
lines.append("[b]模拟 N=%d(atk+%d%% / hp+%d%%)[/b]" % [n, atk_pct, hp_pct])
|
||
lines.append("通关率 [color=#80d080]%.0f%%[/color](%d/%d)" % [100.0 * float(wins) / float(maxi(1, n)), wins, n])
|
||
lines.append("整关承伤:均 %.1f · p90 %.0f(基础 HP %d)" % [_mean(dmg_all), _p90(dmg_all), base_hp])
|
||
if not hp_left_win.is_empty():
|
||
lines.append("通关剩余 HP 均 %.1f · 进 Boss HP 均 %.1f" % [_mean(hp_left_win), _mean(hp_boss)])
|
||
lines.append("平均轮数 %.1f" % _mean(rounds_all))
|
||
if death_nodes.is_empty():
|
||
lines.append("败北节点:—")
|
||
else:
|
||
var parts: Array = []
|
||
for k in death_nodes.keys():
|
||
parts.append("%s×%d" % [k, death_nodes[k]])
|
||
lines.append("败北节点:%s(0-3=战斗序,4=Boss)" % " ".join(parts))
|
||
return "\n".join(lines)
|
||
|
||
|
||
static func _mean(a: Array) -> float:
|
||
if a.is_empty():
|
||
return 0.0
|
||
var t := 0.0
|
||
for v in a:
|
||
t += float(v)
|
||
return t / a.size()
|
||
|
||
|
||
static func _p90(a: Array) -> float:
|
||
if a.is_empty():
|
||
return 0.0
|
||
var b := a.duplicate()
|
||
b.sort()
|
||
return float(b[mini(b.size() - 1, int(0.9 * b.size()))])
|