132 lines
7.6 KiB
GDScript
132 lines
7.6 KiB
GDScript
# 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")
|