Files
mtgodot-poc/project/client_phase_integration_system.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

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")