# equip_rules_test —— §4.1 修改 2:装备穿戴合法性校验(EquipRules)1:1 自检。 # canonical #17。 godot --headless --path project --script equip_rules_test.gd extends SceneTree const EquipRules = preload("res://equip_rules.gd") var failed := 0 func _ck(ok: bool, message: String) -> void: if not ok: failed += 1 printerr("FAIL: " + message) func _weapon(extra := {}) -> Dictionary: var d := {"type": 1, "sub_type": 0, "anti_flags": 0, "wear_flags": 1 << 4, "limits": []} d.merge(extra, true) return d func _init() -> void: _test_race_tables() _test_default_wear() _test_slot_accepts() _test_antiflag() _test_wearable() _test_limits() _test_order_and_newslots() if failed == 0: print("PASS: equip_rules_test (§4.1 CanEquipNow antiflag→wearable→limit 1:1)") quit(0) else: quit(1) func _test_race_tables() -> void: # race 0..7 = WARRIOR_M / ASSASSIN_W / SURA_M / SHAMAN_W / WARRIOR_W / ASSASSIN_M / SURA_W / SHAMAN_M _ck(EquipRules.sex_of_race(0) == EquipRules.SEX_MALE, "race 0 male") _ck(EquipRules.sex_of_race(1) == EquipRules.SEX_FEMALE, "race 1 female") _ck(EquipRules.sex_of_race(4) == EquipRules.SEX_FEMALE, "race 4 female (warrior W)") _ck(EquipRules.sex_of_race(5) == EquipRules.SEX_MALE, "race 5 male (assassin M)") _ck(EquipRules.job_of_race(0) == EquipRules.JOB_WARRIOR, "race 0 warrior") _ck(EquipRules.job_of_race(1) == EquipRules.JOB_ASSASSIN, "race 1 assassin") _ck(EquipRules.job_of_race(6) == EquipRules.JOB_SURA, "race 6 sura") _ck(EquipRules.job_of_race(7) == EquipRules.JOB_SHAMAN, "race 7 shaman") func _test_default_wear() -> void: _ck(EquipRules.default_wear_for(_weapon()) == EquipRules.WEAR_WEAPON, "weapon -> WEAR_WEAPON") _ck(EquipRules.default_wear_for({"type": 2, "sub_type": 0}) == EquipRules.WEAR_BODY, "armor body -> WEAR_BODY") _ck(EquipRules.default_wear_for({"type": 2, "sub_type": 1}) == EquipRules.WEAR_HEAD, "armor head -> WEAR_HEAD") _ck(EquipRules.default_wear_for({"type": 2, "sub_type": 2}) == EquipRules.WEAR_SHIELD, "armor shield -> WEAR_SHIELD") _ck(EquipRules.default_wear_for({"type": 2, "sub_type": 5}) == EquipRules.WEAR_NECK, "armor neck -> WEAR_NECK") _ck(EquipRules.default_wear_for({"type": 28, "sub_type": 0}) == EquipRules.WEAR_COSTUME_BODY, "costume body -> 19") _ck(EquipRules.default_wear_for({"type": 28, "sub_type": 1}) == EquipRules.WEAR_COSTUME_HAIR, "costume hair -> 20") _ck(EquipRules.default_wear_for({"type": 33}) == EquipRules.WEAR_RING1, "ring -> WEAR_RING1") _ck(EquipRules.default_wear_for({"type": 34}) == EquipRules.WEAR_BELT, "belt -> WEAR_BELT") _ck(EquipRules.default_wear_for({"type": 3}) == -1, "potion -> not equippable") _ck(not EquipRules.is_equippable({"type": 3}), "is_equippable(potion) false") _ck(EquipRules.is_equippable(_weapon()), "is_equippable(weapon) true") func _test_slot_accepts() -> void: _ck(EquipRules.slot_accepts(_weapon(), EquipRules.WEAR_WEAPON), "weapon accepted at WEAR_WEAPON") _ck(not EquipRules.slot_accepts(_weapon(), EquipRules.WEAR_BODY), "weapon rejected at WEAR_BODY") _ck(EquipRules.slot_accepts({"type": 2, "wear_flags": 1 << 0}, EquipRules.WEAR_BODY), "body armor at WEAR_BODY") _ck(EquipRules.slot_accepts({"type": 2, "wear_flags": 1 << 7}, EquipRules.WEAR_UNIQUE2), "unique flag at WEAR_UNIQUE2") _ck(EquipRules.slot_accepts({"type": 33}, EquipRules.WEAR_RING1), "ring type at WEAR_RING1 (no wear_flags)") _ck(not EquipRules.slot_accepts(_weapon(), EquipRules.WEAR_RING1), "weapon rejected at WEAR_RING1") _ck(EquipRules.slot_accepts({"type": 34}, EquipRules.WEAR_BELT), "belt type at WEAR_BELT") func _test_antiflag() -> void: var female_only := _weapon({"anti_flags": EquipRules.ANTIFLAG_FEMALE}) _ck(not EquipRules.can_equip(female_only, -1, {"race": 1})["ok"], "ANTI_FEMALE blocks female (race 1)") _ck(EquipRules.can_equip(female_only, -1, {"race": 1})["code"] == "ANTI_SEX", "code ANTI_SEX") _ck(EquipRules.can_equip(female_only, -1, {"race": 0})["ok"], "ANTI_FEMALE ok for male (race 0)") var male_only := _weapon({"anti_flags": EquipRules.ANTIFLAG_MALE}) _ck(not EquipRules.can_equip(male_only, -1, {"race": 0})["ok"], "ANTI_MALE blocks male") _ck(EquipRules.can_equip(male_only, -1, {"sex": EquipRules.SEX_FEMALE})["ok"], "ANTI_MALE ok when sex override female") var no_warrior := _weapon({"anti_flags": EquipRules.ANTIFLAG_WARRIOR}) _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)") 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") func _test_wearable() -> void: var res := EquipRules.can_equip(_weapon(), EquipRules.WEAR_BODY, {"race": 0}) _ck(not res["ok"] and res["code"] == "NOT_WEARABLE_HERE", "weapon at WEAR_BODY -> NOT_WEARABLE_HERE") _ck(EquipRules.can_equip(_weapon(), EquipRules.WEAR_WEAPON, {"race": 0})["ok"], "weapon at WEAR_WEAPON ok") func _test_limits() -> void: var lv30 := _weapon({"limits": [{"type": EquipRules.LIMIT_LEVEL, "value": 30}]}) var r := EquipRules.can_equip(lv30, -1, {"race": 0, "level": 20}) _ck(not r["ok"] and r["code"] == "LIMIT_LEVEL" and int(r["need"]) == 30, "level 20 < 30 -> LIMIT_LEVEL need 30") _ck(EquipRules.can_equip(lv30, -1, {"race": 0, "level": 30})["ok"], "level 30 == 30 ok") _ck(EquipRules.can_equip(lv30, -1, {"race": 0, "level": 99})["ok"], "level 99 ok") var str50 := _weapon({"limits": [{"type": EquipRules.LIMIT_STR, "value": 50}]}) _ck(EquipRules.can_equip(str50, -1, {"race": 0})["ok"], "STR limit skipped when ctx has no st (-1)") _ck(not EquipRules.can_equip(str50, -1, {"race": 0, "st": 40})["ok"], "st 40 < 50 -> fail") _ck(EquipRules.can_equip(str50, -1, {"race": 0, "st": 50})["ok"], "st 50 ok") _ck(EquipRules.can_equip(str50, -1, {"race": 0, "st": 40})["code"] == "LIMIT_STR", "code LIMIT_STR") var con20 := _weapon({"limits": [{"type": EquipRules.LIMIT_CON, "value": 20}]}) _ck(not EquipRules.can_equip(con20, -1, {"race": 0, "ht": 10})["ok"], "ht 10 < 20 -> LIMIT_CON") # value <= 0 的 limit 项被忽略 var zero := _weapon({"limits": [{"type": EquipRules.LIMIT_LEVEL, "value": 0}]}) _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 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") # wearable 先于 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") # 新装备槽:ring / belt / costume 按 type 判定,无 wear_flags 也能穿 _ck(EquipRules.can_equip({"type": 33}, EquipRules.WEAR_RING1, {"race": 0})["ok"], "ring at RING1 ok") _ck(EquipRules.can_equip({"type": 34}, EquipRules.WEAR_BELT, {"race": 0})["ok"], "belt at BELT ok") _ck(EquipRules.can_equip({"type": 28, "sub_type": 0}, -1, {"race": 0})["ok"], "costume body default slot ok") _ck(EquipRules.can_equip({"type": 28}, EquipRules.WEAR_BELT, {"race": 0})["code"] == "NOT_WEARABLE_HERE", "costume at BELT rejected") # 空物品 _ck(EquipRules.can_equip({}, -1, {})["code"] == "NO_ITEM", "empty item -> NO_ITEM")