feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# pk_penalty_system.gd —— Metin2 40250 恶名红名掉落惩罚与露西之戒保护 1:1
|
||||
# 对照 40250 服务端 char_battle.cpp, char.cpp, Item 70043, 70102, 70048
|
||||
class_name PkPenaltySystem
|
||||
extends RefCounted
|
||||
|
||||
signal lucy_ring_protected()
|
||||
signal item_dropped_on_death(item: Dictionary)
|
||||
signal alignment_restored(new_alignment: int)
|
||||
|
||||
const MIN_PENALTY_LEVEL := 50
|
||||
const ITEM_LUCY_RING := 70043 # 露西之戒
|
||||
const ITEM_ZEN_BEAN := 70102 # 禅豆 (+500 善恶值洗恶名)
|
||||
const ITEM_CLOAK_OF_PEACE := 70048 # 和平披风 (隐匿红名)
|
||||
|
||||
# 负善恶值掉落几率表 (40250 char_battle.cpp 1:1)
|
||||
static func get_death_drop_chance(alignment: int) -> float:
|
||||
if alignment >= 0:
|
||||
return 0.0
|
||||
elif alignment >= -3999: # 凶恶
|
||||
return 0.05
|
||||
elif alignment >= -7999: # 歹毒
|
||||
return 0.10
|
||||
elif alignment >= -11999: # 恶贯满盈
|
||||
return 0.15
|
||||
else: # <= -12000 嗜血魔王
|
||||
return 0.25
|
||||
|
||||
# 死亡掉落综合判定
|
||||
func process_death_penalty(
|
||||
player_level: int,
|
||||
alignment: int,
|
||||
equipment: Dictionary,
|
||||
inventory: Array,
|
||||
forced_roll: float = -1.0
|
||||
) -> Dictionary:
|
||||
# 1. 50 级以下或非红名(>=0) 免除一切死亡掉落
|
||||
if player_level < MIN_PENALTY_LEVEL or alignment >= 0:
|
||||
return {
|
||||
"dropped": false,
|
||||
"reason": "SAFE_LEVEL_OR_POSITIVE_KARMA",
|
||||
"msg": "受新手保护或和平声望庇佑,免于死亡惩罚。"
|
||||
}
|
||||
|
||||
# 2. 检查是否佩戴露西之戒 (70043)
|
||||
var has_lucy_ring := false
|
||||
var ring_slot_key = ""
|
||||
for eq_key in equipment.keys():
|
||||
var eq = equipment[eq_key]
|
||||
if eq != null and int(eq.get("vnum", 0)) == ITEM_LUCY_RING:
|
||||
has_lucy_ring = true
|
||||
ring_slot_key = eq_key
|
||||
break
|
||||
|
||||
if has_lucy_ring:
|
||||
# 露西之戒替死碎裂,阻断掉落
|
||||
equipment[ring_slot_key] = null
|
||||
lucy_ring_protected.emit()
|
||||
return {
|
||||
"dropped": false,
|
||||
"ring_shattered": true,
|
||||
"msg": "【露西之戒】替你承受了死亡的厄运,戒指粉碎瓦解,身上物品完好无损!"
|
||||
}
|
||||
|
||||
# 3. 计算掉落几率
|
||||
var chance := get_death_drop_chance(alignment)
|
||||
var roll := forced_roll if forced_roll >= 0.0 else randf()
|
||||
|
||||
if roll > chance:
|
||||
return {
|
||||
"dropped": false,
|
||||
"reason": "LUCKY_ROLL",
|
||||
"msg": "幸运未触发掉落惩罚。"
|
||||
}
|
||||
|
||||
# 4. 寻找可掉落的物品 (已锁定的物品 is_locked 严禁掉落)
|
||||
var droppable_candidates: Array = []
|
||||
|
||||
# 从未锁定装备中搜寻
|
||||
for eq_key in equipment.keys():
|
||||
var it = equipment[eq_key]
|
||||
if it != null and not it.get("is_locked", false):
|
||||
droppable_candidates.append({"type": "equip", "key": eq_key, "item": it})
|
||||
|
||||
# 从未锁定背包中搜寻
|
||||
for i in range(inventory.size()):
|
||||
var it = inventory[i]
|
||||
if it != null and not it.get("is_locked", false):
|
||||
droppable_candidates.append({"type": "inv", "slot": i, "item": it})
|
||||
|
||||
if droppable_candidates.is_empty():
|
||||
return {
|
||||
"dropped": false,
|
||||
"reason": "ALL_ITEMS_PROTECTED",
|
||||
"msg": "身上的所有贵重物品均处于安全加锁保护中,未发生掉落!"
|
||||
}
|
||||
|
||||
# 掉落一件物品
|
||||
var chosen = droppable_candidates[randi() % droppable_candidates.size()]
|
||||
var dropped_item: Dictionary = chosen["item"].duplicate(true)
|
||||
|
||||
if chosen["type"] == "equip":
|
||||
equipment[chosen["key"]] = null
|
||||
else:
|
||||
inventory[chosen["slot"]] = null
|
||||
|
||||
item_dropped_on_death.emit(dropped_item)
|
||||
|
||||
return {
|
||||
"dropped": true,
|
||||
"dropped_item": dropped_item,
|
||||
"msg": "恶名昭彰!你被击杀,珍贵物品【%s】散落到了地面!" % dropped_item.get("name", "物品")
|
||||
}
|
||||
|
||||
# 使用禅豆 (70102) 消除 500 点恶名
|
||||
func use_zen_bean(
|
||||
slot_idx: int,
|
||||
inventory: Array,
|
||||
current_alignment: int
|
||||
) -> Dictionary:
|
||||
if slot_idx < 0 or slot_idx >= inventory.size():
|
||||
return {"ok": false, "reason": "INVALID_SLOT"}
|
||||
|
||||
var it = inventory[slot_idx]
|
||||
if it == null or int(it.get("vnum", 0)) != ITEM_ZEN_BEAN:
|
||||
return {"ok": false, "reason": "NOT_ZEN_BEAN", "msg": "这不是禅豆!"}
|
||||
|
||||
if current_alignment >= 0:
|
||||
return {
|
||||
"ok": false,
|
||||
"reason": "NOT_EVIL",
|
||||
"msg": "你的名声本就光明清白,无需服用禅豆洗去恶名。"
|
||||
}
|
||||
|
||||
# 消耗禅豆
|
||||
var cnt: int = int(it.get("count", 1))
|
||||
if cnt <= 1:
|
||||
inventory[slot_idx] = null
|
||||
else:
|
||||
it["count"] = cnt - 1
|
||||
|
||||
# 回复 500 点善恶值 (上限为 0)
|
||||
var new_alignment: int = mini(0, current_alignment + 500)
|
||||
alignment_restored.emit(new_alignment)
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"old_alignment": current_alignment,
|
||||
"new_alignment": new_alignment,
|
||||
"restored": 500,
|
||||
"msg": "服用了禅豆,心灵得到了净化,恶名值减少了 500 点!"
|
||||
}
|
||||
|
||||
# 判定和平披风 (70048) 是否隐匿红名
|
||||
func is_red_name_hidden(equipment: Dictionary) -> bool:
|
||||
for eq in equipment.values():
|
||||
if eq != null and int(eq.get("vnum", 0)) == ITEM_CLOAK_OF_PEACE:
|
||||
return true
|
||||
return false
|
||||
Reference in New Issue
Block a user