262 lines
9.6 KiB
GDScript
262 lines
9.6 KiB
GDScript
# entity_rules.gd —— CInstanceBase::IsAttackableInstance 及其判据的 1:1 迁移(§5.4)。
|
||
#
|
||
# 参考(REF/ = /Users/shenlei/Work/mt/40250/ClientVS22/source/):
|
||
# UserInterface/InstanceBase.cpp:2147 IsAttackableInstance —— 唯一判据,逐行照抄
|
||
# UserInterface/InstanceBase.cpp:2116 IsConflictAlignmentInstance
|
||
# UserInterface/InstanceBase.cpp:470 IsSameEmpire
|
||
# UserInterface/InstanceBase.cpp:502 GetAlignmentGrade
|
||
# UserInterface/InstanceBase.cpp:524 GetAlignmentType
|
||
# UserInterface/InstanceBase.cpp:2059 SetPKMode / :2073 SetKiller / :2086 SetStateFlags
|
||
# UserInterface/InstanceBaseEffect.cpp:495 __FindDUELKey
|
||
# UserInterface/InstanceBaseEffect.cpp:505 IsPVPInstance
|
||
# UserInterface/PythonPlayer.cpp:1445 IsSamePartyMember
|
||
# UserInterface/InstanceBase.cpp:2390 IsWoodenDoor(vnum 13000 / 30111..30119)
|
||
#
|
||
# 纯静态谓词。调用方(net_play.gd)负责把网络快照拼成 self_e / victim_e / ctx;
|
||
# 部分依赖在本 40250 客户端的发布源码里没有 setter(同 §3.4/§3.6/§3.8 的处理),
|
||
# 在下方逐条标注为 seam。
|
||
extends RefCounted
|
||
|
||
# EPKModes(REF/UserInterface/Packet.h:1273)
|
||
const PK_MODE_PEACE := 0
|
||
const PK_MODE_REVENGE := 1
|
||
const PK_MODE_FREE := 2
|
||
const PK_MODE_PROTECT := 3
|
||
const PK_MODE_GUILD := 4
|
||
|
||
# alignment type(REF/UserInterface/InstanceBase.h:249)
|
||
const ALIGNMENT_TYPE_WHITE := 0
|
||
const ALIGNMENT_TYPE_NORMAL := 1
|
||
const ALIGNMENT_TYPE_DARK := 2
|
||
|
||
# duel mode(REF/UserInterface/InstanceBase.h:378,GetDuelMode 返回 m_dwDuelMode)
|
||
const DUEL_NONE := 0
|
||
const DUEL_CANNOTATTACK := 1
|
||
const DUEL_START := 2
|
||
|
||
# ADD_CHARACTER_STATE_*(REF/UserInterface/Packet.h:1264)—— SetStateFlags 从 state_flags
|
||
# 位里取 killer / party(InstanceBase.cpp:2086)。
|
||
const STATE_KILLER := 1 << 3
|
||
const STATE_PARTY := 1 << 4
|
||
|
||
# _entity_kind 语义(net_play._entity_kind):0 PC / 1 NPC / 2 MONSTER / 3 STONE /
|
||
# 4 WARP / 5 BUILDING / 6 WOODEN_DOOR。
|
||
const KIND_PC := 0
|
||
const KIND_NPC := 1
|
||
const KIND_MONSTER := 2
|
||
const KIND_STONE := 3
|
||
const KIND_WARP := 4
|
||
const KIND_BUILDING := 5
|
||
const KIND_WOODEN_DOOR := 6
|
||
|
||
# --- alignment(善恶)---------------------------------------------------------
|
||
|
||
# CInstanceBase::GetAlignmentGrade(InstanceBase.cpp:502)
|
||
static func alignment_grade(a: int) -> int:
|
||
if a >= 12000: return 0
|
||
elif a >= 8000: return 1
|
||
elif a >= 4000: return 2
|
||
elif a >= 1000: return 3
|
||
elif a >= 0: return 4
|
||
elif a > -4000: return 5
|
||
elif a > -8000: return 6
|
||
elif a > -12000: return 7
|
||
return 8
|
||
|
||
# CInstanceBase::GetAlignmentType(InstanceBase.cpp:524)
|
||
static func alignment_type(a: int) -> int:
|
||
match alignment_grade(a):
|
||
0, 1, 2, 3:
|
||
return ALIGNMENT_TYPE_WHITE
|
||
5, 6, 7, 8:
|
||
return ALIGNMENT_TYPE_DARK
|
||
return ALIGNMENT_TYPE_NORMAL
|
||
|
||
# --- 类别谓词(GraphicThingInstance 的 type 位,见 net_play._entity_kind)---------
|
||
|
||
static func _kind(e: Dictionary) -> int:
|
||
return int(e.get("kind", e.get("ch_type", 0)))
|
||
|
||
static func _is_poly(e: Dictionary) -> bool:
|
||
# m_GraphicThingInstance.IsPoly()(变身:模型换成怪,thing type 变 ENEMY)
|
||
return bool(e.get("poly", false)) \
|
||
or int(e.get("polymorph", 0)) != 0 \
|
||
or int(e.get("poly_vnum", 0)) != 0
|
||
|
||
static func _is_pc(e: Dictionary) -> bool:
|
||
# CActorInstance::IsPC = (TYPE_PC == m_eActorType)(ActorInstance.cpp:282)。
|
||
# 变身中的玩家仍是 TYPE_PC —— IsPoly() 另经 race >= MAIN_RACE_MAX_NUM 判真,
|
||
# 但 IsPC() 不看 race,所以这里**不**排除 poly(否则 IsAttackableInstance 的
|
||
# `else if (IsPC())` 会漏掉变身玩家,与参考端不一致)。
|
||
return _kind(e) == KIND_PC
|
||
|
||
static func _is_stone(e: Dictionary) -> bool:
|
||
return _kind(e) == KIND_STONE
|
||
|
||
static func _is_enemy(e: Dictionary) -> bool:
|
||
# CActorInstance::IsEnemy = (TYPE_ENEMY == m_eActorType)(ActorInstance.cpp:298)——
|
||
# 仅普通怪。变身玩家是 TYPE_PC 不是 ENEMY;独立的 TYPE_POLY 实例归 _is_poly。
|
||
return _kind(e) == KIND_MONSTER
|
||
|
||
static func _is_wooden_door(e: Dictionary) -> bool:
|
||
# CInstanceBase::IsWoodenDoor(InstanceBase.cpp:2390)
|
||
if _kind(e) == KIND_WOODEN_DOOR:
|
||
return true
|
||
var vnum := int(e.get("race", e.get("vnum", 0)))
|
||
return vnum == 13000 or (vnum >= 30111 and vnum <= 30119)
|
||
|
||
static func _is_building(e: Dictionary) -> bool:
|
||
# CInstanceBase::IsBuilding() = m_GraphicThingInstance.IsBuilding()。
|
||
# classic 的 bType / POC fixture 可直接给 kind;旧包仍可用 building 布尔字段。
|
||
return bool(e.get("building", false)) or _kind(e) == KIND_BUILDING \
|
||
or _kind(e) == KIND_WOODEN_DOOR
|
||
|
||
# --- killer / party / empire / duel / pvp -----------------------------------
|
||
|
||
static func _is_killer(e: Dictionary) -> bool:
|
||
# m_isKiller,由 SetStateFlags(state_flags & ADD_CHARACTER_STATE_KILLER) 置位。
|
||
return (int(e.get("state_flags", 0)) & STATE_KILLER) != 0
|
||
|
||
static func _is_same_party(a_vid: int, b_vid: int, ctx: Dictionary) -> bool:
|
||
# CPythonPlayer::IsSamePartyMember = IsPartyMemberByVID(a) && IsPartyMemberByVID(b)
|
||
var vids: Array = ctx.get("party_vids", [])
|
||
return a_vid in vids and b_vid in vids
|
||
|
||
static func _is_same_empire(self_e: Dictionary, victim_e: Dictionary) -> bool:
|
||
# CInstanceBase::IsSameEmpire(InstanceBase.cpp:470)
|
||
if int(victim_e.get("empire", 0)) == 0:
|
||
return true
|
||
# IsGameMaster() —— 本 POC 无 GM 标记 → seam(恒 false)。
|
||
if bool(self_e.get("game_master", false)) or bool(victim_e.get("game_master", false)):
|
||
return true
|
||
return int(victim_e.get("empire", 0)) == int(self_e.get("empire", 0))
|
||
|
||
static func _find_duel_key(self_vid: int, victim_vid: int, ctx: Dictionary) -> bool:
|
||
# CInstanceBase::__FindDUELKey(InstanceBaseEffect.cpp:495)—— g_kSet_dwDUELKey 里
|
||
# 有 MAKE_PVPKEY(min,max) 才算。本 POC 的 get_duel().opponents 是主角的对手 VID 列表。
|
||
var opp: Array = ctx.get("duel_opponents", [])
|
||
return victim_vid in opp or self_vid in opp
|
||
|
||
static func _pair_in(pairs: Array, a: int, b: int) -> bool:
|
||
for p in pairs:
|
||
if (int(p[0]) == a and int(p[1]) == b) or (int(p[0]) == b and int(p[1]) == a):
|
||
return true
|
||
return false
|
||
|
||
static func _is_pvp_instance(self_e: Dictionary, victim_e: Dictionary, ctx: Dictionary) -> bool:
|
||
# CInstanceBase::IsPVPInstance(InstanceBaseEffect.cpp:505):
|
||
# if (GetDuelMode()) return true;
|
||
# return __FindPVPKey(vidSrc, vidDst) || __FindGVGKey(guildSrc, guildDst);
|
||
if int(ctx.get("duel_mode", 0)) != 0:
|
||
return true
|
||
var sv := int(self_e.get("vid", 0))
|
||
var vv := int(victim_e.get("vid", 0))
|
||
if _pair_in(ctx.get("pvp_pairs", []), sv, vv):
|
||
return true
|
||
var sg := int(self_e.get("guild", 0))
|
||
var vg := int(victim_e.get("guild", 0))
|
||
if sg != 0 and vg != 0 and _pair_in(ctx.get("gvg_pairs", []), sg, vg):
|
||
return true
|
||
return false
|
||
|
||
static func is_conflict_alignment_instance(self_e: Dictionary, victim_e: Dictionary) -> bool:
|
||
# CInstanceBase::IsConflictAlignmentInstance(InstanceBase.cpp:2116)
|
||
if _pk_mode_of(victim_e) == PK_MODE_PROTECT:
|
||
return false
|
||
var mine := alignment_type(int(self_e.get("alignment", 0)))
|
||
var theirs := alignment_type(int(victim_e.get("alignment", 0)))
|
||
match mine:
|
||
ALIGNMENT_TYPE_NORMAL, ALIGNMENT_TYPE_WHITE:
|
||
if theirs == ALIGNMENT_TYPE_DARK:
|
||
return true
|
||
ALIGNMENT_TYPE_DARK:
|
||
if mine != theirs:
|
||
return true
|
||
return false
|
||
|
||
static func _pk_mode_of(e: Dictionary) -> int:
|
||
return int(e.get("pk_mode", 0))
|
||
|
||
# --- CInstanceBase::IsAttackableInstance(InstanceBase.cpp:2147)—— 逐行 -----------
|
||
|
||
static func is_attackable_instance(self_e: Dictionary, victim_e: Dictionary, ctx: Dictionary) -> bool:
|
||
var sv := int(self_e.get("vid", 0))
|
||
var vv := int(victim_e.get("vid", -1))
|
||
|
||
# if (__IsMainInstance()) { if (rkPlayer.IsObserverMode()) return false; }
|
||
if sv == int(ctx.get("main_vid", 0)) and bool(ctx.get("observer", false)):
|
||
return false
|
||
|
||
# if (GetVirtualID() == rkInstVictim.GetVirtualID()) return false;
|
||
if sv == vv:
|
||
return false
|
||
|
||
var self_pk := int(ctx.get("pk_mode", 0))
|
||
|
||
if _is_stone(self_e):
|
||
if _is_pc(victim_e):
|
||
return true
|
||
elif _is_pc(self_e):
|
||
if _is_stone(victim_e):
|
||
return true
|
||
|
||
if _is_pc(victim_e):
|
||
var duel := int(ctx.get("duel_mode", 0))
|
||
if duel != 0:
|
||
match duel:
|
||
DUEL_CANNOTATTACK:
|
||
return false
|
||
DUEL_START:
|
||
return _find_duel_key(sv, vv, ctx)
|
||
|
||
# if (PK_MODE_GUILD == GetPKMode()) if (GetGuildID() == victim.GetGuildID()) return false;
|
||
if self_pk == PK_MODE_GUILD:
|
||
if int(self_e.get("guild", 0)) == int(victim_e.get("guild", 0)):
|
||
return false
|
||
|
||
# if (victim.IsKiller()) if (!IsSamePartyMember(...)) return true;
|
||
if _is_killer(victim_e):
|
||
if not _is_same_party(sv, vv, ctx):
|
||
return true
|
||
|
||
# if (PK_MODE_PROTECT != GetPKMode()) { ... }
|
||
if self_pk != PK_MODE_PROTECT:
|
||
if self_pk == PK_MODE_FREE:
|
||
if _pk_mode_of(victim_e) != PK_MODE_PROTECT:
|
||
if not _is_same_party(sv, vv, ctx):
|
||
return true
|
||
if self_pk == PK_MODE_GUILD:
|
||
if _pk_mode_of(victim_e) != PK_MODE_PROTECT:
|
||
if not _is_same_party(sv, vv, ctx):
|
||
if int(self_e.get("guild", 0)) != int(victim_e.get("guild", 0)):
|
||
return true
|
||
|
||
# if (IsSameEmpire(victim)) { ... } else { return true; }
|
||
if _is_same_empire(self_e, victim_e):
|
||
if _is_pvp_instance(self_e, victim_e, ctx):
|
||
return true
|
||
if self_pk == PK_MODE_REVENGE:
|
||
if not _is_same_party(sv, vv, ctx):
|
||
if is_conflict_alignment_instance(self_e, victim_e):
|
||
return true
|
||
else:
|
||
return true
|
||
|
||
if _is_enemy(victim_e):
|
||
return true
|
||
|
||
if _is_wooden_door(victim_e):
|
||
return true
|
||
elif _is_enemy(self_e):
|
||
if _is_pc(victim_e):
|
||
return true
|
||
if _is_building(victim_e):
|
||
return true
|
||
elif _is_poly(self_e):
|
||
if _is_pc(victim_e):
|
||
return true
|
||
if _is_enemy(victim_e):
|
||
return true
|
||
|
||
return false
|