Files
gplan-2/game/presentation/views/battle_end_overlay.gd
T
DKChan 9a72394652 chore(代码卫生): 头注存量清偿批——82 文件按两类白名单改写为 ## 文档注释载体
按 code-conventions §1.4.1 第三轮定稿执行:core 22+schemas 18+presentation 根 14+views 28,全部头注 #→##、删日期/变更叙事/GDD 与架构反向链接/字段与域清单/机制描述,保留职责摘要+真实使用约束(防事故禁令/单一入口/信号契约每信号一行)。main.gd 31→5 行、save_manager 45→5 行、adventure_runner 37→5 行为最大单件。子代理分模块执行(波一 core+schemas、波二 presentation+views,并发≤2)。验收=tools/verify_comment_only_change.py 逐字节校验(82 文件头注区外逐行一致=零行为变更)+audit_file_headers 全绿;纯注释批免冒烟(先例 5843270)
2026-10-16 23:28:07 +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 flag_tex := UiKit.load_tex("res://assets/art/board/flag_seal.png")
if flag_tex != null:
seal_img.texture = flag_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_flag_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_flag_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_flag_recall(count: int) -> void:
_fx_flag_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_flag_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, "flag_dots": _fx_flag_dots}
func _emit_continue_pressed() -> void:
continue_pressed.emit()