232 lines
9.9 KiB
GDScript
232 lines
9.9 KiB
GDScript
# 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()
|