Files
gplan-2/game/presentation/views/battle_end_overlay.gd
T
DKChan 32fdf957fd refactor(代码术语): 卡① flag→seal 全族换代+game/ 禁用词清扫(迭代 31 卡①)
标识符面:flag_*→seal_ 族(含 Godot 信号 flag_judged→seal_judged 与连接/回调同名、
FACTS 两键 flags_count→seals_count/dug_under_flag→dug_under_seal 全链、
.tres 键名与 schema .gd 同批同步);显式豁免零改动=size_flags_*(Godot 布局属性)+_fatal_flag。
中文面:金币→GP(金币袋→GP 袋)/插旗→封印/雷格→危险格/惩罚格→危险格/旗→封印·封印印记族,
game/ 内 *.gd/.tres/.md/.json 四类禁用词与 flag 双双零命中(V1/V4)。
守卫字面量不动(被检查的禁字不是使用):smoke_all V21 零「雷/旗」检查面、
ui_font_asset_test 字形集攻/雷/旗/封 保持原断言逻辑。
代拍板:板面单字槽 glyph「旗」→「封」(禁字纪律+单字位宽,消费面四处同名随动)。
素材换代(2026-10-18 用户裁案甲):assets/art/board/flag_seal.png→seal_mark.png(素材不入 git,
盘上改名+导入重刷),代码两处 res:// 字符串+COL_FLAG/CURSOR_FLAG 常量同步,
闸件四处随批(run_all_gates.sh/render_gate.sh/golden_baseline_refresh.sh/path_check.py)。
anchor.json 键名手改,重锚与改前改后 diff 随本卡收口(同裁案甲)。

