装备校验链/快捷栏/公会/好友情侣/交易仓库五项差距修复(增量 129)

- 装备校验:can_equip 按 CanEquipNow→FindEquipCell→EquipItem 精确重排,
  新增 find_equip_cell() 1:1 复刻(RING/UNIQUE 占用翻转、COSTUME/BELT/DS),
  帝国 antiflag 降级为 tooltip 提示,LIMIT_PCBANG/REAL_TIME* 语义与倒计时文案
- 快捷栏:死亡全槽禁用+激活守卫、变身(affect 220)技能置灰、
  物品槽失配置灰(服务器回收/换武器后刷新)
- 公会:GUILD_AUTH_* 权限位门控踢人/公告/技能升级,/gskillup、
  被宣战应答弹窗(/war·/nowar)、资金存取行(40250 服务端已禁用仍保留 1:1)
- 好友/情侣:messenger 三固定组(好友/公会/情侣),lover_login/logout/
  near/far/divorce 命令事件→在线/在身边状态与离婚隐藏
- 交易/仓库:ExchangeState.last_notice(ALREADY/LESS_GOLD)结束浮层提示;
  仓库改密 /safebox_change_password 三段输入;关窗补 /safebox_close·/mall_close;
  仓库金钱经核实 40250 无 wire 协议,UI 定界只读
- app_flow:重连时 guild_marks_ready 重复连接加 is_connected 守卫
- 相机 look_at 零向量守卫(首帧/传送落点)
- 测试:equip/love/guild 断言更新,79 项中 78 绿(game_camera_test 为
  HEAD 既有失败);真服冒烟 LIVE_SMOKE PASS(192.168.21.203 全链路)
