完善客户端功能并同步差距文档

This commit is contained in:
shenlei
2026-09-03 18:40:01 +09:00
parent f93a172296
commit 13ccb8d02d
135 changed files with 21881 additions and 1259 deletions
+255
View File
@@ -0,0 +1,255 @@
# 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 IsWoodenDoorvnum 13000 / 30111..30119
#
# 纯静态谓词。调用方(net_play.gd)负责把网络快照拼成 self_e / victim_e / ctx
# 部分依赖在本 40250 客户端的发布源码里没有 setter(同 §3.4/§3.6/§3.8 的处理),
# 在下方逐条标注为 seam。
extends RefCounted
# EPKModesREF/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 typeREF/UserInterface/InstanceBase.h:249
const ALIGNMENT_TYPE_WHITE := 0
const ALIGNMENT_TYPE_NORMAL := 1
const ALIGNMENT_TYPE_DARK := 2
# duel modeREF/UserInterface/InstanceBase.h:378GetDuelMode 返回 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 / partyInstanceBase.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
const KIND_PC := 0
const KIND_NPC := 1
const KIND_MONSTER := 2
const KIND_STONE := 3
const KIND_WARP := 4
# --- alignment(善恶)---------------------------------------------------------
# CInstanceBase::GetAlignmentGradeInstanceBase.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::GetAlignmentTypeInstanceBase.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::IsWoodenDoorInstanceBase.cpp:2390
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()。
# 建筑 / 城墙的 thing type 在本 POC 未单独下发(IsWall 的 race 白名单未随源码发布)→ seam。
return bool(e.get("building", false))
# --- 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::IsSameEmpireInstanceBase.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::__FindDUELKeyInstanceBaseEffect.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::IsPVPInstanceInstanceBaseEffect.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::IsConflictAlignmentInstanceInstanceBase.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::IsAttackableInstanceInstanceBase.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