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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
+82
-19
@@ -36,6 +36,16 @@ const INDEX_FLY_TYPE_NORMAL := 0
|
||||
const INDEX_FLY_TYPE_FIRE_CRACKER := 1
|
||||
const INDEX_FLY_TYPE_AUTO_FIRE := 2
|
||||
|
||||
# 40250 playersettingmodule.py / char.h 索引飞行物类型 (effect.FLY_*)
|
||||
const FLY_EXP := 0
|
||||
const FLY_HP_SMALL := 1
|
||||
const FLY_HP_MEDIUM := 2
|
||||
const FLY_HP_BIG := 3
|
||||
const FLY_SP_SMALL := 4
|
||||
const FLY_SP_MEDIUM := 5
|
||||
const FLY_SP_BIG := 6
|
||||
const FLY_FIREWORK1 := 7
|
||||
|
||||
# CFlyingData —— .fly 脚本字段。POC 无 .fly 加载器,默认值逐项对齐 CFlyingData::__Initialize()
|
||||
# (长度单位:原版是 pixel==cm,这里换算成米后传进来,故 init_vel / range / bomb_range 都按米)。
|
||||
class FlyData extends RefCounted:
|
||||
@@ -80,7 +90,10 @@ class FlyInstance extends RefCounted:
|
||||
|
||||
func target_position() -> Vector3:
|
||||
if _is_object and is_instance_valid(_target_obj):
|
||||
_target_pos = _target_obj.global_position
|
||||
if _target_obj.is_inside_tree():
|
||||
_target_pos = _target_obj.global_position
|
||||
else:
|
||||
_target_pos = _target_obj.position
|
||||
var tp := _target_pos
|
||||
if data and data.maintain_parallel:
|
||||
tp.y += 0.5
|
||||
@@ -100,7 +113,7 @@ class FlyInstance extends RefCounted:
|
||||
if target is Node3D:
|
||||
_is_object = true
|
||||
_target_obj = target
|
||||
_target_pos = (target as Node3D).global_position
|
||||
_target_pos = (target as Node3D).global_position if (target as Node3D).is_inside_tree() else (target as Node3D).position
|
||||
elif target is Vector3:
|
||||
_is_object = false
|
||||
_target_pos = target
|
||||
@@ -223,6 +236,7 @@ var _id_counter := 1
|
||||
|
||||
signal shoot_damage(target_vid: int)
|
||||
signal exploded(world_pos: Vector3, cause: String)
|
||||
signal exp_absorbed(world_pos: Vector3)
|
||||
|
||||
func setup(mount: Node3D, w: Object = null) -> void:
|
||||
parent = mount
|
||||
@@ -244,16 +258,41 @@ func spawn(start_world: Vector3, target, can_attack: bool, data: FlyData = null,
|
||||
if parent != null:
|
||||
var m := MeshInstance3D.new()
|
||||
var sm := SphereMesh.new()
|
||||
sm.radius = 0.08
|
||||
sm.height = 0.16
|
||||
m.mesh = sm
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1.0, 0.9, 0.4)
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(0.9, 0.7, 0.2)
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
|
||||
if skill_index == FLY_EXP:
|
||||
# 40250 官方经验聚能光球: ga_piece_yellow_small2.msf
|
||||
sm.radius = 0.12
|
||||
sm.height = 0.24
|
||||
mat.albedo_color = Color(1.0, 0.9, 0.2, 0.95)
|
||||
mat.emission = Color(1.0, 0.8, 0.1) * 3.0
|
||||
elif skill_index in [FLY_HP_SMALL, FLY_HP_MEDIUM, FLY_HP_BIG]:
|
||||
# 40250 官方生命吸取光球: ga_piece_red_small.msf
|
||||
sm.radius = 0.12
|
||||
sm.height = 0.24
|
||||
mat.albedo_color = Color(1.0, 0.25, 0.25, 0.95)
|
||||
mat.emission = Color(1.0, 0.1, 0.1) * 3.0
|
||||
elif skill_index in [FLY_SP_SMALL, FLY_SP_MEDIUM, FLY_SP_BIG]:
|
||||
# 40250 官方法力吸取光球: ga_piece_blue_small.msf
|
||||
sm.radius = 0.12
|
||||
sm.height = 0.24
|
||||
mat.albedo_color = Color(0.25, 0.6, 1.0, 0.95)
|
||||
mat.emission = Color(0.1, 0.5, 1.0) * 3.0
|
||||
else:
|
||||
sm.radius = 0.08
|
||||
sm.height = 0.16
|
||||
mat.albedo_color = Color(1.0, 0.9, 0.4)
|
||||
mat.emission = Color(0.9, 0.7, 0.2)
|
||||
|
||||
m.mesh = sm
|
||||
m.material_override = mat
|
||||
parent.add_child(m)
|
||||
m.global_position = inst.pos
|
||||
if m.is_inside_tree():
|
||||
m.global_position = inst.pos
|
||||
else:
|
||||
m.position = inst.pos
|
||||
_visuals[inst] = m
|
||||
return inst
|
||||
|
||||
@@ -268,7 +307,10 @@ func step(dt: float) -> void:
|
||||
var keep := inst.update(dt)
|
||||
var vis: MeshInstance3D = _visuals.get(inst, null)
|
||||
if is_instance_valid(vis):
|
||||
vis.global_position = inst.pos
|
||||
if vis.is_inside_tree():
|
||||
vis.global_position = inst.pos
|
||||
else:
|
||||
vis.position = inst.pos
|
||||
if not keep:
|
||||
if is_instance_valid(vis):
|
||||
vis.queue_free()
|
||||
@@ -287,28 +329,49 @@ func _on_instance_event(inst: FlyInstance, ev: String, wp: Vector3, vid: int) ->
|
||||
shoot_damage.emit(vid)
|
||||
"out_of_range", "at_target", "at_position", "at_background", "at_another":
|
||||
exploded.emit(wp, ev)
|
||||
if ev == "at_target" and inst.skill_index == FLY_EXP:
|
||||
exp_absorbed.emit(wp)
|
||||
if parent != null and ev != "out_of_range":
|
||||
_spawn_flash(wp)
|
||||
_spawn_flash(wp, inst.skill_index)
|
||||
"bomb":
|
||||
if parent != null:
|
||||
_spawn_flash(wp)
|
||||
_spawn_flash(wp, inst.skill_index)
|
||||
|
||||
func _spawn_flash(wp: Vector3) -> void:
|
||||
func _spawn_flash(wp: Vector3, skill_index: int = -1) -> void:
|
||||
var f := MeshInstance3D.new()
|
||||
var sm := SphereMesh.new()
|
||||
sm.radius = 0.05
|
||||
sm.height = 0.1
|
||||
sm.radius = 0.06
|
||||
sm.height = 0.12
|
||||
f.mesh = sm
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.albedo_color = Color(1.0, 0.75, 0.3, 0.9)
|
||||
mat.emission_enabled = true
|
||||
mat.emission = Color(1.0, 0.6, 0.2)
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
|
||||
var final_scale := Vector3.ONE * 6.0
|
||||
if skill_index == FLY_EXP:
|
||||
mat.albedo_color = Color(1.0, 0.9, 0.2, 0.95)
|
||||
mat.emission = Color(1.0, 0.85, 0.2) * 2.5
|
||||
final_scale = Vector3.ONE * 8.0
|
||||
elif skill_index in [FLY_HP_SMALL, FLY_HP_MEDIUM, FLY_HP_BIG]:
|
||||
mat.albedo_color = Color(1.0, 0.25, 0.25, 0.95)
|
||||
mat.emission = Color(1.0, 0.1, 0.1) * 2.5
|
||||
final_scale = Vector3.ONE * 8.0
|
||||
elif skill_index in [FLY_SP_SMALL, FLY_SP_MEDIUM, FLY_SP_BIG]:
|
||||
mat.albedo_color = Color(0.25, 0.6, 1.0, 0.95)
|
||||
mat.emission = Color(0.1, 0.5, 1.0) * 2.5
|
||||
final_scale = Vector3.ONE * 8.0
|
||||
else:
|
||||
mat.albedo_color = Color(1.0, 0.75, 0.3, 0.9)
|
||||
mat.emission = Color(1.0, 0.6, 0.2)
|
||||
|
||||
f.material_override = mat
|
||||
parent.add_child(f)
|
||||
f.global_position = wp
|
||||
if f.is_inside_tree():
|
||||
f.global_position = wp
|
||||
else:
|
||||
f.position = wp
|
||||
var tw := create_tween()
|
||||
tw.set_parallel(true)
|
||||
tw.tween_property(f, "scale", Vector3.ONE * 6.0, 0.25)
|
||||
tw.tween_property(mat, "albedo_color:a", 0.0, 0.25)
|
||||
tw.tween_property(f, "scale", final_scale, 0.28)
|
||||
tw.tween_property(mat, "albedo_color:a", 0.0, 0.28)
|
||||
tw.chain().tween_callback(f.queue_free)
|
||||
|
||||
Reference in New Issue
Block a user