Files
mtgodot-poc/project/equip_rules_test.gd
T

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")