完善客户端功能并同步差距文档
This commit is contained in:
@@ -19,11 +19,16 @@ var anim: Node # Metin2AnimPlayer
|
||||
var motion_dir := ""
|
||||
var action_dir := ""
|
||||
var _state := ""
|
||||
var _assets_root := ""
|
||||
var _audio: Node
|
||||
var _sound_instances: Array = []
|
||||
var _sound_frame := -1
|
||||
var _forward := ["weapon_gr2", "shield_gr2", "gr2_path", "hair_gr2", "hair_skin", "specular_power"]
|
||||
|
||||
# pump 可空:每个重活(gr2 解析 / LOD 扫描 / hair 折叠 / .msa 解析)之间调一下
|
||||
# 它(= M2Client.net_poll),免得整段阻塞几秒漏 PONG 被服务器踢。
|
||||
func build(assets_root: String, race: int, pump := Callable()) -> bool:
|
||||
_assets_root = assets_root
|
||||
if not ClassDB.class_exists("Metin2Model"):
|
||||
return false
|
||||
var cls: String = CLASS_OF[race & 3]
|
||||
@@ -64,6 +69,10 @@ func build(assets_root: String, race: int, pump := Callable()) -> bool:
|
||||
CharShadow.attach(self, 1.3) # 脚下接触阴影 + 强制 cast_shadow
|
||||
return true
|
||||
|
||||
func set_audio(audio_node: Node) -> void:
|
||||
_audio = audio_node
|
||||
_refresh_sound_script(String(anim.get("anim_path")) if anim else "")
|
||||
|
||||
func set_anim_state(s: String) -> void:
|
||||
if s == _state or anim == null or motion_dir == "":
|
||||
return
|
||||
@@ -74,6 +83,7 @@ func set_anim_state(s: String) -> void:
|
||||
if FileAccess.file_exists(msa):
|
||||
anim.set("loop", s in ["wait", "walk", "run"])
|
||||
anim.set("anim_path", msa)
|
||||
_refresh_sound_script(msa)
|
||||
|
||||
# Play one of the 40250 CRaceMotionData one-shot motions. The protocol sends
|
||||
# the numeric motion id; paired emotions use the other entity's race to select
|
||||
@@ -148,13 +158,41 @@ func set_motion_id(motion: int, target_race: int = -1) -> bool:
|
||||
if String(anim.get("anim_path")) == msa:
|
||||
anim.set("anim_path", "")
|
||||
anim.set("anim_path", msa)
|
||||
_refresh_sound_script(msa)
|
||||
return true
|
||||
|
||||
func _on_playback_finished() -> void:
|
||||
if _state == "__motion":
|
||||
# CLIENT-GAP §3.7: the hit reaction ("damage") is a one-shot knockback clip.
|
||||
# When it ends the actor leaves the pushed state (CActorInstance::IsPushing),
|
||||
# so fall back to idle — net_play polls is_in_hit_reaction() to drop its gate.
|
||||
if _state == "__motion" or _state == "damage":
|
||||
_state = ""
|
||||
set_anim_state("wait")
|
||||
|
||||
# True while the one-shot hit/knockback clip is still playing.
|
||||
func is_in_hit_reaction() -> bool:
|
||||
return _state == "damage"
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _audio == null or anim == null or _sound_instances.is_empty() or not anim.has_method("get_time") \
|
||||
or not bool(anim.get("playing")):
|
||||
return
|
||||
var current_time: float = anim.call("get_time")
|
||||
var frame := int(floor(current_time * 60.0))
|
||||
if frame == _sound_frame:
|
||||
return
|
||||
_sound_frame = frame
|
||||
_audio.update_sound_instances_3d(global_position.x, global_position.y, global_position.z,
|
||||
frame, _sound_instances, true)
|
||||
|
||||
func _refresh_sound_script(motion_path: String) -> void:
|
||||
_sound_instances = []
|
||||
_sound_frame = -1
|
||||
if _audio == null or motion_path == "" or not motion_path.to_lower().ends_with(".msa"):
|
||||
return
|
||||
if _audio.has_method("load_mss_for_motion"):
|
||||
_sound_instances = _audio.load_mss_for_motion(motion_path)
|
||||
|
||||
func _set(prop: StringName, val: Variant) -> bool:
|
||||
if String(prop) in _forward and model:
|
||||
model.set(prop, val)
|
||||
|
||||
Reference in New Issue
Block a user