基线读数(改前,主区 ed2dfaf6):冒烟 1151 检查 0 失败/单元 34 套全绿/探针 --click
存量红仅 screen_flow_probe(user:// 断点污染在册已知项)。
改后同批读数(本 worktree):冒烟 1151 检查 0 失败/探针 --click 同一存量红、其余全绿。
2026-10-19 02:52:38 +08:00

333 lines
14 KiB
GDScript
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.
## VERTICAL SLICE - NOT FOR PRODUCTION
## 战斗/冒险终局 overlay(模态):判定行+三行账+明细回看入口+继续/返回,失败侧暮蓝氛围。
## 摘要文本经 open_victory/open_defeat 由组合根装配喂入——本类零文案装配。
class_name BattleEndOverlay
extends Control
signal continue_pressed # M9:review_pressed 退役(P-C7——冒险中回看归「生涯」,06 登记)
const COL_GOLD := UiKit.GOLD
const COL_DUSK := UiKit.DUSK # 暮蓝(全作唯一暮蓝档)
const COL_WARM := UiKit.WARM
var _dim: ColorRect
var _seal: Panel
var _verdict: Label
var _rows_box: VBoxContainer # M9 三行账(GP/钥石/本关加成——demo ⑧两列对齐 2026-10-06 审计差距④;失败侧整行隐藏=P-C4 维持 OQ14)
var _gp_row: HBoxContainer
var _gp_name: Label
var _gp_val: Label
var _ks_row: HBoxContainer
var _ks_name: Label
var _ks_val: Label
var _bonus_row_l: HBoxContainer
var _bonus_name: Label
var _bonus_val: Label
var _cele: TextureRect # M9 庆祝立绘(idle 占位浮动——正式帧待资产批)
var _btn: Button
var _board_rect := Rect2()
func _init() -> void:
# 二轮修改批 B(2026-09-29 用户目检裁决):半透明浮层文字不可读 → 实底石板皮面板形态
#(与营地/设置/图例同族)。半透明氛围 dim 保留(胜利暖纱/失败暮蓝——open_* 就地换色),信息与
# 按钮全部上板;金蜡封 = 面板头部静态徽记(原全屏坠落演出退役——落点被面板遮挡)。
# 骨架经 UiKit.modal_shell(T4 收口批);_dim 引用摘出供 open_* 换氛围色。
var shell := UiKit.modal_shell(self, Color(COL_WARM, 0.0), Vector2(520, 0), 12, true,
UiKit.panel_skinned(Vector4(16, 14, 16, 14)))
_dim = shell.dim
var box: VBoxContainer = shell.box
var header := HBoxContainer.new()
header.add_theme_constant_override("separation", 12)
header.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_child(header)
_seal = Panel.new()
_seal.mouse_filter = Control.MOUSE_FILTER_IGNORE
var sb := UiKit.panel_flat(COL_GOLD, UiKit.GOLD_DEEP, 200, 4)
_seal.add_theme_stylebox_override("panel", sb)
_seal.custom_minimum_size = Vector2(52, 52)
_seal.size_flags_vertical = Control.SIZE_SHRINK_CENTER
header.add_child(_seal)
var seal_img := TextureRect.new()
var seal_tex := UiKit.load_tex("res://assets/art/board/seal_mark.png")
if seal_tex != null:
seal_img.texture = seal_tex
seal_img.custom_minimum_size = Vector2(32, 32)
seal_img.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
seal_img.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
seal_img.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
seal_img.size_flags_vertical = Control.SIZE_SHRINK_CENTER
seal_img.mouse_filter = Control.MOUSE_FILTER_IGNORE
_seal.add_child(seal_img)
_verdict = UiKit.label("",UiKit.FS_TITLE, true, UiKit.INK_TITLE)
_verdict.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_verdict.size_flags_vertical = Control.SIZE_SHRINK_CENTER
header.add_child(_verdict)
# M9 庆祝立绘(demo ⑧——idle 帧浮动占位;正式帧归资产批;减动效=静帧)
_cele = TextureRect.new()
var cele_tex := UiKit.load_tex(UiKit.TEX_HERO_IDLE)
if cele_tex != null:
_cele.texture = cele_tex
_cele.custom_minimum_size = Vector2(64, 64)
_cele.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_cele.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
_cele.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_cele.size_flags_vertical = Control.SIZE_SHRINK_CENTER
_cele.visible = false
header.add_child(_cele)
_rows_box = VBoxContainer.new()
_rows_box.add_theme_constant_override("separation", 6)
_rows_box.visible = false
box.add_child(_rows_box)
# 两列对齐账行(审计差距④对齐 demo ⑧——项目名左/数值右,定宽 360)
_gp_row = _ledger_row()
_gp_name = _ledger_name(UiKit.FS_SUB, UiKit.GOLD_INK, true) # 尾裁微批a 2026-10-08——GOLD_DEEP 对结算板 2.67:1 越带,换深金墨 5.21:1
_gp_val = _ledger_value(UiKit.FS_SUB, UiKit.GOLD_INK, true) # 同上
_gp_row.add_child(_gp_name)
_gp_row.add_child(_gp_val)
_rows_box.add_child(_gp_row)
_ks_row = _ledger_row()
_ks_name = _ledger_name(UiKit.FS_BODY, UiKit.INK_LABEL, false)
_ks_val = _ledger_value(UiKit.FS_BODY, UiKit.INK_LABEL, true)
_ks_row.add_child(_ks_name)
_ks_row.add_child(_ks_val)
_ks_row.visible = false
_rows_box.add_child(_ks_row)
_bonus_row_l = _ledger_row()
_bonus_name = _ledger_name(UiKit.FS_HINT, UiKit.INK_LABEL, false)
_bonus_val = _ledger_value(UiKit.FS_HINT, UiKit.INK_LABEL, true)
_bonus_row_l.add_child(_bonus_name)
_bonus_row_l.add_child(_bonus_val)
_bonus_row_l.visible = false
_rows_box.add_child(_bonus_row_l)
box.add_child(HSeparator.new())
# M9:明细回看钮退役(P-C7——冒险中回看归「生涯」)
_btn = UiKit.primary_button("继续")
_btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER # 审计差距④——紧凑钮(原全宽)
_btn.custom_minimum_size.x = 150
_btn.pressed.connect(_emit_continue_pressed)
box.add_child(_btn)
visible = false
# 终局画面数据锚 = 战斗结算 判定行(verdict)——判定行文案由组合根装配传入。
# gp_summary(OQ14 口径——胜利总结结算展示 GP 总量+增量):组合根装配「战利品 +增量 · 合计总量」
# 传入(入账次序 = LevelRunner 入账先于组合根 handler,runner.gp 即战后总量);空串 = 不显示(失败侧恒空)。
func open_victory(verdict_line: String, continue_label: String, board_rect: Rect2, gp_summary: String = "",
ks_summary: String = "", bonus_summary: String = "", gp_gain := -1, gp_total := -1) -> void:
_dim.color = Color(UiKit.DIM_NIGHT, 0.62) # 审计差距④——全屏压暗对齐 demo(原 0.28 暖纱透底)
_verdict.text = verdict_line
_seal.visible = true # 胜利徽记(面板头部静态)
_seal.modulate = Color.WHITE
_set_ledger_row(_gp_name, _gp_val, gp_summary, true) # GP 行=固定名「战利品 GP」(滚动/整串均只入值列)
_set_ledger_row(_ks_name, _ks_val, ks_summary)
_ks_row.visible = ks_summary != ""
_set_ledger_row(_bonus_name, _bonus_val, bonus_summary)
_bonus_row_l.visible = bonus_summary != ""
_rows_box.visible = gp_summary != "" or ks_summary != "" or bonus_summary != ""
_btn.text = continue_label
_board_rect = board_rect
_cele.visible = true
_play_celebrate()
visible = true
if UiKit.victory_fx_style == "dust":
_play_victory_dust() # 尘金粒子(P-C5 两套并存——开关=调试浮窗,会话级)
else:
_play_crest_assemble() # 碎砖拼纹章(§2.4——样张 v2 冻结批 T3 终稿;默认)
_play_seal_recall(0) # 胜利不放封印收回
if gp_gain >= 0:
_roll_gp(gp_gain, gp_total)
func open_defeat(verdict_line: String, continue_label: String, board_rect: Rect2) -> void:
# 明度不变的暮蓝色相氛围(全屏唯一暮蓝档——§2.5 最静一拍)
_dim.color = Color(COL_DUSK, 0.6) # 审计差距④同族压暗(原 0.45)
_verdict.text = verdict_line
_board_rect = board_rect
visible = true
_seal.visible = true
_seal.modulate = Color(0.4, 0.4, 0.4) # M9:失败徽记灰化(demo ⑧;旧全隐藏退役)
_cele.visible = false
_rows_box.visible = false # P-C4 维持 OQ14:失败侧不呈现账行
_play_dust_sadle() # 坐砖掸灰(§2.5——终稿)
_play_seal_recall(8)
func close() -> void:
visible = false
_seal.visible = false
_seal.modulate = Color.WHITE
_cele.visible = false
_clear_crest_bricks()
func is_open() -> bool:
return visible
func verdict_text() -> String:
return _verdict.text
# 两列账行工厂(审计差距④——名左/值右,定宽 360)
func _ledger_row() -> HBoxContainer:
var r := HBoxContainer.new()
r.custom_minimum_size = Vector2(360, 0)
return r
func _ledger_name(size: int, col: Color, _bold: bool) -> Label:
var l := UiKit.label("", size, true, col)
l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
return l
func _ledger_value(size: int, col: Color, bold: bool) -> Label:
return UiKit.label("", size, bold, col)
# 拆名/值(组合根传入整串):gp_shape=固定名「战利品 GP」值取余串;其余行按「:」或「 · 」切
#(全角冒号不入值列——T7 实机「:攻击 +1」缩进 Corrections);无分隔符=整串入值列、名空
func _set_ledger_row(name_l: Label, val_l: Label, s: String, gp_shape := false) -> void:
if gp_shape and s.begins_with("战利品 GP"):
name_l.text = "战利品 GP"
val_l.text = s.substr(6).strip_edges()
return
var sep := s.find(":")
if sep >= 0:
name_l.text = s.substr(0, sep)
val_l.text = s.substr(sep + 1).strip_edges()
return
sep = s.find(" · ")
if sep >= 0:
name_l.text = s.substr(0, sep)
val_l.text = s.substr(sep).strip_edges()
else:
name_l.text = ""
val_l.text = s
func _fx_center() -> Vector2:
return get_viewport_rect().size * 0.5
# 封印化光点收回(失败——插过的封印逐面化光点收回占位,§2.5)
func _play_seal_recall(count: int) -> void:
_fx_seal_dots = count
for i in count:
var dot := ColorRect.new()
dot.color = COL_GOLD
dot.size = Vector2(6, 6)
var base := _board_rect.get_center() if _board_rect.size != Vector2.ZERO else get_viewport_rect().size * 0.5
dot.position = base + Vector2((float(i % 5) - 2.0) * _board_rect.size.x * 0.14 if _board_rect.size.x > 0 else 0.0,
(float(i % 3) - 1.0) * _board_rect.size.y * 0.18) # 确定性散布(S2 禁全局随机——占位演出同守)
dot.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(dot)
var tw := dot.create_tween()
tw.tween_interval(UiKit.motion(0.1 * i))
tw.tween_property(dot, "position:y", dot.position.y - 120.0, UiKit.motion(0.8))
tw.parallel().tween_property(dot, "modulate:a", 0.0, UiKit.motion(0.8))
tw.tween_callback(dot.queue_free)
# ---- 演出密度终稿(样张 v2 冻结批 T3——程序化 tween,确定性散布,零新帧资产) ----
var _crest_bricks: Array[ColorRect] = [] # 纹章碎砖驻留面(close 时回收)
var _fx_crest_shards := 0
var _fx_dust_motes := 0
var _fx_seal_dots := 0
# 碎砖拼纹章(胜利 §2.4——蜡封落定后十枚碎砖自四周向蜡封外环收拢拼成纹章环,驻留至 close)
func _play_crest_assemble() -> void:
_clear_crest_bricks()
_fx_crest_shards = 10
var c := _fx_center()
for i in 10:
var brick := ColorRect.new()
brick.color = UiKit.BRICK if i % 2 == 0 else UiKit.FLOOR # 碎砖=砖面族色(掩尘/裸土——图底同源)
brick.size = Vector2(12, 7)
brick.mouse_filter = Control.MOUSE_FILTER_IGNORE
var ang := TAU * float(i) / 10.0
var ring := c + Vector2(cos(ang), sin(ang)) * 64.0
brick.position = c + Vector2(cos(ang), sin(ang)) * 200.0 # 自四周场外入场
add_child(brick)
_crest_bricks.append(brick)
var btw := brick.create_tween()
btw.tween_interval(UiKit.motion(0.52)) # 待蜡封落定(seal 0.42+0.22 弹跳中段)
btw.tween_property(brick, "position", ring - brick.size * 0.5, UiKit.motion(0.4)).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
btw.parallel().tween_property(brick, "rotation", sin(ang) * 0.35, UiKit.motion(0.4))
# 坐砖掸灰(失败 §2.5——暖灰尘絮自棋盘中带缓缓飘落,最静一拍的运动承载)
func _play_dust_sadle() -> void:
_fx_dust_motes = 8
var base := _fx_center()
for i in 8:
var mote := ColorRect.new()
mote.color = Color(UiKit.WARM_FAINT, 0.85)
mote.size = Vector2(4, 4)
mote.mouse_filter = Control.MOUSE_FILTER_IGNORE
mote.position = base + Vector2((float(i % 4) - 1.5) * 30.0, -24.0 - 8.0 * float(i / 4))
add_child(mote)
var mtw := mote.create_tween()
mtw.tween_interval(UiKit.motion(0.08 * i))
mtw.tween_property(mote, "position:y", mote.position.y + 64.0, UiKit.motion(0.9))
mtw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(0.9))
mtw.tween_callback(mote.queue_free)
# M9 庆祝立绘浮动(idle 占位——UiKit.motion 守减动效;motion_scale<1 静帧)
func _play_celebrate() -> void:
if UiKit.motion_scale < 1.0:
return
var tw := _cele.create_tween()
tw.set_loops()
tw.tween_property(_cele, "position:y", _cele.position.y - 4.0, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
tw.tween_property(_cele, "position:y", _cele.position.y, UiKit.motion(0.6)).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
# M9 尘金粒子(P-C5 备选演出——金屑自面板上带飘落闪灭;确定性散布守 S2)
func _play_victory_dust() -> void:
_fx_dust_motes += 10
var c := _fx_center()
for i in 10:
var mote := ColorRect.new()
mote.color = UiKit.CANDLE
mote.size = Vector2(5, 5)
mote.mouse_filter = Control.MOUSE_FILTER_IGNORE
mote.position = c + Vector2((float(i % 5) - 2.0) * 44.0, -40.0 - 10.0 * float(i / 5))
add_child(mote)
var tw := mote.create_tween()
tw.tween_interval(UiKit.motion(0.12 * i))
tw.tween_property(mote, "position:y", mote.position.y + 90.0, UiKit.motion(1.1))
tw.parallel().tween_property(mote, "modulate:a", 0.0, UiKit.motion(1.1))
tw.tween_callback(mote.queue_free)
# M9 GP 行数字滚动(demo ⑧——0.8s 三次缓出;减动效直出终值)
func _roll_gp(gp_gain: int, gp_total: int) -> void:
if UiKit.motion_scale < 1.0:
_gp_val.text = "+%d · 合计 %d" % [gp_gain, gp_total]
return
var tw := create_tween()
tw.tween_method(func(v: float) -> void:
_gp_val.text = "+%d · 合计 %d" % [roundi(gp_gain * v), gp_total],
0.0, 1.0, UiKit.motion(0.8)).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
func _clear_crest_bricks() -> void:
for b in _crest_bricks:
if is_instance_valid(b):
b.queue_free()
_crest_bricks.clear()
# 演出探针出口(fx_probe——胜利纹章/失败掸灰/封印光收回三断言面)
func fx_probe() -> Dictionary:
return {"crest_shards": _fx_crest_shards, "dust_motes": _fx_dust_motes, "seal_dots": _fx_seal_dots}
func _emit_continue_pressed() -> void:
continue_pressed.emit()