- _emit_row("seal_miss") 后移到 _fx_emit("seal_missed") 循环之后,使 _perfect_forgiven 先就位
- 判据从 RAW(_perfect_hit_false==1)改为有效误标(hit_false - forgiven == 1)
- battle_log_view seal_miss 文案两态:已破态加金框、豁免态普通色
- "本板"→"本图" 概念对齐
- 修复 suffix 变量遮蔽(改名 miss_note)
224 lines
8.5 KiB
GDScript
224 lines
8.5 KiB
GDScript
## 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()
|