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

222 lines
8.3 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
# 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] 快照
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:
return not _win.is_empty() and is_instance_valid(_win.get("root"))
# 渲染层开局回填用: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 is_open():
ui.close(_win["root"])
_win = {}
func open() -> void:
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 _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()])