Implement 40250 classic client port

This commit is contained in:
shenlei
2026-09-01 18:49:28 +09:00
parent 2277b1dd60
commit 8f61990a83
481 changed files with 169826 additions and 293 deletions
+57 -5
View File
@@ -58,6 +58,10 @@ const AtlasUI = preload("res://ui/atlas_ui.gd")
const WorldTime = preload("res://world/world_time.gd")
const Weather = preload("res://fx/weather.gd")
# 龙魂是协议兼容层的一部分,但龙魂 40250 服务器没有这个功能。
# 保留 DragonSoulUI / ds_refine 代码供后续兼容,当前客户端不创建入口、不消费背包右键。
const ENABLE_DRAGON_SOUL := false
var client: Node
var world: Node
var player: Node3D
@@ -110,6 +114,8 @@ var _sun: DirectionalLight3D
var _env: Environment
var _env_from_msenv := false # true = 用 Metin2World 从 .msenv 建的 Sun/WorldEnv
var _pending_shoot_skill := 0
var _guild_invite_dialog: ConfirmationDialog
var _gift_dialog: AcceptDialog
func setup(m2client: Node, assets_root: String,
map_path: String = "OutdoorA1/metin2_map_a1") -> void:
@@ -144,6 +150,7 @@ func setup(m2client: Node, assets_root: String,
add_child(net_world)
net_world.world = world
net_world.setup(client, _mount)
net_world.set_local_node(player)
hud = Hud.new()
if _map_loaded():
@@ -221,6 +228,8 @@ func setup(m2client: Node, assets_root: String,
fx.spawn(name, host, true) # GC_SPECIFIC_EFFECT
elif special >= 0 and skill_fx:
skill_fx.spawn_special(special, host)) # GC_SPECIAL_EFFECT 内建 id
if client.has_signal("gift_available"):
client.gift_available.connect(_on_gift_available)
# 任务 / NPC(P7)—— 对话窗自动弹,J 键任务日志
quest_dialog = QuestDialog.new()
@@ -319,12 +328,17 @@ func setup(m2client: Node, assets_root: String,
add_child(guild_ui)
guild_ui.setup(client, ui, skill_table)
guild_ui.mark_image_provider = _guild_mark_upload_image
if client.has_signal("guild_invite_ask"):
client.guild_invite_ask.connect(_on_guild_invite)
refine_ui = RefineUI.new()
add_child(refine_ui)
refine_ui.setup(client, ui, proto)
dragon_soul_ui = DragonSoulUI.new()
add_child(dragon_soul_ui)
dragon_soul_ui.setup(client, ui, proto)
if ENABLE_DRAGON_SOUL:
dragon_soul_ui = DragonSoulUI.new()
add_child(dragon_soul_ui)
dragon_soul_ui.setup(client, ui, proto)
else:
dragon_soul_ui = null
await _yield()
if inventory and "context_consumer" in inventory:
inventory.context_consumer = _on_inv_context
@@ -393,6 +407,42 @@ func _guild_mark_upload_image() -> Image:
img.set_pixel(x, y, Color(0.8, 0.2, 0.2) if (x + y) % 2 == 0 else Color(0.9, 0.85, 0.3))
return img
func _on_guild_invite(guild_id: int, guild_name: String) -> void:
if _guild_invite_dialog and is_instance_valid(_guild_invite_dialog):
_guild_invite_dialog.queue_free()
_guild_invite_dialog = ConfirmationDialog.new()
_guild_invite_dialog.title = "公会邀请"
_guild_invite_dialog.dialog_text = "加入公会“%s”?" % guild_name
_guild_invite_dialog.ok_button_text = "加入"
_guild_invite_dialog.cancel_button_text = "拒绝"
ui.add_child(_guild_invite_dialog)
_guild_invite_dialog.confirmed.connect(func():
client.guild_answer_invite(guild_id, true)
_guild_invite_dialog.queue_free())
_guild_invite_dialog.canceled.connect(func():
client.guild_answer_invite(guild_id, false)
_guild_invite_dialog.queue_free())
_guild_invite_dialog.popup_centered()
func _on_gift_available() -> void:
# 40250's Gift_Show has no corresponding claim packet in the shipped
# PythonNetworkStream. Keep the server notification visible instead of
# silently consuming it; any reward data remains owned by the gift UI/backend.
if ui == null:
return
if _gift_dialog and is_instance_valid(_gift_dialog):
_gift_dialog.popup_centered()
return
_gift_dialog = AcceptDialog.new()
_gift_dialog.title = "礼物"
_gift_dialog.dialog_text = "有新的礼物可查看。"
_gift_dialog.ok_button_text = "知道了"
ui.add_child(_gift_dialog)
_gift_dialog.confirmed.connect(func():
if is_instance_valid(_gift_dialog):
_gift_dialog.queue_free())
_gift_dialog.popup_centered()
# .msa MotionEventData 分派(对齐 GameLib/RaceMotionData EMotionEventType):
# 1 EFFECT / 10 EFFECT_TO_TARGET → 挂特效;2 SCREEN_WAVING → 震屏;
# 3 SCREEN_FLASHING → 屏闪;5 SOUND / 有 sound 字段 → 3D 音;6 FLY / 9 WARP → 钩子。
@@ -470,7 +520,7 @@ func _on_inv_context(window: int, cell: int) -> bool:
if safebox_ui and safebox_ui.is_open():
safebox_ui.deposit(window, cell)
return true
if dragon_soul_ui and dragon_soul_ui.is_open():
if ENABLE_DRAGON_SOUL and dragon_soul_ui and dragon_soul_ui.is_open():
dragon_soul_ui.add_cell(window, cell)
return true
return false
@@ -494,7 +544,7 @@ func _unhandled_input(e: InputEvent) -> void:
friend_ui.toggle()
elif e.keycode == KEY_G and guild_ui:
guild_ui.toggle()
elif e.keycode == KEY_L and dragon_soul_ui:
elif e.keycode == KEY_L and ENABLE_DRAGON_SOUL and dragon_soul_ui:
dragon_soul_ui.toggle()
elif e.keycode == KEY_M and atlas_ui:
atlas_ui.toggle()
@@ -572,6 +622,8 @@ func set_player_model(node: Node3D) -> void:
player = node
cam.target = node
pc.player = node
if net_world and net_world.has_method("set_local_node"):
net_world.set_local_node(node)
old.queue_free()
# --- internals -------------------------------------------------------------