- docs/CLIENT-GAP.md 同步增量 129

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kUFvQGYusuY3dkfGitmAe
This commit is contained in:
shenlei
2026-09-07 21:43:30 +09:00
co-authored by Claude Code
parent 61272b75e9
commit 8092ff44e2
25 changed files with 1067 additions and 134 deletions
+67 -11
View File
@@ -1,4 +1,6 @@
# equip_rules_test —— §4.1 修改 2装备穿戴合法性校验(EquipRules)1:1 自检。
# equip_rules_test —— 装备穿戴合法性校验(EquipRules)1:1 自检。
# 顺序对齐服务器真实链:CanEquipNow(JOB→limit→UNIQUE 去重) → FindEquipCell →
# EquipItem 后置(燕尾服/变身/性别/骑乘)。
# canonical #17。 godot --headless --path project --script equip_rules_test.gd
extends SceneTree
@@ -24,8 +26,10 @@ func _init() -> void:
_test_wearable()
_test_limits()
_test_order_and_newslots()
_test_find_equip_cell()
_test_post_checks()
if failed == 0:
print("PASS: equip_rules_test (§4.1 CanEquipNow antiflag→wearable→limit 1:1)")
print("PASS: equip_rules_test (CanEquipNow→FindEquipCell→EquipItem 后置 1:1)")
quit(0)
else:
quit(1)
@@ -78,10 +82,13 @@ func _test_antiflag() -> void:
_ck(EquipRules.can_equip(no_warrior, -1, {"race": 0})["code"] == "ANTI_JOB", "ANTI_WARRIOR blocks warrior (race 0)")
_ck(EquipRules.can_equip(no_warrior, -1, {"race": 1})["ok"], "ANTI_WARRIOR ok for assassin (race 1)")
# 帝国 antiflag 不是装备门(服务器装备路径从不检查)——can_equip 放行,
# 冲突位只由 empire_warning() 给 tooltip。
var no_emp_a := _weapon({"anti_flags": EquipRules.ANTIFLAG_EMPIRE_A})
_ck(EquipRules.can_equip(no_emp_a, -1, {"empire": 1})["code"] == "ANTI_EMPIRE", "ANTI_EMPIRE_A blocks empire 1")
_ck(EquipRules.can_equip(no_emp_a, -1, {"empire": 2})["ok"], "ANTI_EMPIRE_A ok for empire 2")
_ck(EquipRules.can_equip(no_emp_a, -1, {"empire": 0})["ok"], "no empire -> anti empire skipped")
_ck(EquipRules.can_equip(no_emp_a, -1, {"empire": 1})["ok"], "ANTI_EMPIRE no longer blocks equip")
_ck(EquipRules.empire_warning(no_emp_a, 1) == EquipRules.ANTIFLAG_EMPIRE_A, "empire_warning reports conflict")
_ck(EquipRules.empire_warning(no_emp_a, 2) == 0, "empire_warning clean for empire 2")
_ck(EquipRules.empire_warning(no_emp_a, 0) == 0, "empire_warning clean without empire")
func _test_wearable() -> void:
var res := EquipRules.can_equip(_weapon(), EquipRules.WEAR_BODY, {"race": 0})
@@ -109,16 +116,22 @@ func _test_limits() -> void:
_ck(EquipRules.can_equip(zero, -1, {"race": 0, "level": 1})["ok"], "limit value 0 ignored")
func _test_order_and_newslots() -> void:
# anti-flag 先于 limit:两个都失败 → 返回 ANTI_SEX
# limit 先于性别(服务器顺序:CanEquipNow 的 limit 在 EquipItem 的
# FN_check_item_sex 之前):两个都失败 → 返回 LIMIT_LEVEL
var both := _weapon({"anti_flags": EquipRules.ANTIFLAG_FEMALE,
"limits": [{"type": EquipRules.LIMIT_LEVEL, "value": 99}]})
_ck(EquipRules.can_equip(both, -1, {"race": 1, "level": 1})["code"] == "ANTI_SEX",
"antiflag checked before limit")
_ck(EquipRules.can_equip(both, -1, {"race": 1, "level": 1})["code"] == "LIMIT_LEVEL",
"limit checked before sex antiflag (server order)")
# JOB 仍是最先的 antiflag 检查
var job_first := _weapon({"anti_flags": EquipRules.ANTIFLAG_WARRIOR,
"limits": [{"type": EquipRules.LIMIT_LEVEL, "value": 99}]})
_ck(EquipRules.can_equip(job_first, -1, {"race": 0, "level": 1})["code"] == "ANTI_JOB",
"job antiflag checked before limit")
# wearable 先于 limit
# 显式槽不匹配(客户端防御层)在 limit 之后
var wl := _weapon({"limits": [{"type": EquipRules.LIMIT_LEVEL, "value": 99}]})
_ck(EquipRules.can_equip(wl, EquipRules.WEAR_BODY, {"race": 0, "level": 1})["code"] == "NOT_WEARABLE_HERE",
"wearable checked before limit")
_ck(EquipRules.can_equip(wl, EquipRules.WEAR_BODY, {"race": 0, "level": 99})["code"] == "NOT_WEARABLE_HERE",
"explicit slot mismatch -> NOT_WEARABLE_HERE")
# 新装备槽:ring / belt / costume 按 type 判定,无 wear_flags 也能穿
_ck(EquipRules.can_equip({"type": 33}, EquipRules.WEAR_RING1, {"race": 0})["ok"], "ring at RING1 ok")
@@ -129,3 +142,46 @@ func _test_order_and_newslots() -> void:
# 空物品
_ck(EquipRules.can_equip({}, -1, {})["code"] == "NO_ITEM", "empty item -> NO_ITEM")
func _test_find_equip_cell() -> void:
# item.cpp:472 的 else-if 优先级与 RING/UNIQUE 占用翻转。
_ck(EquipRules.find_equip_cell(_weapon()) == EquipRules.WEAR_WEAPON, "weapon cell")
_ck(EquipRules.find_equip_cell(_weapon({"wear_flags": (1 << 0) | (1 << 4)})) == EquipRules.WEAR_BODY,
"BODY wins over WEAPON in else-if chain")
_ck(EquipRules.find_equip_cell({"type": 33}) == EquipRules.WEAR_RING1, "ring -> RING1 when free")
_ck(EquipRules.find_equip_cell({"type": 33}, {EquipRules.WEAR_RING1: 1234}) == EquipRules.WEAR_RING2,
"ring -> RING2 when RING1 occupied")
var uniq := {"type": 2, "wear_flags": 1 << 7, "vnum": 70001}
_ck(EquipRules.find_equip_cell(uniq) == EquipRules.WEAR_UNIQUE1, "unique -> UNIQUE1 when free")
_ck(EquipRules.find_equip_cell(uniq, {EquipRules.WEAR_UNIQUE1: 70002}) == EquipRules.WEAR_UNIQUE2,
"unique -> UNIQUE2 when UNIQUE1 occupied")
# 无 wear_flag 且非 COSTUME/DS/RING/BELT → -1TOTEM 同)
_ck(EquipRules.find_equip_cell({"type": 3}) == -1, "no wear flag -> -1")
_ck(EquipRules.find_equip_cell({"type": 26, "wear_flags": 1 << 0}) == -1, "totem -> -1")
# 龙魂石走客户端专用路径
_ck(EquipRules.find_equip_cell({"type": 29, "sub_type": 3}) == -1, "DS -> -1 (dedicated path)")
func _test_post_checks() -> void:
# EquipItem 后置门(char_item.cpp:6122+)。
# 变身中除 ARROW 外一律拒绝
var bow := _weapon({"wear_flags": 1 << 9})
_ck(EquipRules.can_equip(_weapon(), -1, {"race": 0, "polymorphed": true})["code"] == "POLYMORPH",
"polymorphed blocks weapon")
_ck(EquipRules.can_equip(bow, -1, {"race": 0, "polymorphed": true})["ok"],
"polymorphed allows ARROW")
# 燕尾服骑乘禁止(vnum 11901..11904
var tux := {"type": 2, "sub_type": 0, "wear_flags": 1 << 0, "vnum": 11901}
_ck(EquipRules.can_equip(tux, -1, {"race": 0, "riding": true})["code"] == "RIDE_TUXEDO",
"tuxedo blocked while riding")
_ck(EquipRules.can_equip(tux, -1, {"race": 0, "riding": false})["ok"], "tuxedo ok on foot")
# 骑乘物品已骑乘禁止(UNIQUE + RIDE/MOUNT_RIDE 子类)
var horse := {"type": 16, "sub_type": 2, "wear_flags": 1 << 7, "vnum": 50051}
_ck(EquipRules.can_equip(horse, -1, {"race": 0, "riding": true})["code"] == "RIDE_ITEM",
"ride item blocked while riding")
_ck(EquipRules.can_equip(horse, -1, {"race": 0, "riding": false})["ok"], "ride item ok on foot")
# UNIQUE 同 vnum 去重
var u := {"type": 2, "wear_flags": 1 << 7, "vnum": 70001}
_ck(EquipRules.can_equip(u, -1, {"race": 0, "worn": {EquipRules.WEAR_UNIQUE1: 70001}})["code"] == "UNIQUE_DUP",
"same vnum in UNIQUE1 -> UNIQUE_DUP")
_ck(EquipRules.can_equip(u, -1, {"race": 0, "worn": {EquipRules.WEAR_UNIQUE1: 70002}})["ok"],
"different vnum in UNIQUE1 ok")