# 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 signal review_requested const BAND_H_DIG := 24 const BAND_H_SETTLE := 84 const AGG_TYPES := ["reward_ineffective"] const COL_GOLD := UiKit.HEX_GOLD const COL_INK := UiKit.HEX_INK 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) _summary = UiKit.label("", 16, false) # 对比度修复(v3.5 批 B3 2026-09-30——明细条灰字对深灰底实测 2.63:1):暗底双色墨制=暖白族 _summary.add_theme_color_override("font_color", 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("结算明细 ▸ 点击展开回看", 14, false) _hint.add_theme_color_override("font_color", 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 gui_input.connect(_on_gui_input) 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() func _on_gui_input(ev: InputEvent) -> void: if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT: review_requested.emit() # ---- 聚合计数(未生效类行防逐行惩罚轰炸——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=#9a8a72]R%d%s[/color] " % [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 := "#e0b860" if v > 0 else "#c47c7c" 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 := "#7cc47c" if v > 0 else "#c47c7c" 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=#d9402a][size=17]-%d[/size][/color]%s%s%s%s" % [name_, v, roll_txt, tail, kill, suffix] var body_col := "#e07050" if row.get("killed", false) else ("#9a8a72" if v == 0 else "#e0a050") 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=#7cc47c]%s +%d%s%s[/color]" % [name_, v, tail, suffix] "reward_ineffective": # 未生效类行:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数 var n := (" ×%d" % agg_count) if agg_count > 1 else "" return prefix + "[s][color=#b08060]⚠ %s%s[/color][/s]%s" % [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=#9a8a72]%s[/color]" % body) "cast": # 施放行(敌人技能池批——蓄力行 charge 改型):增益技 0 伤害施放,具名技能 return prefix + "[color=#b09a72]✦ %s%s[/color]" % [name_, suffix] "decay": return prefix + "[color=#8a9ab0]状态衰减 ×%d[/color]" % v "verdict": if int(row.value) == 1: return "[color=#80d080][b]◆ %s[/b][/color]" % name_ return "[color=#e0b860][b]◆ %s[/b][/color]" % 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()