fix: 装备属性面板避让逻辑 + 多项功能更新

- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
This commit is contained in:
shen
2026-09-21 16:38:59 -07:00
parent 1522a8a1a1
commit c93894313a
5498 changed files with 4558004 additions and 1442 deletions
+54 -31
View File
@@ -7,12 +7,13 @@
# 脚本格式(`CPythonEventManager`):纯文本 + `[命令 arg(值)]` token 交错。
# [ENTER] 换行
# [CLEAR] 清空
# [NEXT] / [DONE] 「继续 / 关闭」按钮 -> M2Client.script_answer(255)
# [NEXT] 「继续」按钮 -> M2Client.script_answer(254)
# [DONE] 「关闭」按钮 -> CloseSelf only; DoneEvent fires while parsing
# [QUESTION arg("是") arg("否")] 选项按钮 -> script_answer(0..N-1)
# [LETTER value("...")] 追加文本(旧写法)
# [INPUT] 显示文本输入框,提交 -> M2Client.quest_input()
# [SELECT_ITEM] 要求从背包选一颗魔石 -> 发 select_item_requested 信号(打开 SelectItemUI)
# [CONFIRM_WAIT] 等:本版忽略
# [CONFIRM_WAIT] 等待:取消按钮按 BUTTON_TYPE_CANCEL 发送 script_answer(254)
#
# quest_confirm_ask(GC_QUEST_CONFIRM)也在这里弹 是/否 -> quest_confirm(yes, pid)。
extends Node
@@ -103,20 +104,19 @@ func begin_script(src: String) -> void:
_active_cursor = 0
_active_script = true
# RegisterEventSetFromString replaces a leading RUN_CINEMA event set with
# the referenced .msc file. Keep the signal for the presentation layer and
# load the event text when the loose asset is available.
# the referenced .msc file. A missing file makes registration fail in
# 40250; RecvScriptPacket then does not call OnScriptEventStart and must not
# leave an empty, locked quest window behind.
if not _active_commands.is_empty() and String(_active_commands[0].get("literal", "")) == "" \
and String(_active_commands[0].get("name", "")).to_upper().trim_prefix("/") == "RUN_CINEMA":
var cinema := _arg_s(String(_active_commands[0].get("tok", "")), "value")
if cinema != "":
cinema_requested.emit(cinema)
var cinema_text := _read_script_file(cinema)
if cinema_text != "":
_active_commands = _tokenize(cinema_text)
else:
# No local .msc: keep the empty event set alive so the caller can
# show its own cinema player, but do not re-process RUN_CINEMA.
_active_commands = []
var cinema_text := _read_script_file(cinema) if cinema != "" else ""
if cinema_text == "":
_finish_runtime()
_root.visible = false
return
cinema_requested.emit(cinema)
_active_commands = _tokenize(cinema_text)
_present_event_set(_active_set)
_root.visible = true
opened.emit()
@@ -317,6 +317,35 @@ func _on_confirm(msg: String, _timeout: int, request_pid: int) -> void:
_root.visible = true
opened.emit()
func _send_script_answer(answer: int) -> void:
if client and client.has_method("script_answer"):
client.script_answer(answer)
func _escape_close() -> void:
# uiquest.py::OnPressEscapeKey dispatches from the currently visible
# button type. CANCEL and NEXT answer 254, DONE only closes, and a visible
# QUESTION answers with the last option before OnCancel/CloseSelf. The
# native quest_cancel() fallback is the stock 255 sentinel for a dialog that
# has no answer button at all.
var button_type := ""
var choices: Array = []
if _active_set != null:
button_type = String(_active_set.next_button_type)
choices = _active_set.choices
if _confirm_wait or button_type == "CANCEL":
_send_script_answer(254)
elif button_type == "DONE":
pass
elif button_type == "NEXT":
_send_script_answer(254)
elif not choices.is_empty():
_send_script_answer(choices.size() - 1)
elif client and client.has_method("quest_cancel"):
client.quest_cancel()
else:
_send_script_answer(255)
close()
func close() -> void:
_finish_runtime()
if _root:
@@ -329,17 +358,7 @@ func _unhandled_input(event: InputEvent) -> void:
return
var key := event as InputEventKey
if key.pressed and not key.echo and key.keycode == KEY_ESCAPE:
if _active_script:
skip()
var vp := get_viewport()
if vp:
vp.set_input_as_handled()
return
if client and client.has_method("quest_cancel"):
client.quest_cancel()
elif client and client.has_method("script_answer"):
client.script_answer(254)
close()
_escape_close()
var vp := get_viewport()
if vp:
vp.set_input_as_handled()
@@ -663,8 +682,9 @@ func _process_event_set(es: EventSet, cmd: Dictionary) -> void:
es.next_button_type = "NEXT"
es.iAdjustLine += 2
EVT_DONE:
# MakeNextButton(BUTTON_TYPE_DONE); the reference invokes DoneEvent
# from the button callback, not while the token is being parsed.
# PythonEventManager::ProcessEventSet invokes DoneEvent immediately,
# then MakeNextButton(BUTTON_TYPE_DONE) creates a close-only button.
done_event.emit()
es.has_next = true
es.next_button_type = "DONE"
es.iAdjustLine += 2
@@ -1133,10 +1153,11 @@ func _fill_buttons(choices: Array, has_next: bool, has_input := false, confirm_w
if confirm_wait:
var cancel := _mkbtn("取消")
cancel.pressed.connect(func():
if client and client.has_method("quest_cancel"):
client.quest_cancel()
elif client and client.has_method("script_answer"):
# MakeNextButton(BUTTON_TYPE_CANCEL): answer 254, then OnCancel.
if client and client.has_method("script_answer"):
client.script_answer(254)
elif client and client.has_method("quest_cancel"):
client.quest_cancel()
close())
return
if choices.is_empty():
@@ -1146,7 +1167,10 @@ func _fill_buttons(choices: Array, has_next: bool, has_input := false, confirm_w
b.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
b.pressed.connect(func():
if is_done:
done_event.emit()
# ClientVS22 uiquest.py::MakeNextButton(BUTTON_TYPE_DONE)
# calls CloseSelf() and does not send SCRIPT_ANSWER(254).
close()
return
if client and client.has_method("script_answer"):
client.script_answer(254)
close())
@@ -1288,4 +1312,3 @@ func _mkbtn(text: String) -> Button:
_btnrow.add_child(b)
return b