## VERTICAL SLICE - NOT FOR PRODUCTION ## B 区结算明细带——战斗结算步骤流到玩家文案的全部行格式化规则内聚于此(挖掘期收起 1 行上轮摘要、结算期实时流)。 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) # 明细条灰字对深灰底实测 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() # 无点击回看入口——冒险中不给数据回看(归「生涯」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[Variant, Variant] = _pending_agg[key] var row: Dictionary[Variant, Variant] = 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 袋 +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": # 伤害行无行上拆行文案——行上归因读数只剩掷点档位随行 # 护甲等级掷点留档(掷点读数与档位随行可读;吃掷点分量恒带,棋盘来源行不带) 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 "" # 伤害数字不明显——伤害值放大加色独立于行文(朱红大一号——数字即信息主角,非同字号色温微差) 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] "seal_miss": var miss_note := "" if hl == "perfect_broken": miss_note = "(本图完美封印已破——清图奖励不再加倍)" elif hl == "": miss_note = "(首次封印落空已豁免——不计入完美封印判定)" var body := "✗ %s%s" % [name_, miss_note] return prefix + (_gold_frame(body) if hl == "perfect_broken" else "[color=#%s]%s[/color]" % [UiKit.HEX_LOG_ROUND, body]) "cast": # 施放行:增益技 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()