Files
gplan-2/game/presentation/views/battle_log_view.gd
T

232 lines
9.9 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
# 实现 GDD:design/ux/battle.md(B 区结算明细带改造——挖掘期收起 1 行上轮摘要 / 结算期 84px
# 实时流 / 点击展开回看 overlay〔pattern 6 模态纪律〕;行模板「{行为} {值} → {目标}(来源)」、
# 聚合计数行、零值行可见、效果行 value≠pool_value 禁相加、未生效四类行 icon/划线形状冗余、
# highlight 金框〔flag_hit/clear_reward_h2/perfect_broken〕、击杀标记)。
# Date: 2026-09-12(基础 = 2026-09-11 13 类行协议格式化版;2026-09-15 格面资源批 15→10 类:
# 删 item / consumed_ineffective / use_rejected / window_open / window_skip 五类——可选类道具与窗口下线,
# 战斗结算 行协议清单同步;2026-09-16 护甲等级掷点批:伤害行补掷点读数/档位随行标注 + nat20 金框高亮;
# 同日 敌人技能池批:施放行 = 蓄力行 charge 改型〔增益技 0 伤害施放、具名技能〕;
# 2026-09-20 八型批呈现层漏网修复(真人试玩卡断):伏击退役拆除伤害行 attach 字段后,
# 基线+伏击与普通+穿刺+魔法三分量两拆行分支未同步拆除——敌方行(带 baseline 无 attach)
# 格式化即 Invalid access,结算段中途夭折、界面卡死;两分支随批拆除,行上归因只剩掷点随行)
# 高内聚边界:战斗结算 步骤流 → 玩家文案的全部格式化规则内聚于此;来源文案映射来自 SettlementTable。
class_name BattleLogView
extends PanelContainer
const BAND_H_DIG := 24
const BAND_H_SETTLE := 84
const AGG_TYPES := ["reward_ineffective"]
const COL_GOLD := UiKit.HEX_GOLD
var _db
var _log: RichTextLabel
var _summary: Label
var _hint: Label
var _mode := "dig" # dig = 收起摘要 / settle = 实时流(模式归组合根驱动)
var _round_rows: Array[Dictionary] = [] # 本结算轮行缓冲(上轮摘要构建源)
var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count})
func _init(db_ref) -> void:
_db = db_ref
custom_minimum_size = Vector2(0, BAND_H_DIG)
size_flags_vertical = Control.SIZE_SHRINK_END
var vb := VBoxContainer.new()
vb.add_theme_constant_override("separation", 2)
add_child(vb)
var head := HBoxContainer.new()
vb.add_child(head)
# 对比度修复(v3.5 批 B3 2026-09-30——明细条灰字对深灰底实测 2.63:1):暗底双色墨制=暖白族
_summary = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM)
_summary.custom_minimum_size = Vector2(0, BAND_H_DIG - 4)
_summary.clip_text = true
_summary.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
head.add_child(_summary)
_hint = UiKit.label("上轮摘要(回看归「生涯」)",UiKit.FS_HINT, false, UiKit.WARM) # 辅助提示(≥14 辅助档)随上暖白
_hint.size_flags_horizontal = Control.SIZE_SHRINK_END
head.add_child(_hint)
_log = RichTextLabel.new()
_log.bbcode_enabled = true
_log.scroll_following = true
_log.custom_minimum_size = Vector2(0, BAND_H_SETTLE - 26)
_log.clip_contents = true
_log.scroll_active = true
vb.add_child(_log)
_log.visible = false
func set_mode(mode: String) -> void:
if _mode == mode:
return
_mode = mode
var settle := mode == "settle"
_log.visible = settle
_summary.visible = not settle
custom_minimum_size = Vector2(0, BAND_H_SETTLE if settle else BAND_H_DIG)
func mode() -> String:
return _mode
func clear() -> void:
_log.clear()
_round_rows.clear()
_pending_agg = {}
_summary.text = ""
# 每结算轮结束(下一轮开始/终局)——刷新挖掘期"上轮摘要"行
func finish_settlement_round() -> void:
_flush_pending_agg()
if _round_rows.is_empty():
return
var parts: Array[String] = []
for bb in _round_rows:
parts.append(_strip_bb(str(bb)))
var text := "上轮: " + " | ".join(parts)
if text.length() > 190:
text = text.substr(0, 190) + "…"
_summary.text = text
_round_rows.clear()
func append_bb(bb: String) -> void:
_flush_pending_agg()
_log.append_text(bb + "\n")
func append_row(row: Dictionary) -> void:
if AGG_TYPES.has(String(row.get("type", ""))):
_accumulate_agg(row)
return
_flush_pending_agg()
var bb := _format_row(row)
_round_rows.append(bb)
_log.append_text(bb + "\n")
func parsed_text() -> String:
return _log.get_parsed_text()
# M9(P-C7 裁):带点击回看入口退役——冒险中不给数据回看(归「生涯」06 登记);带保留单行摘要显示
# ---- 聚合计数(未生效类行防逐行惩罚轰炸——pattern 3 聚合纪律) ----
func _accumulate_agg(row: Dictionary) -> void:
var key := "%s|%s" % [String(row.get("type", "")), String(row.get("batch_key", ""))]
if _pending_agg.has(key):
_pending_agg[key]["count"] = int(_pending_agg[key]["count"]) + 1
else:
_flush_pending_agg()
_pending_agg[key] = {"row": row, "count": 1}
func _flush_pending_agg() -> void:
if _pending_agg.is_empty():
return
for key in _pending_agg:
var item: Dictionary = _pending_agg[key]
var row: Dictionary = item["row"]
var count: int = item["count"]
var bb := _format_row(row, count)
_round_rows.append(bb)
_log.append_text(bb + "\n")
_pending_agg = {}
# ---- 行格式化(模板「{行为} {值} → {目标}(来源)」) ----
func _source_label(src_key: String) -> String:
if src_key == "":
return ""
return String(_db.settlement.source_label_map.get(src_key, src_key))
func _gold_frame(text: String) -> String:
# highlight 金框强调(形状+色彩双通道:■ 前缀 + 金底)
return "[bgcolor=%s][color=#%s]■ %s[/color][/bgcolor]" % [COL_GOLD, UiKit.HEX_ON_GOLD, text]
func _format_row(row: Dictionary, agg_count := -1) -> String:
var src := _source_label(String(row.get("source_class", "")))
var round_no: int = int(row.get("round_index", 0))
var prefix := " [color=#%s]R%d%s[/color] " % [UiKit.HEX_LOG_ROUND, round_no, ("·" + src) if src != "" else ""]
var hl := String(row.get("highlight", ""))
var v: int = int(row.get("value", 0))
var name_: String = String(row.get("display_name", ""))
var target := String(row.get("target", ""))
var tail := " → %s" % target if target != "" else ""
var suffix := "(%s)" % src if src != "" else ""
match String(row.get("type", "")):
"effect":
# GP 型效果行(GP+商店房实施批 2026-09-21):「金币袋 +3」兑付反馈——value = 本行 ±power,
# escrow = 战斗内缓冲现值(入钱包在战斗末一次上抛——呈现语义与增益池分账)
if String(row.get("effect_id", "")) in ["gp_gain", "gp_loss"]:
var gp_col := UiKit.HEX_LOG_GAIN if v > 0 else UiKit.HEX_LOG_LOSS
var gp_body := "%s %+d(战斗暂存 %+d)%s" % [name_, v, int(row.get("escrow", 0)), suffix]
return prefix + (_gold_frame(gp_body) if hl != "" else "[color=%s]%s[/color]" % [gp_col, gp_body])
# 效果行 value = 单次贡献值,pool_value = 池合成值——禁相加呈现(战斗结算 呈现细则)
var pool: int = int(row.get("pool_value", 0))
var col := UiKit.HEX_LOG_HEAL if v > 0 else UiKit.HEX_LOG_LOSS
var body := "%s 单次%+d(池 %+d)%s" % [name_, v, pool, suffix]
return prefix + (_gold_frame(body) if hl != "" else "[color=%s]%s[/color]" % [col, body])
"damage":
# 伤害行无行上拆行文案——基线+伏击与三分量两旧归因拆行已随伏击退役批(2026-09-20)
# 与充能退役批(2026-09-19)出协议,行上归因读数只剩掷点档位随行
# 护甲等级掷点留档(2026-09-16 掷点批——掷点读数与档位随行可读;吃掷点分量恒带,棋盘来源行不带)
var roll_txt := ""
if row.get("roll", null) != null:
roll_txt = "〔掷 %d vs 护甲 %d" % [int(row.roll), int(row.ac)]
if bool(row.get("crit", false)):
roll_txt += " · 暴击×%d" % int(_db.settlement.crit_multiplier)
elif int(row.tier) < 100:
roll_txt += " · ×%d%%" % int(row.tier)
roll_txt += "〕"
var kill := " ⚔击杀!" if row.get("killed", false) else ""
# 二轮修改批 C(2026-09-29 用户目检——「每次结算标出的伤害数字不明显」):
# 伤害值放大加色独立于行文(朱红大一号——数字即信息主角,非同字号色温微差)
var body := "%s [color=#%s][size=17]-%d[/size][/color]%s%s%s%s" % [name_, UiKit.HEX_LOG_DMG_NUM, v, roll_txt, tail, kill, suffix]
var body_col := UiKit.HEX_LOG_DMG_KILL if row.get("killed", false) else (UiKit.HEX_LOG_ROUND if v == 0 else UiKit.HEX_LOG_DMG_BODY)
if bool(row.get("crit", false)):
return prefix + _gold_frame(body) # nat 最大面暴击 = 金框高亮行
return prefix + "[color=%s]%s[/color]" % [body_col, body]
"heal":
return prefix + "[color=#%s]%s +%d%s%s[/color]" % [UiKit.HEX_LOG_HEAL, name_, v, tail, suffix]
"reward_ineffective":
# 未生效类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
var n := (" ×%d" % agg_count) if agg_count > 1 else ""
return prefix + "[s][color=#%s]⚠ %s%s[/color][/s]%s" % [UiKit.HEX_LOG_VOIDED, name_, n, suffix]
"flag_miss":
var body := "✗ %s%s" % [name_, "(本板完美排雷已破——归因此旗)" if hl == "perfect_broken" else ""]
return prefix + (_gold_frame(body) if hl == "perfect_broken" else "[color=#%s]%s[/color]" % [UiKit.HEX_LOG_ROUND, body])
"cast":
# 施放行(敌人技能池批——蓄力行 charge 改型):增益技 0 伤害施放,具名技能
return prefix + "[color=#%s]✦ %s%s[/color]" % [UiKit.HEX_LOG_CAST, name_, suffix]
"decay":
return prefix + "[color=#%s]状态衰减 ×%d[/color]" % [UiKit.HEX_LOG_DECAY, v]
"verdict":
if int(row.value) == 1:
return "[color=#%s][b]◆ %s[/b][/color]" % [UiKit.HEX_LOG_WIN, name_]
return "[color=#%s][b]◆ %s[/b][/color]" % [UiKit.HEX_LOG_GAIN, name_]
"highlight":
return prefix + _gold_frame("★ %s" % name_)
return prefix + name_ + suffix
func _strip_bb(bb: String) -> String:
var out := ""
var in_tag := false
for ch in bb:
if ch == "[":
in_tag = true
elif ch == "]":
in_tag = false
elif not in_tag:
out += ch
return out.replace(" ", " ").strip_edges()