Files
mtgodot-poc/project/equip_rules.gd
T

235 lines
7.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# equip_rules.gd —— 装备穿戴合法性校验(纯函数校验器)。
#
# 语义顺序对齐服务器 `char_item.cpp::CanEquipNow`
# antiflag(性别 / 职业 / 帝国) → wearable_flag 与目标 wear 槽匹配 → limit(等级 / 属性)。
#
# 参考枚举 1:1 自 `REF/GameLib/ItemData.h``EItemAntiFlag` / `EItemWearableFlag` /
# `EWearPositions` / `ELimitTypes` / `EItemType` / `EArmorSubTypes` / `ECostumeSubTypes`)。
# 40250 客户端本身**不做** `CanEquipNow` 前置门(`PythonItemModule` 只暴露 `IsWearableFlag` /
# `GetLimit`,真正的仲裁在服务器)。这里按 §3.9 `__CanShot` 的方式在客户端防御式重建一道前置
# 门——精确的 tie-break 顺序、PCBANG / REAL_TIME 类 limit、已穿同槽替换等留待接服务器比对(seam)。
extends RefCounted
# --- EItemAntiFlagItemData.hclassic 排列:帝国位在 1<<9..1<<11---
const ANTIFLAG_FEMALE := 1 << 0
const ANTIFLAG_MALE := 1 << 1
const ANTIFLAG_WARRIOR := 1 << 2
const ANTIFLAG_ASSASSIN := 1 << 3
const ANTIFLAG_SURA := 1 << 4
const ANTIFLAG_SHAMAN := 1 << 5
const ANTIFLAG_EMPIRE_A := 1 << 9
const ANTIFLAG_EMPIRE_B := 1 << 10
const ANTIFLAG_EMPIRE_R := 1 << 11
# --- EItemWearableFlag ---
const WEARABLE_BODY := 1 << 0
const WEARABLE_HEAD := 1 << 1
const WEARABLE_FOOTS := 1 << 2
const WEARABLE_WRIST := 1 << 3
const WEARABLE_WEAPON := 1 << 4
const WEARABLE_NECK := 1 << 5
const WEARABLE_EAR := 1 << 6
const WEARABLE_UNIQUE := 1 << 7
const WEARABLE_SHIELD := 1 << 8
const WEARABLE_ARROW := 1 << 9
# --- EWearPositions0..10+ 新装备系统槽(GameType.h19..23---
const WEAR_BODY := 0
const WEAR_HEAD := 1
const WEAR_FOOTS := 2
const WEAR_WRIST := 3
const WEAR_WEAPON := 4
const WEAR_NECK := 5
const WEAR_EAR := 6
const WEAR_UNIQUE1 := 7
const WEAR_UNIQUE2 := 8
const WEAR_ARROW := 9
const WEAR_SHIELD := 10
const WEAR_MAX_NUM := 11
const WEAR_COSTUME_BODY := 19
const WEAR_COSTUME_HAIR := 20
const WEAR_RING1 := 21
const WEAR_RING2 := 22
const WEAR_BELT := 23
# --- ELimitTypes ---
const LIMIT_NONE := 0
const LIMIT_LEVEL := 1
const LIMIT_STR := 2
const LIMIT_DEX := 3
const LIMIT_INT := 4
const LIMIT_CON := 5
const LIMIT_PCBANG := 6
const LIMIT_REAL_TIME := 7
# --- EItemType(相关项)+ 子类 ---
const ITEM_TYPE_WEAPON := 1
const ITEM_TYPE_ARMOR := 2
const ITEM_TYPE_COSTUME := 28
const ITEM_TYPE_RING := 33
const ITEM_TYPE_BELT := 34
const ARMOR_BODY := 0
const ARMOR_HEAD := 1
const ARMOR_SHIELD := 2
const ARMOR_WRIST := 3
const ARMOR_FOOTS := 4
const ARMOR_NECK := 5
const ARMOR_EAR := 6
const COSTUME_BODY := 0
const COSTUME_HAIR := 1
# --- 职业 / 性别 <- racecommon length.h `MAIN_RACE_*`job = race % 4;性别见下表)---
const JOB_WARRIOR := 0
const JOB_ASSASSIN := 1
const JOB_SURA := 2
const JOB_SHAMAN := 3
const SEX_MALE := 0
const SEX_FEMALE := 1
# race 0..7 = WARRIOR_M / ASSASSIN_W / SURA_M / SHAMAN_W / WARRIOR_W / ASSASSIN_M / SURA_W / SHAMAN_M
const SEX_BY_RACE := [SEX_MALE, SEX_FEMALE, SEX_MALE, SEX_FEMALE, SEX_FEMALE, SEX_MALE, SEX_FEMALE, SEX_MALE]
const JOB_ANTIFLAG := [ANTIFLAG_WARRIOR, ANTIFLAG_ASSASSIN, ANTIFLAG_SURA, ANTIFLAG_SHAMAN]
# 帝国 1/2/3GC_EMPIRE)→ 对应 anti 位
const EMPIRE_ANTIFLAG := {1: ANTIFLAG_EMPIRE_A, 2: ANTIFLAG_EMPIRE_B, 3: ANTIFLAG_EMPIRE_R}
static func sex_of_race(race: int) -> int:
return int(SEX_BY_RACE[race]) if race >= 0 and race < SEX_BY_RACE.size() else SEX_MALE
static func job_of_race(race: int) -> int:
return (race % 4) if race >= 0 else JOB_WARRIOR
# 物品默认落在哪个 wear 槽(type / sub_type 决定)。不可穿戴 → -1。
static func default_wear_for(item: Dictionary) -> int:
var itype := int(item.get("type", -1))
var sub := int(item.get("sub_type", -1))
match itype:
ITEM_TYPE_WEAPON:
return WEAR_WEAPON
ITEM_TYPE_COSTUME:
return WEAR_COSTUME_HAIR if sub == COSTUME_HAIR else WEAR_COSTUME_BODY
ITEM_TYPE_RING:
return WEAR_RING1
ITEM_TYPE_BELT:
return WEAR_BELT
ITEM_TYPE_ARMOR:
match sub:
ARMOR_HEAD: return WEAR_HEAD
ARMOR_SHIELD: return WEAR_SHIELD
ARMOR_WRIST: return WEAR_WRIST
ARMOR_FOOTS: return WEAR_FOOTS
ARMOR_NECK: return WEAR_NECK
ARMOR_EAR: return WEAR_EAR
_: return WEAR_BODY
return -1
static func is_equippable(item: Dictionary) -> bool:
return default_wear_for(item) >= 0
# wear 槽 → 需要的 wearable_flag 位(新装备槽 19..23 无 wearable_flag → 0)。
static func wear_to_wearable(wear: int) -> int:
match wear:
WEAR_BODY: return WEARABLE_BODY
WEAR_HEAD: return WEARABLE_HEAD
WEAR_FOOTS: return WEARABLE_FOOTS
WEAR_WRIST: return WEARABLE_WRIST
WEAR_WEAPON: return WEARABLE_WEAPON
WEAR_NECK: return WEARABLE_NECK
WEAR_EAR: return WEARABLE_EAR
WEAR_UNIQUE1, WEAR_UNIQUE2: return WEARABLE_UNIQUE
WEAR_ARROW: return WEARABLE_ARROW
WEAR_SHIELD: return WEARABLE_SHIELD
return 0
# 目标槽能否接受这件物品:新装备槽按 item type,旧 11 槽按 wearable_flag 位。
static func slot_accepts(item: Dictionary, wear: int) -> bool:
var itype := int(item.get("type", -1))
match wear:
WEAR_COSTUME_BODY, WEAR_COSTUME_HAIR:
return itype == ITEM_TYPE_COSTUME
WEAR_RING1, WEAR_RING2:
return itype == ITEM_TYPE_RING
WEAR_BELT:
return itype == ITEM_TYPE_BELT
var need := wear_to_wearable(wear)
if need == 0:
return false
return (int(item.get("wear_flags", 0)) & need) != 0
static func _fail(code: String, need := 0) -> Dictionary:
return {"ok": false, "code": code, "need": need}
# 主校验。
# item : proto 字典 —— {type, sub_type, anti_flags, wear_flags, limits:[{type,value}]}
# wear : 目标 EWearPositions(或新装备槽 19..23);传 -1 用 default_wear_for(item)
# ctx : {race, sex, job, empire, level, st, dx, ht, iq}
# —— sex/job 缺省时由 race 推;属性 st/dx/ht/iq 缺省或 < 0 = 不判(seam
# 返回 {ok: bool, code: String, need: int}。code 见下方常量注释;need = 失败 limit 的要求值。
static func can_equip(item: Dictionary, wear: int, ctx: Dictionary) -> Dictionary:
if item.is_empty():
return _fail("NO_ITEM")
var target := wear if wear >= 0 else default_wear_for(item)
if target < 0:
return _fail("NOT_EQUIPPABLE")
var anti := int(item.get("anti_flags", 0))
var race := int(ctx.get("race", 0))
var sex := int(ctx.get("sex", sex_of_race(race)))
var job := int(ctx.get("job", job_of_race(race)))
var empire := int(ctx.get("empire", 0))
# 1) anti-flag:性别
if sex == SEX_FEMALE and (anti & ANTIFLAG_FEMALE):
return _fail("ANTI_SEX")
if sex == SEX_MALE and (anti & ANTIFLAG_MALE):
return _fail("ANTI_SEX")
# 2) anti-flag:职业
if job >= 0 and job < JOB_ANTIFLAG.size() and (anti & int(JOB_ANTIFLAG[job])):
return _fail("ANTI_JOB")
# 3) anti-flag:帝国
var emp_bit := int(EMPIRE_ANTIFLAG.get(empire, 0))
if emp_bit != 0 and (anti & emp_bit):
return _fail("ANTI_EMPIRE")
# 4) wearable_flag 与目标槽匹配
if not slot_accepts(item, target):
return _fail("NOT_WEARABLE_HERE")
# 5) limit(等级 / 属性)—— 逐条 GetLimit
for lim in item.get("limits", []):
var t := int(lim.get("type", 0))
var v := int(lim.get("value", 0))
if v <= 0:
continue
match t:
LIMIT_LEVEL:
if int(ctx.get("level", 0)) < v:
return _fail("LIMIT_LEVEL", v)
LIMIT_STR:
if _stat(ctx, "st") >= 0 and _stat(ctx, "st") < v:
return _fail("LIMIT_STR", v)
LIMIT_DEX:
if _stat(ctx, "dx") >= 0 and _stat(ctx, "dx") < v:
return _fail("LIMIT_DEX", v)
LIMIT_INT:
if _stat(ctx, "iq") >= 0 and _stat(ctx, "iq") < v:
return _fail("LIMIT_INT", v)
LIMIT_CON:
if _stat(ctx, "ht") >= 0 and _stat(ctx, "ht") < v:
return _fail("LIMIT_CON", v)
_:
pass # PCBANG / REAL_TIME* → 服务器判(seam
return {"ok": true, "code": "", "need": 0}
static func _stat(ctx: Dictionary, key: String) -> int:
return int(ctx.get(key, -1))