- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
193 lines
6.1 KiB
GDScript
193 lines
6.1 KiB
GDScript
# client_phase_integration_system.gd —— 40250 客户端全状态机与全流程一体化集成系统 1:1
|
|
# 严格对照:
|
|
# Client/Eternexus/root/networkmodule.py, interfacemodule.py, game.py
|
|
# ClientVS22/source/UserInterface/PythonNetworkStream.h (PHASE_*)
|
|
extends RefCounted
|
|
|
|
const IntroLoginPhase = preload("res://intro_login_phase.gd")
|
|
const IntroEmpirePhase = preload("res://intro_empire_phase.gd")
|
|
const IntroSelectPhase = preload("res://intro_select_phase.gd")
|
|
const IntroCreatePhase = preload("res://intro_create_phase.gd")
|
|
const IntroLoadingPhase = preload("res://intro_loading_phase.gd")
|
|
|
|
const MessengerSystem = preload("res://messenger_system.gd")
|
|
const WhisperChatSystem = preload("res://whisper_chat_system.gd")
|
|
const AtlasNavigationSystem = preload("res://atlas_navigation_system.gd")
|
|
const EquipmentInspectSystem = preload("res://equipment_inspect_system.gd")
|
|
const BgmJukeboxSystem = preload("res://bgm_jukebox_system.gd")
|
|
|
|
# 官方 Phase 枚举 (PythonNetworkStream.h)
|
|
enum ClientPhase {
|
|
PHASE_OFFLINE,
|
|
PHASE_LOGIN,
|
|
PHASE_EMPIRE,
|
|
PHASE_SELECT,
|
|
PHASE_CREATE,
|
|
PHASE_LOADING,
|
|
PHASE_GAME
|
|
}
|
|
|
|
var current_phase: int = ClientPhase.PHASE_LOGIN
|
|
|
|
# 阶段子模块实例
|
|
var login_phase: RefCounted = null
|
|
var empire_phase: RefCounted = null
|
|
var select_phase: RefCounted = null
|
|
var create_phase: RefCounted = null
|
|
var loading_phase: RefCounted = null
|
|
|
|
# 游戏内核心交互模块实例
|
|
var messenger: RefCounted = null
|
|
var whisper: RefCounted = null
|
|
var atlas: RefCounted = null
|
|
var equip_inspect: RefCounted = null
|
|
var jukebox: RefCounted = null
|
|
|
|
# 活跃角色数据
|
|
var active_character: Dictionary = {}
|
|
var active_account: String = ""
|
|
|
|
# 热键弹窗焦点栈 (用于 Escape 键自顶向下关闭)
|
|
var dialog_focus_stack: Array = []
|
|
|
|
func _init() -> void:
|
|
current_phase = ClientPhase.PHASE_LOGIN
|
|
active_character = {}
|
|
active_account = ""
|
|
dialog_focus_stack.clear()
|
|
|
|
# 初始化各阶段系统
|
|
login_phase = IntroLoginPhase.new()
|
|
empire_phase = IntroEmpirePhase.new()
|
|
select_phase = IntroSelectPhase.new()
|
|
create_phase = IntroCreatePhase.new()
|
|
loading_phase = IntroLoadingPhase.new()
|
|
|
|
# 初始化游戏内交互系统
|
|
messenger = MessengerSystem.new()
|
|
whisper = WhisperChatSystem.new()
|
|
atlas = AtlasNavigationSystem.new()
|
|
equip_inspect = EquipmentInspectSystem.new()
|
|
jukebox = BgmJukeboxSystem.new()
|
|
|
|
# 1. 登录跳转流 (Login -> Select / Empire)
|
|
func process_login(account: String, password_input: String) -> Dictionary:
|
|
var r = login_phase.attempt_login(account, password_input)
|
|
if not r["success"]:
|
|
return r
|
|
|
|
active_account = account
|
|
if r.get("has_characters", false):
|
|
current_phase = ClientPhase.PHASE_SELECT
|
|
r["next_phase"] = ClientPhase.PHASE_SELECT
|
|
else:
|
|
current_phase = ClientPhase.PHASE_EMPIRE
|
|
r["next_phase"] = ClientPhase.PHASE_EMPIRE
|
|
|
|
return r
|
|
|
|
# 2. 阵营抉择流 (Empire -> Create)
|
|
func process_empire_select(empire_id: int) -> Dictionary:
|
|
if current_phase != ClientPhase.PHASE_EMPIRE:
|
|
return { "success": false, "error": "NOT_IN_EMPIRE_PHASE" }
|
|
|
|
empire_phase.select_empire(empire_id)
|
|
var conf = empire_phase.confirm_empire_selection()
|
|
current_phase = ClientPhase.PHASE_CREATE
|
|
conf["next_phase"] = ClientPhase.PHASE_CREATE
|
|
return conf
|
|
|
|
# 3. 角色塑形流 (Create -> Select)
|
|
func process_character_create(name: String, empire_id: int = 1) -> Dictionary:
|
|
if current_phase != ClientPhase.PHASE_CREATE:
|
|
return { "success": false, "error": "NOT_IN_CREATE_PHASE" }
|
|
|
|
var r = create_phase.create_character(name, empire_id)
|
|
if not r["success"]:
|
|
return r
|
|
|
|
# 存入角色选择大厅首个空槽位
|
|
select_phase.set_character_slot(0, r["character"])
|
|
current_phase = ClientPhase.PHASE_SELECT
|
|
r["next_phase"] = ClientPhase.PHASE_SELECT
|
|
return r
|
|
|
|
# 4. 选择角色出战流 (Select -> Loading -> Game)
|
|
func process_character_start(slot_idx: int) -> Dictionary:
|
|
if current_phase != ClientPhase.PHASE_SELECT:
|
|
return { "success": false, "error": "NOT_IN_SELECT_PHASE" }
|
|
|
|
select_phase.select_slot(slot_idx)
|
|
var r = select_phase.start_game()
|
|
if not r["success"]:
|
|
return r
|
|
|
|
active_character = r["character"]
|
|
|
|
# 启动过场加载
|
|
loading_phase.start_loading()
|
|
current_phase = ClientPhase.PHASE_LOADING
|
|
|
|
# 完成 4 阶段加载
|
|
for i in range(5):
|
|
loading_phase.advance_loading_step()
|
|
|
|
if loading_phase.is_completed():
|
|
current_phase = ClientPhase.PHASE_GAME
|
|
jukebox.on_enter_map("metin2_map_a1")
|
|
messenger.main_player_name = active_character.get("name", "Hero")
|
|
whisper.main_player_name = active_character.get("name", "Hero")
|
|
r["next_phase"] = ClientPhase.PHASE_GAME
|
|
|
|
return r
|
|
|
|
# 5. 全局热键路由与窗口调度 (1:1 interfacemodule.py)
|
|
func handle_input_hotkey(key_name: String) -> Dictionary:
|
|
if current_phase != ClientPhase.PHASE_GAME:
|
|
return { "handled": false, "phase": current_phase }
|
|
|
|
match key_name:
|
|
"Alt+M": # 好友面板
|
|
var is_showing: bool = (dialog_focus_stack.has("MESSENGER"))
|
|
if is_showing:
|
|
dialog_focus_stack.erase("MESSENGER")
|
|
return { "handled": true, "action": "CLOSE_MESSENGER" }
|
|
else:
|
|
dialog_focus_stack.append("MESSENGER")
|
|
return { "handled": true, "action": "OPEN_MESSENGER" }
|
|
|
|
"M": # Atlas 全景大地图
|
|
var visible = atlas.toggle_atlas()
|
|
if visible:
|
|
dialog_focus_stack.append("ATLAS")
|
|
return { "handled": true, "action": "OPEN_ATLAS" }
|
|
else:
|
|
dialog_focus_stack.erase("ATLAS")
|
|
return { "handled": true, "action": "CLOSE_ATLAS" }
|
|
|
|
"Escape": # 自顶向下关闭弹窗或呼出系统菜单
|
|
if not dialog_focus_stack.is_empty():
|
|
var top_dialog = dialog_focus_stack.pop_back()
|
|
if top_dialog == "ATLAS":
|
|
atlas.hide_atlas()
|
|
return { "handled": true, "action": "CLOSED_TOP_DIALOG", "dialog": top_dialog }
|
|
else:
|
|
return { "handled": true, "action": "OPEN_SYSTEM_MENU" }
|
|
|
|
return { "handled": false }
|
|
|
|
# 6. 登出游戏 (Game -> Select / Login)
|
|
func logout_to_select() -> void:
|
|
current_phase = ClientPhase.PHASE_SELECT
|
|
active_character = {}
|
|
dialog_focus_stack.clear()
|
|
atlas.hide_atlas()
|
|
jukebox.on_enter_map("intro_select")
|
|
|
|
func logout_to_login() -> void:
|
|
current_phase = ClientPhase.PHASE_LOGIN
|
|
active_character = {}
|
|
active_account = ""
|
|
dialog_focus_stack.clear()
|
|
jukebox.on_enter_map("intro_login")
|