完善客户端功能并同步差距文档

This commit is contained in:
shenlei
2026-09-03 18:40:01 +09:00
parent f93a172296
commit 13ccb8d02d
135 changed files with 21881 additions and 1259 deletions
+157
View File
@@ -0,0 +1,157 @@
# MouseController —— ClientVS22 mousemodule.py 的全局物品拖放层。
#
# 目标控件通过 register_target() 注册;物品源调用 attach_item() 后,控制器
# 接管鼠标释放,命中目标时调用回调,释放到窗口外 / 右键 / ESC / 失焦则取消。
# 光标状态由 CursorManager 管理,跟随图标不参与世界点击。
extends Node
const CursorManager = preload("res://ui/cursor_manager.gd")
var cursor: Node
var audio: Node
var _overlay: Control
var _icon: TextureRect
var _label: Label
var _attached: Dictionary = {}
var _targets: Array[Dictionary] = []
func setup(parent: Node, cursor_manager: Node = null, audio_node: Node = null) -> void:
cursor = cursor_manager
audio = audio_node
_overlay = Control.new()
_overlay.name = "MouseItemOverlay"
_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
_overlay.z_index = 1000
_overlay.visible = false
_overlay.size = Vector2(72, 72)
parent.add_child(_overlay)
_icon = TextureRect.new()
_icon.position = Vector2(0, 0)
_icon.size = Vector2(48, 48)
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
_overlay.add_child(_icon)
_label = Label.new()
_label.position = Vector2(2, 48)
_label.size = Vector2(68, 20)
_label.add_theme_font_size_override("font_size", 10)
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
_overlay.add_child(_label)
set_process_input(true)
func attach_item(window: int, cell: int, vnum: int, count: int,
texture: Texture2D = null, source := "inventory", metadata: Dictionary = {}) -> bool:
if vnum <= 0 or window < 0 or cell < 0:
return false
_attached = {"window": window, "cell": cell, "vnum": vnum,
"count": maxi(1, count), "source": source}
# Keep the instance fields that the reference mouse module exposes to the
# receiving window (anti flags, sockets, attributes, ...), while retaining
# the canonical source coordinates supplied by the caller.
for key in metadata:
if key not in ["window", "cell", "vnum", "count", "source"]:
_attached[key] = metadata[key]
if _icon:
_icon.texture = texture
_icon.visible = texture != null
if _label:
_label.text = "#%d%s" % [vnum, (" ×%d" % count) if count > 1 else ""]
_label.visible = true
if _overlay:
_overlay.visible = true
if cursor and cursor.has_method("set_cursor"):
cursor.set_cursor(CursorManager.ITEM)
_play_ui("pick.wav")
return true
func attach_money(amount: int, source := "inventory") -> bool:
if amount <= 0:
return false
_attached = {"window": -1, "cell": -1, "vnum": -1, "count": amount,
"source": source, "money": true}
if _icon:
_icon.texture = null
_icon.visible = false
if _label:
_label.text = "金币 ×%d" % amount
_label.visible = true
if _overlay:
_overlay.visible = true
if cursor and cursor.has_method("set_cursor"):
cursor.set_cursor(CursorManager.ITEM)
_play_ui("pick.wav")
return true
func cancel() -> void:
_attached.clear()
if _overlay:
_overlay.visible = false
if cursor and cursor.has_method("reset"):
cursor.reset()
func is_attached() -> bool:
return not _attached.is_empty()
func attached() -> Dictionary:
return _attached.duplicate(true)
func register_target(control: Control, callback: Callable, owner: Node = null) -> void:
if control == null or not callback.is_valid():
return
_targets.append({"control": control, "callback": callback, "owner": owner})
func unregister_owner(owner: Node) -> void:
for i in range(_targets.size() - 1, -1, -1):
if _targets[i].get("owner", null) == owner:
_targets.remove_at(i)
func _process(_dt: float) -> void:
if _overlay and _overlay.visible:
_overlay.global_position = get_viewport().get_mouse_position() + Vector2(12, 12)
# 清理已被窗口销毁的目标,防止拖放回调落到旧 UI。
for i in range(_targets.size() - 1, -1, -1):
var c: Control = _targets[i].get("control", null)
if c == null or not is_instance_valid(c):
_targets.remove_at(i)
func _input(event: InputEvent) -> void:
if not is_attached():
return
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
cancel()
get_viewport().set_input_as_handled()
return
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT:
cancel()
get_viewport().set_input_as_handled()
return
if event is InputEventMouseButton and not event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
_drop_at(event.position)
get_viewport().set_input_as_handled()
func _drop_at(position: Vector2) -> void:
var payload := attached()
for i in range(_targets.size() - 1, -1, -1):
var c: Control = _targets[i].get("control", null)
if c == null or not is_instance_valid(c):
_targets.remove_at(i)
continue
if not c.visible or not c.get_global_rect().has_point(position):
continue
var cb: Callable = _targets[i].get("callback", Callable())
if cb.is_valid() and bool(cb.call(payload)):
_play_ui("drop.wav")
cancel()
return
# 与参考端释放到无效区域的取消语义一致。
_play_ui("loginfail.wav")
cancel()
func _play_ui(name: String) -> void:
if audio and audio.has_method("play_ui"):
audio.play_ui(name)
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_WINDOW_FOCUS_OUT:
cancel()