Complete remaining client gap features

This commit is contained in:
shenlei
2026-09-04 13:25:05 +09:00
parent 13ccb8d02d
commit 9a4a044da5
47 changed files with 6667 additions and 140 deletions
+121 -1
View File
@@ -43,6 +43,7 @@ const SystemMenuUI = preload("res://ui/system_menu_ui.gd")
const Quickbar = preload("res://ui/quickbar.gd")
const QuestDialog = preload("res://ui/quest_dialog.gd")
const QuestLog = preload("res://ui/quest_log.gd")
const DungeonResultUI = preload("res://ui/dungeon_result_ui.gd")
const SelectItemUI = preload("res://ui/select_item_ui.gd")
const PartyUI = preload("res://ui/party_ui.gd")
const FriendUI = preload("res://ui/friend_ui.gd")
@@ -95,6 +96,7 @@ var system_menu_ui: Node # SystemMenuUI
var quickbar: Node # Quickbar
var quest_dialog: Node # QuestDialog
var quest_log: Node # QuestLog
var dungeon_result_ui: Node # DungeonResultUI
var select_item_ui: Node # SelectItemUI
var party_ui: Node # PartyUI
var friend_ui: Node # FriendUI
@@ -171,6 +173,10 @@ func setup(m2client: Node, assets_root: String,
add_child(net_world)
net_world.world = world
net_world.setup(client, _mount)
# §8.8 屏幕空间实体血条:明确绑定轨道相机,避免依赖 viewport 当前相机,
# 也让地图切换 / 多视口时投影来源稳定。
if net_world.has_method("set_camera"):
net_world.set_camera(cam)
net_world.set_local_node(player)
net_world.entity_added.connect(func(node: Node3D, _vid: int):
if node not in pc.pickables:
@@ -195,6 +201,9 @@ func setup(m2client: Node, assets_root: String,
# §8.6 targetBoard.GetTargetVID() -> net_world:选中目标始终强显名字。
if net_play.has_signal("target_changed") and net_world and net_world.has_method("set_target_vid"):
net_play.target_changed.connect(func(vid: int): net_world.set_target_vid(int(vid)))
# §8.8 game.py OnRender chr.Pick() -> ShowCharacterTextTail:悬停角色也强显名字(pick_show.gd)。
if pc.has_signal("hover_entity_changed") and net_world and net_world.has_method("set_hover_vid"):
pc.hover_entity_changed.connect(func(vid: int): net_world.set_hover_vid(int(vid)))
if _assets != "":
net_play.set_asset_root(_assets) # §3.5:加载 playersettingmodule.py 连击段表
@@ -225,6 +234,10 @@ func setup(m2client: Node, assets_root: String,
chat.setup(client, ui, _assets)
if net_play and net_play.has_signal("fishing_feedback") and chat.has_method("on_fishing_feedback"):
net_play.fishing_feedback.connect(chat.on_fishing_feedback)
# §8.8 / §5.4 game.py SetPCTargetBoard :804 —— 按住 LCONTROL 点同阵营他人 PC 时
# self.interface.OpenWhisperDialog(name)。chat.start_whisper 打开独立私聊窗口。
if net_play and net_play.has_signal("whisper_requested") and chat.has_method("start_whisper"):
net_play.whisper_requested.connect(chat.start_whisper)
if net_world and net_world.has_signal("main_bubble"):
net_world.main_bubble.connect(_player_bubble)
@@ -295,6 +308,15 @@ func setup(m2client: Node, assets_root: String,
quest_dialog = QuestDialog.new()
add_child(quest_dialog)
quest_dialog.setup(client, ui, proto, assets_root)
if quest_dialog.has_signal("camera_event_requested_full"):
quest_dialog.camera_event_requested_full.connect(_on_quest_camera_event)
if quest_dialog.has_signal("fade_event_requested_full"):
quest_dialog.fade_event_requested_full.connect(_on_quest_fade_event)
if quest_dialog.has_signal("dungeon_result_requested"):
quest_dialog.dungeon_result_requested.connect(_on_dungeon_result)
dungeon_result_ui = DungeonResultUI.new()
add_child(dungeon_result_ui)
dungeon_result_ui.setup(ui)
quest_log = QuestLog.new()
add_child(quest_log)
quest_log.setup(client, ui)
@@ -330,6 +352,8 @@ func setup(m2client: Node, assets_root: String,
await _yield()
if chat and chat.has_method("set_proto"):
chat.set_proto(proto)
if quest_dialog and quest_dialog.has_method("set_proto"):
quest_dialog.set_proto(proto)
var mp := assets_root.path_join("locale/locale/en/mob_proto")
if FileAccess.file_exists(mp):
proto.call("load_mob_proto", mp)
@@ -367,7 +391,16 @@ func setup(m2client: Node, assets_root: String,
# 地面掉落物
ground_items = GroundItems.new()
add_child(ground_items)
ground_items.setup(client, _mount, func() -> Node: return player, proto, item_list)
# SetItemTextTailOwner :722strOwnership = ApplicationStringTable(IDS_POSSESSIVE_MORPHENE)
# 为空则字面 "'s"。poc 用 EterLocale 的 POSSESSIVE_MORPHENE 键作对应物(缺则回 "")。
var _possessive := ""
if assets_root != "":
var _poss_loc: RefCounted = load("res://locale.gd").new()
_poss_loc.setup(assets_root, "en")
if _poss_loc.has("POSSESSIVE_MORPHENE"):
_possessive = _poss_loc.t("POSSESSIVE_MORPHENE")
ground_items.setup(client, _mount, func() -> Node: return player, proto, item_list,
func() -> Camera3D: return cam, _possessive)
pc.set_input_surfaces(cursor_manager, ui, ground_items,
Callable(net_play, "cancel_fishing"), Callable(net_play, "on_ground_click"))
view_equipment_ui = ViewEquipmentUI.new()
@@ -489,6 +522,15 @@ func setup(m2client: Node, assets_root: String,
game_option_ui.display_option_changed.connect(_on_display_option)
if net_world and net_world.has_method("set_always_show_name") and game_option_ui.has_method("display_value"):
net_world.set_always_show_name(game_option_ui.display_value("always_show_name") == 1)
# §8.8 / §5.4「伤害数字」radio -> net_worldCPythonSystem::SetShowDamageFlag)。
if net_world and net_world.has_method("set_show_damage") and game_option_ui.has_method("display_value"):
net_world.set_show_damage(game_option_ui.display_value("show_damage") == 1)
# §8.4「名字色」radio -> net_worlduigameoption.__SetNameColorMode → chrmgr.SetEmpireNameMode)。
if net_world and net_world.has_method("set_empire_name_mode") and game_option_ui.has_method("display_value"):
net_world.set_empire_name_mode(game_option_ui.display_value("name_color") == 1)
# §8.4「看他国玩家目标框」radio -> net_playuitarget.TargetBoard.Open 前置过滤)。
if net_play and net_play.has_method("set_view_other_empire_target") and game_option_ui.has_method("display_value"):
net_play.set_view_other_empire_target(game_option_ui.display_value("target_board") == 1)
system_menu_ui = SystemMenuUI.new()
add_child(system_menu_ui)
system_menu_ui.setup(ui, client, assets_root, system_option_ui, game_option_ui)
@@ -614,6 +656,69 @@ func _queue_shot(skill: int) -> void:
_pending_shots.pop_front()
var _flash_rect: ColorRect
var _quest_fade_rect: ColorRect
var _quest_fade_tween: Tween
func _on_quest_camera_event(kind: String, setting: Dictionary, blendtime: float) -> void:
if cam == null or not is_instance_valid(cam):
return
match kind:
"SET_CAMERA":
if cam.has_method("set_event_camera"):
cam.set_event_camera(setting)
"BLEND_CAMERA":
if cam.has_method("blend_event_camera"):
cam.blend_event_camera(setting, blendtime)
"RESTORE_CAMERA":
if cam.has_method("set_default_camera"):
cam.set_default_camera()
func _on_quest_fade_event(kind: String, speed: float) -> void:
if ui == null:
if quest_dialog and quest_dialog.has_method("end_event_process"):
quest_dialog.end_event_process()
return
if _quest_fade_rect == null or not is_instance_valid(_quest_fade_rect):
_quest_fade_rect = ColorRect.new()
_quest_fade_rect.name = "QuestFade"
_quest_fade_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
_quest_fade_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
_quest_fade_rect.z_index = 100
ui.add_child(_quest_fade_rect)
var white := kind.begins_with("WHITE_")
_quest_fade_rect.color = Color(1, 1, 1, _quest_fade_rect.color.a) if white \
else Color(0, 0, 0, _quest_fade_rect.color.a)
var fade_in := kind.ends_with("_IN")
var target_alpha := 0.0 if fade_in else 1.0
_quest_fade_rect.visible = target_alpha > 0.0 or _quest_fade_rect.color.a > 0.001
if _quest_fade_tween and _quest_fade_tween.is_valid():
_quest_fade_tween.kill()
# uiquest.py advances alpha by speed once per frame. At 60 FPS this is
# equivalent to 1 / (speed * 60) seconds; malformed zero speeds use the
# reference default to avoid permanently blocking the event set.
var frame_speed := maxf(absf(speed), 0.035)
var duration := absf(target_alpha - _quest_fade_rect.color.a) / (frame_speed * 60.0)
_quest_fade_tween = create_tween()
_quest_fade_tween.tween_property(_quest_fade_rect, "color:a", target_alpha, duration)
_quest_fade_tween.tween_callback(func():
_quest_fade_rect.visible = target_alpha > 0.0
if quest_dialog and quest_dialog.has_method("end_event_process"):
quest_dialog.end_event_process())
func _on_dungeon_result(result: Dictionary) -> void:
if dungeon_result_ui and dungeon_result_ui.has_method("show_result"):
dungeon_result_ui.show_result(result)
func _clear_quest_presentation() -> void:
if _quest_fade_tween and _quest_fade_tween.is_valid():
_quest_fade_tween.kill()
_quest_fade_tween = null
if _quest_fade_rect and is_instance_valid(_quest_fade_rect):
_quest_fade_rect.color.a = 0.0
_quest_fade_rect.visible = false
if cam and cam.has_method("set_default_camera"):
cam.set_default_camera()
func _screen_flash(secs: float) -> void:
if hud == null:
return
@@ -661,6 +766,9 @@ func _on_world_reset() -> void:
widget.call("_close")
if atlas_ui and atlas_ui.has_method("close"):
atlas_ui.close()
if dungeon_result_ui and dungeon_result_ui.has_method("close"):
dungeon_result_ui.close()
_clear_quest_presentation()
if ui and ui.has_method("close_top"):
while ui.close_top():
pass
@@ -731,6 +839,12 @@ func _unhandled_input(e: InputEvent) -> void:
if key == KEY_SHIFT:
_quick_page_mode = e.pressed
return
# game.py SetPCTargetBoard :807 —— app.IsPressed(app.DIK_LCONTROL)。参考端在点 PC 的
# 那一刻轮询 LCONTROL 按住态;poc 把按下 / 松开喂给 net_play,点 PC 时再查(seam ⑫)。
if key == KEY_CTRL:
if net_play and net_play.has_method("set_lcontrol_down"):
net_play.set_lcontrol_down(e.pressed)
return
if not e.pressed:
match key:
KEY_SPACE:
@@ -855,6 +969,12 @@ func _set_names(visible: bool) -> void:
func _on_display_option(key: String, value: int) -> void:
if key == "always_show_name" and net_world and net_world.has_method("set_always_show_name"):
net_world.set_always_show_name(value == 1)
elif key == "show_damage" and net_world and net_world.has_method("set_show_damage"):
net_world.set_show_damage(value == 1)
elif key == "name_color" and net_world and net_world.has_method("set_empire_name_mode"):
net_world.set_empire_name_mode(value == 1)
elif key == "target_board" and net_play and net_play.has_method("set_view_other_empire_target"):
net_play.set_view_other_empire_target(value == 1)
func _send_command(text: String) -> void:
if client and client.has_method("say"):