Files
DKChan 4b174877be 补提交(布局几何单点化批·代码面+工具面+architecture行·跨会话遗留): battle_layout.tres 新表单行五字段(stage_h/info_panel_w/ap_bar_h/log_band_h_dig/log_band_h_settle)经 schema battle_layout_table.gd+域容器 config_domain_presentation.gd 收进 ConfigDb.presentation(ConfigDB 三容器→四容器,manifest 增 battle_layout 键,B区高度裁决 2026-10-30 用户裁=取代码标准 stage_h);views 五件构造签名增 layout 参(BattleStageView/HeroInfoPanelView/BoardZoneView/ApBarView/BattleLogView 常量高度改表值驱动)+ui_assembler 装配传 layout+probes 两件改址+main.gd 结算带注释去数值;tools/layout_config.py 新件+render_sample_sheet 两版读布局表现算(样张工具面);文档面: architecture.md ConfigDB 行换代(四容器成员 7/7/5/1、类变量 10)+HUD框架.md 三区表/装板不等式/调参表去数值化改指布局表+数据配置.md+interaction-patterns.md 明细带句去数值(battle.md 区表与待办清单登记面已随 1cdef6ba 在库);消费漏网 L1 就地补改九处(构造签名换型与 iter45 断点件收编的测试改址漏网,判据零变化): theme_projection_test:386/player_copy_sweep_test:44,98/adventure_feats_test:772/standee_fx_sweep_test:75/feat_vouchers_test:292 六处补 layout 实参,adventure_feats_test:791/level_shop_economy_test:1480/feat_vouchers_test:81 三处断点成员直读改经 a._bp_store(iter45卡②六成员搬移漏网,02 闸合回后首全量暴露);load_tables 超函数长键清偿(容器装配块抽 _assign_loaded 私有协函数——表行按域收进四容器+resolver 注入单一职责,与 _cross_validate 委托先例同型,S13 新增红修至基线口径内);窄验: 四件修复套件单跑全绿+gdstyle 基线外新违规 0
2026-10-31 05:05:08 +08:00

277 lines
10 KiB
GDScript
Raw Permalink 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
## B 区结算明细带——战斗结算步骤流到玩家文案的全部行格式化规则内聚于此(挖掘期收起 1 行上轮摘要、结算期实时流)。
class_name BattleLogView
extends PanelContainer
const AGG_TYPES := ["reward_ineffective"]
const COL_GOLD := UiKit.HEX_GOLD
var _db
var _band_h_dig := 0
var _band_h_settle := 0
var _log: RichTextLabel
var _summary: Label
var _hint: Label
var _mode := "dig" # dig = 收起摘要 / settle = 实时流(模式归组合根驱动)
var _round_rows: Array[String] = [] # 本结算轮行缓冲(上轮摘要构建源——append 的是 _format_row 格式化产物 String)
var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count})
func _init(db_ref, layout: BattleLayoutTable) -> void:
_db = db_ref
_band_h_dig = layout.log_band_h_dig
_band_h_settle = layout.log_band_h_settle
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.combat.settlement.core.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 suffix := "(%s)" % src if src != "" else ""
var kind := String(row.get("type", ""))
var body := ""
match kind:
"effect":
body = _fmt_effect(row, prefix, suffix)
"damage":
body = _fmt_damage(row, prefix, suffix)
"heal", "cast", "decay":
body = _fmt_simple(kind, row, prefix, suffix)
"reward_ineffective":
body = _fmt_ineffective(row, prefix, suffix, agg_count)
"seal_miss":
body = _fmt_seal_miss(row, prefix)
"verdict":
body = _fmt_verdict(row)
_:
body = _fmt_default(row, prefix, suffix)
return body
## 简单行域(heal/cast/decay 三型——单行单墨直排)
func _fmt_simple(kind: String, row: Dictionary, prefix: String, suffix: String) -> String:
var name_: String = String(row.get("display_name", ""))
match kind:
"heal":
return prefix + "[color=#%s]%s +%d%s%s[/color]" % [UiKit.HEX_LOG_HEAL, name_, int(row.get("value", 0)), _tail_of(row), suffix]
"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, int(row.get("value", 0))]
return ""
## 默认行域(未知型/高亮星标——未知型=名行直排)
func _fmt_default(row: Dictionary, prefix: String, suffix: String) -> String:
var name_: String = String(row.get("display_name", ""))
if String(row.get("type", "")) == "highlight":
return prefix + _gold_frame("★ %s" % name_)
return prefix + name_ + suffix
## 尾注域:目标格指向(行文附加)
func _tail_of(row: Dictionary) -> String:
var target := String(row.get("target", ""))
return " → %s" % target if target != "" else ""
## GP 型效果行域:「GP 袋 +3」兑付反馈——value = 本行 ±power,
## escrow = 战斗内缓冲现值(入钱包在战斗末一次上抛——呈现语义与增益池分账)
func _fmt_effect(row: Dictionary, prefix: String, suffix: String) -> String:
var v: int = int(row.get("value", 0))
var hl := String(row.get("highlight", ""))
var name_: String = String(row.get("display_name", ""))
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])
## 伤害行域:行上归因读数只剩掷点档位随行(护甲等级掷点留档——掷点读数与档位随行可读;
## 吃掷点分量恒带,棋盘来源行不带)。伤害数字不明显——伤害值放大加色独立于行文(朱红大一号)
func _fmt_damage(row: Dictionary, prefix: String, suffix: String) -> String:
var v: int = int(row.get("value", 0))
var name_: String = String(row.get("display_name", ""))
var tail := _tail_of(row)
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.combat.settlement.damage_roll.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]
if bool(row.get("crit", false)):
return prefix + _gold_frame(body) # nat 最大面暴击 = 金框高亮行
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)
return prefix + "[color=%s]%s[/color]" % [body_col, body]
## 未生效行域:⚠ icon + 划线形状冗余(非色相独载)+ 聚合计数
func _fmt_ineffective(row: Dictionary, prefix: String, suffix: String, agg_count: int) -> String:
var name_: String = String(row.get("display_name", ""))
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]
## 封印落空行域:完美破/首失豁免两注记分档(金框=破完美/普通墨=豁免)
func _fmt_seal_miss(row: Dictionary, prefix: String) -> String:
var hl := String(row.get("highlight", ""))
var name_: String = String(row.get("display_name", ""))
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])
## 裁定行域:胜金/负绿双墨(粗体菱形头)
func _fmt_verdict(row: Dictionary) -> String:
var name_: String = String(row.get("display_name", ""))
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_]
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()