Files

350 lines
13 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
## 开发调试浮窗:全局可开(组合根启动即开+两屏角标再开),当局数据只读区块为本局种子唯一查看位。
## 扩展调试项按区块往下追加(超高滚动);模拟报告文案装配归 HudPresenter——本类零动效零文案装配。
## 内部数据类 _DebugState=宿主画面登记表(只被本文件填读,文件外零消费者——字段为公开数据成员)。
class_name DebugOverlay
extends Control
signal apply_requested(atk_pct: int, hp_pct: int) ## 应用并关闭——加成落表归组合根单一应用路径
signal simulate_requested(count: int) ## 批量模拟编排归组合根(表基线复位 + 单路径施加)
signal debug_levels_requested # 两层调试对局入口(编排归 debug_session.gd)
signal diagnose_requested # 诊断转储入口(卡死取证编排归组合根)
signal closed
## 内部状态件(u7 子域拆件——契约 9 职责语句「调试浮窗运行态」):单一变化原因=调试面板状态与
## 当局数据投影。探针直读冻结成员(seed_edit/_sim_btn/_dbg_btn)住宿主;
## st.run_data/st.run_labels 经 run_data_probe()/update_run_data() 方法出口(字段直访面零外部依赖)。
class _DebugState extends RefCounted:
var db
var base_atk := 0
var base_hp := 0
var atk: HSlider
var hp: HSlider
var atk_val: Label
var hp_val: Label
var eff: Label
var sim_count: SpinBox
var sim_out: RichTextLabel
var sim_running := false # 批量模拟进行中(is_running 判据——与对局中禁用态分离防互扰)
var ctx_locked := false # 对局中上下文锁定(组合根 _sync_debug_overlay 喂入——批量模拟/两层调试入口禁用)
var run_labels := {} # 当局数据六项标签(只读投影,组合根 update_run_data 喂入)
var run_data := {} # 最近一次投喂快照(探针值对面 run_data_probe)
var st := _DebugState.new()
var seed_edit: LineEdit # 组合根 _seed_value 唯一读取面(探针直读冻结)
var _sim_btn: Button # 两层调试批量模拟入口钮(探针直读冻结)
var _dbg_btn: Button # 两层调试入口钮(对局中禁用承载面;探针直读冻结)
func _init(db_ref, base_atk: int, base_hp: int) -> void:
st.db = db_ref
st.base_atk = base_atk
st.base_hp = base_hp
_build()
_sync_labels()
visible = false # 显式初始化——不靠树序巧合(缺陷⑤家族纪律)
# ---- 对外语义面(组合根/探针)----
func atk_pct() -> int:
return int(st.atk.value)
func hp_pct() -> int:
return int(st.hp.value)
func set_sliders(atk: int, hp: int) -> void:
st.atk.value = atk
st.hp.value = hp
func reset_sliders() -> void:
st.atk.value = 0
st.hp.value = 0
_sync_labels()
func effect_text() -> String:
return st.eff.text
func set_sim_count(n: int) -> void:
st.sim_count.value = n
func is_running() -> bool:
return st.sim_running # 批量模拟进行中(与对局中禁用态分离,置灰 ≠ 在跑)
func set_sim_running(running: bool) -> void:
st.sim_running = running
_sync_sim_button()
# 对局中上下文锁定(组合根 _sync_debug_overlay 喂入):批量模拟与两层调试入口
# 在活跃对局并存态不开放(长程异步模拟/并发对局编排与活跃结算并存态破坏状态机)
func set_context_locked(locked: bool) -> void:
st.ctx_locked = locked
_sync_sim_button()
# 当局数据只读投影喂入(六项——组合根 _debug_run_data 构造;纯标签重查零动效)
func update_run_data(data: Dictionary) -> void:
st.run_data = data.duplicate()
for key in st.run_labels:
(st.run_labels[key] as Label).text = str(data.get(key, "—"))
# 当局数据快照读面(探针值对面——ui_tune_probe 断言六项与只读访问器逐项一致)
func run_data_probe() -> Dictionary:
return st.run_data.duplicate()
# 两钮同判据(模拟运行 / 对局上下文锁定对「模拟」「两层调试」两入口等价)——
# 判据单点:两 setter 都经此同步,禁在调用点各写一份
func _sync_sim_button() -> void:
_sim_btn.disabled = st.sim_running or st.ctx_locked
_sim_btn.text = "模拟中…" if st.sim_running else "运行模拟"
_dbg_btn.disabled = st.sim_running or st.ctx_locked
func set_sim_progress(text: String) -> void:
st.sim_out.text = text
func set_sim_report(text: String) -> void:
st.sim_out.text = text
func output_text() -> String:
return st.sim_out.get_parsed_text()
# ---- 探针接缝(真实翻译出口——按钮 pressed 信号路径,非直调组合根)----
func press_apply_for_probe() -> void:
_apply_btn_pressed()
func press_reset_for_probe() -> void:
_reset_btn_pressed()
func press_simulate_for_probe() -> void:
_sim_btn.pressed.emit()
# ---- 内部(信号 → 外发)----
func _apply_btn_pressed() -> void:
apply_requested.emit(atk_pct(), hp_pct())
func _reset_btn_pressed() -> void:
reset_sliders()
func effective_atk() -> int:
return UiKit.scaled(st.base_atk, atk_pct())
func effective_max_hp() -> int:
return UiKit.scaled(st.base_hp, hp_pct())
# ---- 内部构建(标题/关闭固定于浮窗骨架,可扩展列表区居中滚动)----
func _build() -> void:
var shell: Dictionary[Variant, Variant] = UiKit.modal_shell(self, UiKit.BG_OVERLAY, Vector2(640, 0), 8)
var box: VBoxContainer = shell.box
_build_intro(box)
# 可扩展列表页(后续调试项按区块往下追加——超高滚动,滚轮下探)
var scroll := ScrollContainer.new()
scroll.custom_minimum_size = Vector2(0, 430)
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
box.add_child(scroll)
var list := VBoxContainer.new()
list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
list.add_theme_constant_override("separation", 8)
scroll.add_child(list)
_build_run_data(list)
_build_fx_row(list)
_build_seed_row(list)
_build_slider_block(list)
_build_sim_block(list)
_build_debug_diag(list)
var close_btn := UiKit.button("关闭")
close_btn.pressed.connect(_emit_closed)
box.add_child(close_btn)
## 头部域:标题+说明(百分比叠加基础值——影响所有模式,不落盘)
func _build_intro(box: VBoxContainer) -> void:
var title := UiKit.label("调试",UiKit.FS_TITLE, true, UiKit.WARM)
box.add_child(title)
var desc := UiKit.label(
"开发调试专用(UI 最顶层「调试」浮层角标随时可开——任何界面不遮挡)。当局数据=本局只读投影,种子即本局种子唯一查看位。\n百分比叠加基础值:攻击 %d / 生命 %d。影响所有模式(手动与模拟),不落盘。" % [st.base_atk, st.base_hp],
13, false, UiKit.WARM_SOFT)
box.add_child(desc)
box.add_child(HSeparator.new())
## 区块·当局数据(六项只读投影:种子=本局种子唯一查看位〔adventure
## .master_seed 现值,非 _seed_value() 重算〕/关卡/节点/HP/GP/钥石;数据由组合根开窗与模态开合
## 时 update_run_data 喂入;纯标签零信号连接零动效)
func _build_run_data(list: VBoxContainer) -> void:
var run_head := UiKit.label("当局数据(只读)",UiKit.FS_HINT, true, UiKit.WARM)
list.add_child(run_head)
var run_grid := GridContainer.new()
run_grid.columns = 2
run_grid.add_theme_constant_override("h_separation", 14)
run_grid.add_theme_constant_override("v_separation", 2)
list.add_child(run_grid)
for item in [["seed", "种子"], ["level", "关卡"], ["node", "节点"], ["hp", "HP"], ["gp", "GP"], ["keystone", "钥石"]]:
var cap := UiKit.label(String(item[1]),UiKit.FS_HINT, false, UiKit.WARM_FAINT)
cap.custom_minimum_size = Vector2(52, 0)
run_grid.add_child(cap)
var val := UiKit.label("—",UiKit.FS_HINT, false, UiKit.WARM)
st.run_labels[item[0]] = val
run_grid.add_child(val)
list.add_child(HSeparator.new())
## 区块⓪胜利演出风格(两套并存——纹章拼合[默认]/尘金粒子;会话级不落盘)
func _build_fx_row(list: VBoxContainer) -> void:
var fx_row := HBoxContainer.new()
fx_row.add_theme_constant_override("separation", 10)
list.add_child(fx_row)
fx_row.add_child(UiKit.label("胜利演出",UiKit.FS_HINT, false, UiKit.WARM))
var fx_btn := UiKit.button("纹章拼合" if UiKit.victory_fx_style == "crest" else "尘金粒子")
fx_btn.tooltip_text = "两套演出并存——点击切换(会话级,不落盘)"
fx_btn.pressed.connect(_on_fx_toggle.bind(fx_btn)) # lambda 转命名方法(只读巡检白名单方法名精确可判)
fx_row.add_child(fx_btn)
## 区块①随机种子(种子三分判定归组合根 _seed_value 单点):
## 默认空框(空=随全局规则:首局恒 42/非首局随机;显式填入全场景优先)
func _build_seed_row(list: VBoxContainer) -> void:
var seed_row := HBoxContainer.new()
list.add_child(seed_row)
seed_row.add_child(UiKit.label("随机种子",UiKit.FS_BODY, false))
seed_edit = LineEdit.new()
seed_edit.text = ""
seed_edit.placeholder_text = "留空=随机 · 首局恒 42"
seed_edit.custom_minimum_size = Vector2(120, 0)
seed_row.add_child(seed_edit)
list.add_child(HSeparator.new())
## 区块②角色数值调整板(攻击/生命两滑轨+生效值行+应用/重置钮)
func _build_slider_block(list: VBoxContainer) -> void:
var atk_row := _slider_row(list, "攻击加成")
st.atk = atk_row.slider
st.atk_val = atk_row.value_label
var hp_row := _slider_row(list, "生命加成")
st.hp = hp_row.slider
st.hp_val = hp_row.value_label
st.eff = UiKit.label("",UiKit.FS_HINT, false)
list.add_child(st.eff)
var btn_row := HBoxContainer.new()
list.add_child(btn_row)
var apply_btn := UiKit.button("应用并关闭")
apply_btn.pressed.connect(_apply_btn_pressed)
btn_row.add_child(apply_btn)
var reset_btn := UiKit.button("重置为 0")
reset_btn.pressed.connect(_reset_btn_pressed)
btn_row.add_child(reset_btn)
list.add_child(HSeparator.new())
## 区块③批量模拟(与 headless 扫描同口径——编排归 tune_sim_controller)
func _build_sim_block(list: VBoxContainer) -> void:
var sim_row := HBoxContainer.new()
list.add_child(sim_row)
sim_row.add_child(UiKit.label("批量模拟第一关",UiKit.FS_BODY, false))
st.sim_count = SpinBox.new()
st.sim_count.min_value = 10
st.sim_count.max_value = 200
st.sim_count.step = 10
st.sim_count.value = 30
st.sim_count.custom_minimum_size = Vector2(90, 0)
sim_row.add_child(st.sim_count)
sim_row.add_child(UiKit.label("次(理性玩家策略,含面板当前加成)",UiKit.FS_HINT, false))
_sim_btn = UiKit.button("运行模拟")
_sim_btn.pressed.connect(_emit_simulate_requested)
sim_row.add_child(_sim_btn)
st.sim_out = RichTextLabel.new()
st.sim_out.bbcode_enabled = true
st.sim_out.custom_minimum_size = Vector2(0, 300)
list.add_child(st.sim_out)
list.add_child(HSeparator.new())
## 区块④⑤两层调试入口与诊断转储(对局中禁用;转储编排经信号外发归组合根——纯视图边界;
## dump_diagnosis 卡死取证功能保留,不设门控隐藏——本浮窗本就可全局可开,双轨无意义)
func _build_debug_diag(list: VBoxContainer) -> void:
_dbg_btn = UiKit.button("调试 · 连续关卡两层(HP 关内延续)")
_dbg_btn.pressed.connect(_emit_debug_levels)
list.add_child(_dbg_btn)
var diag_row := HBoxContainer.new()
diag_row.add_theme_constant_override("separation", 10)
list.add_child(diag_row)
diag_row.add_child(UiKit.label("诊断转储",UiKit.FS_BODY, false))
var diag_btn := UiKit.button("转储")
diag_btn.tooltip_text = "卡死取证:全量核心状态+环形缓冲 → user://logs/diag_*.json"
diag_btn.pressed.connect(_emit_diagnose_requested)
diag_row.add_child(diag_btn)
func _slider_row(box: VBoxContainer, title: String) -> Dictionary:
var row := HBoxContainer.new()
box.add_child(row)
row.add_child(UiKit.label(title,UiKit.FS_BODY, false))
var s := HSlider.new()
s.min_value = 0
s.max_value = 300
s.step = 5
s.custom_minimum_size = Vector2(320, 24) # 皮肤走全局主题(与设置屏音量滑轨同一构建方式,消双轨)
row.add_child(s)
var v := UiKit.label("+0%",UiKit.FS_BODY, true)
v.custom_minimum_size = Vector2(70, 0)
row.add_child(v)
s.value_changed.connect(_on_slider_value_changed)
return {"slider": s, "value_label": v}
func _sync_labels() -> void:
st.atk_val.text = "+%d%%" % atk_pct()
st.hp_val.text = "+%d%%" % hp_pct()
st.eff.text = "生效值:攻击 %d → %d · 生命 %d → %d" % [
st.base_atk, effective_atk(), st.base_hp, effective_max_hp()]
func _emit_simulate_requested() -> void:
simulate_requested.emit(int(st.sim_count.value))
func _emit_diagnose_requested() -> void:
diagnose_requested.emit()
func _emit_debug_levels() -> void:
debug_levels_requested.emit()
func _emit_closed() -> void:
closed.emit()
func _on_slider_value_changed(_value: float) -> void:
_sync_labels()
# 胜利演出切换(命名方法,只读巡检白名单承载)
func _on_fx_toggle(btn: Button) -> void:
UiKit.victory_fx_style = "dust" if UiKit.victory_fx_style == "crest" else "crest"
btn.text = "纹章拼合" if UiKit.victory_fx_style == "crest" else "尘金粒子"