fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
This commit is contained in:
+78
-6
@@ -31,6 +31,7 @@ class FakeClient extends Node:
|
||||
var quickslots := []
|
||||
var casts := []
|
||||
var skill_uses := []
|
||||
var guild_skill_uses := []
|
||||
var quickslot_ops := []
|
||||
var saids := []
|
||||
var emotes := []
|
||||
@@ -44,11 +45,39 @@ class FakeClient extends Node:
|
||||
func get_points() -> Dictionary: return points
|
||||
func get_quickslots() -> Array: return quickslots
|
||||
func get_target() -> Dictionary: return {"vid": 2000}
|
||||
func cast_skill(mi, rot, x, y) -> bool: casts.append([mi, rot, x, y]); return true
|
||||
func cast_skill(mi, rot, x, y, arg = 0) -> bool: casts.append([mi, rot, x, y, arg]); return true
|
||||
func use_skill(id, target_vid) -> bool: skill_uses.append([id, target_vid]); return true
|
||||
func quickslot_add(pos, type, ref) -> bool: quickslot_ops.append(["add", pos, type, ref]); return true
|
||||
func quickslot_del(pos) -> bool: quickslot_ops.append(["del", pos]); return true
|
||||
func quickslot_swap(pos, change_pos) -> bool: quickslot_ops.append(["swap", pos, change_pos]); return true
|
||||
func use_guild_skill(id, target_vid) -> bool: guild_skill_uses.append([id, target_vid]); return true
|
||||
func quickslot_add(pos, type, ref) -> bool:
|
||||
quickslot_ops.append(["add", pos, type, ref])
|
||||
quickslots = quickslots.filter(func(q): return int(q.get("pos", -1)) != int(pos))
|
||||
quickslots.append({"pos": pos, "type": type, "ref": ref})
|
||||
quickslots_changed.emit()
|
||||
return true
|
||||
func quickslot_del(pos) -> bool:
|
||||
quickslot_ops.append(["del", pos])
|
||||
quickslots = quickslots.filter(func(q): return int(q.get("pos", -1)) != int(pos))
|
||||
quickslots_changed.emit()
|
||||
return true
|
||||
func quickslot_swap(pos, change_pos) -> bool:
|
||||
quickslot_ops.append(["swap", pos, change_pos])
|
||||
var a := -1
|
||||
var b := -1
|
||||
for i in quickslots.size():
|
||||
if int(quickslots[i].get("pos", -1)) == int(pos): a = i
|
||||
if int(quickslots[i].get("pos", -1)) == int(change_pos): b = i
|
||||
if a >= 0 and b >= 0:
|
||||
var tmp: Dictionary = quickslots[a]
|
||||
quickslots[a] = quickslots[b]
|
||||
quickslots[b] = tmp
|
||||
quickslots[a]["pos"] = pos
|
||||
quickslots[b]["pos"] = change_pos
|
||||
elif a >= 0:
|
||||
quickslots[a]["pos"] = change_pos
|
||||
elif b >= 0:
|
||||
quickslots[b]["pos"] = pos
|
||||
quickslots_changed.emit()
|
||||
return true
|
||||
func skill_up(id) -> bool: saids.append(id); return true
|
||||
func use_item(w, c) -> bool: return true
|
||||
func send_emoticon(id) -> bool: emotes.append(id); return true
|
||||
@@ -87,6 +116,13 @@ func _run() -> void:
|
||||
_ck(String(s1.get("name", "")) != "", "skill 1 has a name (%s)" % s1.get("name"))
|
||||
# 列偏移修正后:skill 1 'Three-Way Cut' motion_name=samyeon motion_idx=1, ATTACK_SKILL
|
||||
_ck(st.motion_idx_of(1) == 1, "skill 1 motion_idx == 1 (列偏移已修)")
|
||||
# 40250 constinfo.py 开启 USE_SKILL_EFFECT_UPGRADE_ENABLE=1 后,
|
||||
# CPythonSkill::GetSkillMotionIndex(iGrade) 使用 SKILL_GRADEGAP=25。
|
||||
_ck(st.has_method("motion_idx_for_grade"), "技能表提供等级动作索引入口")
|
||||
if st.has_method("motion_idx_for_grade"):
|
||||
_ck(st.motion_idx_for_grade(1, 1) == 26, "skill 1 M 级动作索引 = base + 25")
|
||||
_ck(st.motion_idx_for_grade(1, 2) == 51, "skill 1 G 级动作索引 = base + 50")
|
||||
_ck(st.motion_grade_by_idx(26) == 1, "远端动作索引 26 可反解为 M 级")
|
||||
_ck(String(s1.get("motion", "")) == "samyeon", "skill 1 motion_name == samyeon")
|
||||
var skill_icon := UiAssets.load_tex(assets, "ETC/ymir work/ui/skill/warrior/samyeon_01.sub")
|
||||
_ck(skill_icon != null, "skill 1 icon loads from .sub + shared SkillWarrior.dds")
|
||||
@@ -128,6 +164,10 @@ func _run() -> void:
|
||||
_ck(st.is_need_bow(46) and st.is_ranged(46), "IsNeedBow(46)")
|
||||
_ck(st.can_use_weapon_type(46, st.WEAPON_BOW), "CanUseWeaponType(46, BOW)")
|
||||
_ck(not st.can_use_weapon_type(46, st.WEAPON_SWORD), "CanUseWeaponType(46, SWORD) == false")
|
||||
_ck(st.motion_loop_count_of(46, 5) == 2,
|
||||
"skill 46 MotionLoopCountFormula(5) == 2")
|
||||
_ck(st.motion_state_arg(46, 5) == 2,
|
||||
"skill 46 OnUseSkill arg packs loop count in low nibble")
|
||||
# skill 138 Horse Stump:HORSE_SKILL|SEARCH_TARGET|CHARGE_ATTACK
|
||||
_ck(st.is_horse_skill(138), "IsHorseSkill(138)")
|
||||
_ck(st.is_auto_search_target(138), "IsAutoSearchTarget(138)")
|
||||
@@ -303,6 +343,11 @@ func _run() -> void:
|
||||
var qb: Node = Quickbar.new()
|
||||
get_root().add_child(qb)
|
||||
qb.setup(fc, st, ui, func() -> Node: return pl)
|
||||
var cooldown_default: float = qb._skill_cooldown(1)
|
||||
fc.points["casting_speed"] = 20
|
||||
_ck(absf(qb._skill_cooldown(1) - cooldown_default * 1.8) < 0.001,
|
||||
"POINT_CASTING_SPEED=20 applies the 40250 180% cooldown scale")
|
||||
fc.points["casting_speed"] = 0
|
||||
var cast_targets: Array = []
|
||||
qb.skill_cast_started.connect(func(sid: int, vid: int): cast_targets.append([sid, vid]))
|
||||
qb.assign(0, "skill", 1)
|
||||
@@ -316,6 +361,11 @@ func _run() -> void:
|
||||
_ck(cast_targets == [[1, 2000]], "successful cast publishes resolved visual target")
|
||||
if fc.casts.size() == 1:
|
||||
_ck(int(fc.casts[0][0]) == st.motion_idx_of(1), "cast used skill 1's motion_idx")
|
||||
_ck(int(fc.casts[0][4]) == 0, "skill 1 with no loop formula sends OnUseSkill arg 0")
|
||||
fc.skills[0]["master"] = 1
|
||||
_ck(qb._motion_idx_for_skill(1) == 26,
|
||||
"quickbar uses the current M grade motion index (base + SKILL_GRADEGAP)")
|
||||
fc.skills[0]["master"] = 0
|
||||
# 冷却中不再施放
|
||||
qb.activate(0)
|
||||
_ck(fc.skill_uses.size() == 1, "on cooldown -> no second use_skill")
|
||||
@@ -330,6 +380,28 @@ func _run() -> void:
|
||||
qb.activate(0)
|
||||
_ck(fc.casts.size() == 1, "server refresh cannot reset skill cooldown")
|
||||
fc.quickslots = [] # restore this fixture's original server state for later UI cases
|
||||
# ClientVS22::ClickSkillSlot routes GUILD skills to UseGuildSkill rather than
|
||||
# silently dropping them. The dedicated path still enforces guild-war-only.
|
||||
var guild_fc := FakeClient.new()
|
||||
get_root().add_child(guild_fc)
|
||||
var guild_qb: Node = Quickbar.new()
|
||||
get_root().add_child(guild_qb)
|
||||
guild_qb.setup(guild_fc, st, ui, func() -> Node: return pl)
|
||||
guild_qb.assign(0, "skill", 152, false)
|
||||
var guild_rejected: Array = []
|
||||
guild_qb.skill_rejected.connect(func(sid: int, code: String): guild_rejected.append([sid, code]))
|
||||
guild_qb._skill_gate.guild_war_active = false
|
||||
guild_qb.activate(0)
|
||||
_ck(guild_rejected == [[152, "ONLY_FOR_GUILD_WAR"]] and guild_fc.guild_skill_uses.is_empty(),
|
||||
"guild skill outside guild war -> rejected without ordinary CG_USE_SKILL")
|
||||
guild_qb._skill_gate.guild_war_active = true
|
||||
guild_qb.activate(0)
|
||||
_ck(guild_fc.guild_skill_uses == [[152, 0]],
|
||||
"guild skill -> use_guild_skill(skill, 0)")
|
||||
_ck(guild_fc.casts.size() == 1 and int(guild_fc.casts[0][4]) == 1,
|
||||
"guild skill -> FUNC_SKILL action with loop count 1")
|
||||
guild_qb.free()
|
||||
guild_fc.free()
|
||||
# 40250 quickslot skill refs are player skill-slot numbers. The same ref=2
|
||||
# selects a different skill after switching warrior skill group.
|
||||
fc.skill_group = 2
|
||||
@@ -342,8 +414,8 @@ func _run() -> void:
|
||||
_ck(fc.quickslot_ops == [["add", 0, 2, 5]],
|
||||
"assign warrior group 2 skill 20 persists player skill-slot 5")
|
||||
fc.skill_group = 1
|
||||
fc.quickslots = []
|
||||
qb.assign(0, "skill", 1, false)
|
||||
fc.quickslots = [{"pos": 0, "type": 2, "ref": 1}, {"pos": 1, "type": 2, "ref": 1}]
|
||||
qb.restore_from_server()
|
||||
qb._clear(1)
|
||||
# GC_SKILL_COOLTIME_END 提前解锁
|
||||
fc.skill_cooldown_end.emit(1)
|
||||
|
||||
Reference in New Issue
Block a user