Files
mtgodot-poc/project/ui/game_option_ui.gd
T

365 lines
14 KiB
GDScript
Raw 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.
# GameOptionUI (P11) —— 游戏设置窗(1:1 迁移 `assets/root/uigameoption.py` `OptionDialog`)。
#
# var go := preload("res://ui/game_option_ui.gd").new()
# add_child(go)
# go.setup(ui_manager, m2client, assets_root)
# go.toggle() # 由 system_menu_ui 的 game_option_button 打开
#
# 布局走真 `assets/uiscript/uiscript/gameoptiondialog.py`。绑定逐字对照 uigameoption.py
# block_{exchange,party,guild,whisper,friend,party_request}_button (toggle) →
# `/setblockmode <mask ^ bit>`EBlockAction 位:1<<0..1<<5)—— 真聊天命令
# pvp_{peace,revenge,guild,free} (radio) → `/pkmode {0,1,4,2}` —— 真聊天命令
# name_color / target_board / view_chat / always_show_name / show_damage / salestext (radio)
# —— 客户端显示开关,持久化到 user://system_option.cfg [gameopt];渲染节点按需读取这些设置。
extends Node
const MobileTouchButton = preload("res://ui/mobile/mobile_touch_button.gd")
# Packet.h EBlockAction
const BLOCK_BITS := {
"block_exchange_button": 1 << 0,
"block_party_button": 1 << 1,
"block_guild_button": 1 << 2,
"block_whisper_button": 1 << 3,
"block_friend_button": 1 << 4,
"block_party_request_button": 1 << 5,
}
# uigameoption: peace /pkmode 0, revenge /pkmode 1, free /pkmode 2, guild /pkmode 4
const PK_CMD := {"pvp_peace": 0, "pvp_revenge": 1, "pvp_free": 2, "pvp_guild": 4}
# 客户端显示开关:{radio 名 -> [cfg key, 该按钮代表的值]}
const DISPLAY_RADIOS := {
"name_color_normal": ["name_color", 0], "name_color_empire": ["name_color", 1],
"target_board_no_view": ["target_board", 0], "target_board_view": ["target_board", 1],
"view_chat_on_button": ["view_chat", 1], "view_chat_off_button": ["view_chat", 0],
"always_show_name_on_button": ["always_show_name", 1], "always_show_name_off_button": ["always_show_name", 0],
"show_damage_on_button": ["show_damage", 1], "show_damage_off_button": ["show_damage", 0],
"salestext_on_button": ["salestext", 1], "salestext_off_button": ["salestext", 0],
}
const RADIO_GROUPS := [
["name_color_normal", "name_color_empire"],
["target_board_no_view", "target_board_view"],
["view_chat_on_button", "view_chat_off_button"],
["always_show_name_on_button", "always_show_name_off_button"],
["show_damage_on_button", "show_damage_off_button"],
["salestext_on_button", "salestext_off_button"],
]
const LABELS := {
"titlename": "游戏设置", "name_color": "名字颜色", "target_board": "目标框",
"pvp_mode": "PK 模式", "block": "屏蔽", "chat": "聊天显示",
"always_show_name": "总显示名字", "effect_on_off": "伤害数字", "salestext_on_off": "叫卖文字",
"name_color_normal": "普通", "name_color_empire": "阵营",
"target_board_no_view": "不看他国", "target_board_view": "看他国",
"pvp_peace": "和平", "pvp_revenge": "反击", "pvp_guild": "帮会", "pvp_free": "自由",
"block_exchange_button": "交易", "block_party_button": "组队", "block_guild_button": "帮会",
"block_whisper_button": "密语", "block_friend_button": "好友", "block_party_request_button": "组队申请",
"view_chat_on_button": "开", "view_chat_off_button": "关",
"always_show_name_on_button": "开", "always_show_name_off_button": "关",
"show_damage_on_button": "开", "show_damage_off_button": "关",
"salestext_on_button": "开", "salestext_off_button": "关",
}
const CFG_PATH := "user://system_option.cfg"
# uigameoption RefreshAlwaysShowName / 名字色 / 伤害数字等:radio 选中后广播给渲染层。
# key ∈ DISPLAY_RADIOS 的 cfg keyname_color / always_show_name / show_damage / ...)。
signal display_option_changed(key: String, value: int)
var ui: CanvasLayer
var client: Node
var assets_root := ""
var uiscript_dir := ""
var _win: Dictionary = {}
var _cfg := ConfigFile.new()
var _block_mode := 0 # blockMode(本地跟踪 + 服务端回包同步)
var _display := {} # cfg [gameopt] 快照
var _mobile_mode := false
var _mobile_root: Control
static func config_path() -> String:
var override_dir := OS.get_environment("MT_CONFIG_DIR")
if override_dir != "":
return override_dir.path_join("system_option.cfg")
return OS.get_temp_dir().path_join("mtgodot_system_option.cfg") \
if DisplayServer.get_name() == "headless" else CFG_PATH
func setup(ui_manager: CanvasLayer, m2client: Node, assets := "") -> void:
ui = ui_manager
client = m2client
assets_root = assets
if assets_root == "" and ui and "assets_root" in ui:
assets_root = ui.assets_root
uiscript_dir = assets_root.path_join("uiscript/uiscript")
if not DirAccess.dir_exists_absolute(uiscript_dir):
uiscript_dir = assets_root.path_join("uiscript")
_cfg.load(config_path())
for k in ["name_color", "target_board", "view_chat", "always_show_name", "show_damage", "salestext"]:
_display[k] = int(_cfg.get_value("gameopt", k, 1 if k in ["target_board", "view_chat", "always_show_name", "show_damage"] else 0))
_block_mode = int(_cfg.get_value("gameopt", "block_mode", 0))
if client and client.has_signal("block_mode_changed"):
client.block_mode_changed.connect(_on_server_block_mode)
func is_open() -> bool:
if _mobile_mode:
return _mobile_root != null and is_instance_valid(_mobile_root)
return not _win.is_empty() and is_instance_valid(_win.get("root"))
func set_mobile_mode(enabled: bool) -> void:
if not enabled and _mobile_root != null:
var old_mode := _mobile_mode
_mobile_mode = true
close()
_mobile_mode = old_mode
_mobile_mode = enabled
# 渲染层开局回填用:cfg [gameopt] 里某显示开关的当前值(0/1),缺省同 setup() 的默认。
func display_value(key: String) -> int:
return int(_display.get(key, 0))
func toggle() -> void:
if is_open(): close()
else: open()
func close() -> void:
if _mobile_mode:
if _mobile_root and is_instance_valid(_mobile_root) and ui:
ui.close(_mobile_root)
_mobile_root = null
return
if is_open():
ui.close(_win["root"])
_win = {}
func open() -> void:
if _mobile_mode:
_open_mobile()
return
if is_open():
return
var path := uiscript_dir.path_join("gameoptiondialog.py")
if not FileAccess.file_exists(path):
push_warning("GameOptionUI: no gameoptiondialog.py at " + path)
return
_win = ui.open_script(path, assets_root)
if not is_open():
return
_relabel()
_wire()
_sync()
func _open_mobile() -> void:
if is_open():
return
_mobile_root = Control.new()
_mobile_root.name = "MobileGameOptionWindow"
_mobile_root.size = Vector2(700, 360)
_mobile_root.set_meta("mobile_title", "游戏设置")
_mobile_root.mouse_filter = Control.MOUSE_FILTER_STOP
var panel := Panel.new()
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
panel.add_theme_stylebox_override("panel", _mobile_panel_style())
_mobile_root.add_child(panel)
var scroll := ScrollContainer.new()
scroll.name = "MobileGameOptionScroll"
scroll.position = Vector2(14, 48)
scroll.size = Vector2(672, 298)
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
scroll.mouse_filter = Control.MOUSE_FILTER_STOP
_mobile_root.add_child(scroll)
var content := VBoxContainer.new()
content.custom_minimum_size = Vector2(650, 0)
content.add_theme_constant_override("separation", 8)
scroll.add_child(content)
_add_mobile_section_title(content, "屏蔽设置")
_add_mobile_block_buttons(content)
_add_mobile_section_title(content, "PK 模式")
_add_mobile_pk_radios(content)
_add_mobile_section_title(content, "显示设置")
_add_mobile_display_radios(content)
ui.open(_mobile_root)
func _add_mobile_section_title(parent: VBoxContainer, text: String) -> void:
var title := Label.new()
title.text = text
title.custom_minimum_size = Vector2(640, 28)
title.add_theme_font_size_override("font_size", 14)
title.add_theme_color_override("font_color", Color(0.95, 0.82, 0.54))
parent.add_child(title)
func _add_mobile_block_buttons(parent: VBoxContainer) -> void:
var grid := GridContainer.new()
grid.columns = 3
grid.custom_minimum_size = Vector2(640, 94)
grid.add_theme_constant_override("h_separation", 6)
grid.add_theme_constant_override("v_separation", 6)
parent.add_child(grid)
for name in BLOCK_BITS:
var button := Button.new()
button.text = String(LABELS.get(name, name))
button.toggle_mode = true
button.button_pressed = (_block_mode & int(BLOCK_BITS[name])) != 0
button.custom_minimum_size = Vector2(206, 42)
var bit := int(BLOCK_BITS[name])
button.pressed.connect(func(): _toggle_block(bit))
grid.add_child(button)
func _add_mobile_pk_radios(parent: VBoxContainer) -> void:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(640, 44)
row.add_theme_constant_override("separation", 6)
var buttons: Array[Button] = []
for name in ["pvp_peace", "pvp_revenge", "pvp_guild", "pvp_free"]:
var button := Button.new()
button.text = String(LABELS.get(name, name))
button.toggle_mode = true
button.custom_minimum_size = Vector2(145, 42)
button.button_pressed = false
var picked: String = name
button.pressed.connect(func():
for other in buttons.size():
buttons[other].set_pressed_no_signal(buttons[other] == button)
if client and client.has_method("say"):
client.say(0, "/pkmode %d" % PK_CMD[picked]))
buttons.append(button)
row.add_child(button)
var current: Variant = _cfg.get_value("gameopt", "pk_mode", -1)
if int(current) in PK_CMD.values():
for i in PK_CMD.keys().size():
if int(PK_CMD[PK_CMD.keys()[i]]) == int(current):
buttons[i].button_pressed = true
parent.add_child(row)
func _add_mobile_display_radios(parent: VBoxContainer) -> void:
for group in RADIO_GROUPS:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(640, 44)
row.add_theme_constant_override("separation", 6)
var key: String = String(DISPLAY_RADIOS[group[0]][0])
var caption := Label.new()
caption.text = String(LABELS.get(key, key))
caption.custom_minimum_size = Vector2(126, 40)
caption.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
caption.add_theme_font_size_override("font_size", 12)
row.add_child(caption)
var buttons: Array[Button] = []
var current := int(_display.get(key, 0))
for name in group:
var button := Button.new()
button.text = String(LABELS.get(name, name))
button.toggle_mode = true
button.custom_minimum_size = Vector2(112, 42)
button.button_pressed = int(DISPLAY_RADIOS[name][1]) == current
var selected: String = name
button.pressed.connect(func():
for other in buttons.size():
buttons[other].set_pressed_no_signal(buttons[other] == button)
var spec: Array = DISPLAY_RADIOS[selected]
_display[String(spec[0])] = int(spec[1])
_save()
display_option_changed.emit(String(spec[0]), int(spec[1])))
buttons.append(button)
row.add_child(button)
parent.add_child(row)
func _mobile_panel_style() -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = Color(0.04, 0.065, 0.1, 0.98)
style.border_color = Color(0.52, 0.68, 0.9, 0.86)
style.set_border_width_all(1)
style.set_corner_radius_all(8)
return style
func _node(nm: String) -> Control:
if _win.is_empty():
return null
var n = _win.get("nodes", {}).get(nm, null)
return n if n is Control else null
func _relabel() -> void:
for nm in LABELS:
var n := _node(nm)
if n and n.has_method("set_text"):
n.set_text(LABELS[nm])
func _wire() -> void:
# 屏蔽 toggle → /setblockmode
for nm in BLOCK_BITS:
var b := _node(nm)
if b is BaseButton:
b.toggle_mode = true
var bit: int = BLOCK_BITS[nm]
b.pressed.connect(func(): _toggle_block(bit))
# PK radio → /pkmode
_bind_radio(PK_CMD.keys(), func(nm):
if client and client.has_method("say"):
client.say(0, "/pkmode %d" % PK_CMD[nm])) # CHAT_TYPE_TALKING
# 显示开关 radio → 持久化
for group in RADIO_GROUPS:
_bind_radio(group, func(nm):
var spec: Array = DISPLAY_RADIOS[nm]
_display[spec[0]] = int(spec[1])
_save()
display_option_changed.emit(String(spec[0]), int(spec[1])))
var tb := _node("titlebar")
if tb:
for x in tb.find_children("*", "BaseButton", true, false):
x.pressed.connect(close)
func _bind_radio(names: Array, on_pick: Callable) -> void:
var boxes := {}
for nm: String in names:
boxes[nm] = _node(nm)
for nm: String in names:
var b = boxes[nm]
if not (b is BaseButton):
continue
b.toggle_mode = true
var picked: String = nm
b.pressed.connect(func():
for other: String in names:
if boxes[other] is BaseButton:
boxes[other].set_pressed_no_signal(other == picked)
on_pick.call(picked))
func _toggle_block(bit: int) -> void:
_block_mode ^= bit
if client and client.has_method("say"):
client.say(0, "/setblockmode %d" % _block_mode)
_cfg.set_value("gameopt", "block_mode", _block_mode)
var err := _cfg.save(config_path())
if err != OK:
push_warning("GameOptionUI: save failed (%s): %s" % [err, config_path()])
_sync_block()
# 开窗时把状态回填到控件
func _sync() -> void:
_sync_block()
for group in RADIO_GROUPS:
var key: String = DISPLAY_RADIOS[group[0]][0]
var cur := int(_display.get(key, 0))
for nm in group:
var b := _node(nm)
if b is BaseButton:
b.set_pressed_no_signal(int(DISPLAY_RADIOS[nm][1]) == cur)
func _sync_block() -> void:
for nm in BLOCK_BITS:
var b := _node(nm)
if b is BaseButton:
b.set_pressed_no_signal((_block_mode & int(BLOCK_BITS[nm])) != 0)
func _on_server_block_mode(mask: int) -> void:
_block_mode = maxi(0, mask)
_cfg.set_value("gameopt", "block_mode", _block_mode)
_cfg.save(config_path())
_sync_block()
func _save() -> void:
for k in _display:
_cfg.set_value("gameopt", k, int(_display[k]))
_cfg.set_value("gameopt", "block_mode", _block_mode)
var err := _cfg.save(config_path())
if err != OK:
push_warning("GameOptionUI: save failed (%s): %s" % [err, config_path()])