fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
# remote_motion_splash_test —— 40250 ActorInstanceMotionEvent::SPECIAL_ATTACKING
|
||||
# / InstanceBase::AttackProcess 的远端 actor 回归。
|
||||
#
|
||||
# 远端攻击事件必须建立同样的 SplashArea 并执行碰撞、命中上限和去重;
|
||||
# 但远端事件不能伪造本地主角的 CG_ATTACK。
|
||||
extends SceneTree
|
||||
|
||||
const NetPlay = preload("res://net_play.gd")
|
||||
|
||||
class HitActor extends Node3D:
|
||||
var hits := 0
|
||||
var delays: Array = []
|
||||
var shakes := 0
|
||||
var spheres := [{"radius": 60.0, "pos": Vector3.ZERO, "bone": ""}]
|
||||
|
||||
func get_defending_spheres() -> Array:
|
||||
return spheres
|
||||
|
||||
func insert_delay(value: float) -> void:
|
||||
delays.append(value)
|
||||
|
||||
func hit_good(_scalar: float, _stunned: bool) -> void:
|
||||
hits += 1
|
||||
|
||||
func shake() -> void:
|
||||
shakes += 1
|
||||
|
||||
class ClientStub extends Node:
|
||||
var entities: Dictionary = {}
|
||||
var attacks: Array = []
|
||||
|
||||
func is_in_game() -> bool:
|
||||
return true
|
||||
|
||||
func get_entity(vid: int) -> Dictionary:
|
||||
return entities.get(vid, {})
|
||||
|
||||
func get_entities() -> Array:
|
||||
return entities.values()
|
||||
|
||||
func attack(skill: int, vid: int) -> bool:
|
||||
attacks.append([skill, vid])
|
||||
return true
|
||||
|
||||
class PCStub extends Node:
|
||||
var player: Node3D
|
||||
|
||||
class WorldStub extends Node:
|
||||
var nodes: Dictionary = {}
|
||||
|
||||
func node_for(vid: int) -> Node3D:
|
||||
return nodes.get(vid, null)
|
||||
|
||||
var _fail := 0
|
||||
|
||||
func _ck(ok: bool, message: String) -> void:
|
||||
if not ok:
|
||||
_fail += 1
|
||||
printerr("FAIL: " + message)
|
||||
|
||||
func _init() -> void:
|
||||
await _run()
|
||||
if _fail == 0:
|
||||
print("PASS: remote_motion_splash_test (remote SPECIAL_ATTACKING)")
|
||||
quit(0)
|
||||
else:
|
||||
printerr("%d check(s) failed" % _fail)
|
||||
quit(1)
|
||||
|
||||
func _run() -> void:
|
||||
var client := ClientStub.new()
|
||||
var pc := PCStub.new()
|
||||
var world := WorldStub.new()
|
||||
var local := HitActor.new()
|
||||
var remote := HitActor.new()
|
||||
local.position = Vector3(-1.0, 0.0, 0.0)
|
||||
remote.position = Vector3.ZERO
|
||||
pc.player = local
|
||||
for n in [client, pc, world, local, remote]:
|
||||
get_root().add_child(n)
|
||||
|
||||
client.entities[1] = {"vid": 1, "kind": 0, "race": 0, "dead": false,
|
||||
"in_safe": false, "pk_mode": 0}
|
||||
client.entities[2] = {"vid": 2, "kind": 2, "race": 101, "dead": false,
|
||||
"in_safe": false, "owner_vid": 0}
|
||||
world.nodes[1] = local
|
||||
world.nodes[2] = remote
|
||||
|
||||
var np: Node = NetPlay.new()
|
||||
get_root().add_child(np)
|
||||
np.client = client
|
||||
np.pc = pc
|
||||
np.net_world = world
|
||||
np._main_vid = 1
|
||||
np._local_time = 1.0
|
||||
|
||||
var event := {
|
||||
"type": 4,
|
||||
"skill_index": 51,
|
||||
"duration_time": 0.5,
|
||||
"enable_hit_process": true,
|
||||
"attack_type": 0,
|
||||
"hitting_type": 2,
|
||||
"stiffen_time": 0.1,
|
||||
"invisible_time": 0.2,
|
||||
"external_force": 0.0,
|
||||
"hit_limit_count": 1,
|
||||
"collision_spheres": [{"radius": 60.0, "pos": Vector3(100, 0, 0)}],
|
||||
}
|
||||
np.start_remote_motion_splash(2, event)
|
||||
_ck(np._remote_motion_splashes.has(2),
|
||||
"remote SPECIAL_ATTACKING creates an actor-owned SplashArea")
|
||||
|
||||
np._remote_splash_attack_process()
|
||||
_ck(local.hits == 1, "remote splash applies the normal hit reaction to the local actor")
|
||||
_ck(local.delays.size() == 1 and is_equal_approx(float(local.delays[0]), 0.1),
|
||||
"remote splash applies the server motion stiffen time")
|
||||
_ck(remote.delays.size() == 1 and is_equal_approx(float(remote.delays[0]), 0.1),
|
||||
"remote splash inserts delay on the attacking actor, not the local input actor")
|
||||
_ck(client.attacks.is_empty(),
|
||||
"remote splash does not synthesize a local CG_ATTACK packet")
|
||||
|
||||
# Clearing the victim's temporary invisible window must still not produce a
|
||||
# second hit: CActorInstance::m_HitDataMap deduplicates the same VID.
|
||||
np._victim_invisible_until.clear()
|
||||
np._remote_splash_attack_process()
|
||||
_ck(local.hits == 1, "remote splash deduplicates a victim for the area lifetime")
|
||||
_ck(np._remote_motion_splashes.has(2), "active remote splash remains until DuringTime expires")
|
||||
|
||||
np._local_time = 1.5
|
||||
np._remote_splash_attack_process()
|
||||
_ck(not np._remote_motion_splashes.has(2),
|
||||
"remote splash is cleaned up at DuringTime")
|
||||
|
||||
np.free()
|
||||
remote.free()
|
||||
local.free()
|
||||
world.free()
|
||||
pc.free()
|
||||
client.free()
|
||||
await process_frame
|
||||
Reference in New Issue
Block a user