- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
176 lines
6.3 KiB
GDScript
176 lines
6.3 KiB
GDScript
# jewelry_socket_system.gd —— Metin2 40250 首饰饰品金刚石打孔与精炼宝石镶嵌 1:1
|
||
# 对照 40250 服务端 char_item.cpp (PutGemInAccessory), item_attribute.cpp
|
||
class_name JewelrySocketSystem
|
||
extends RefCounted
|
||
|
||
const ITEM_DIAMOND := 50621 # 精炼金刚石
|
||
const MAX_SOCKETS := 3
|
||
const GEM_LIFETIME_SECS := 86400.0 # 24 小时战斗佩戴寿命
|
||
|
||
# 饰品支持打孔的分类 (14:手镯, 16:项链, 17:耳环)
|
||
const VALID_ACCESSORY_PREFIXES := [14, 16, 17]
|
||
|
||
# 饰品材质与对应精炼宝石映射 (base_vnum_pattern -> gem_vnum)
|
||
const ACCESSORY_GEM_MAPPING: Dictionary = {
|
||
"copper": 50624, # 铜首饰 -> 铜 50624
|
||
"silver": 50625, # 银首饰 -> 银 50625
|
||
"gold": 50626, # 金首饰 -> 金 50626
|
||
"jade": 50627, # 玉首饰 -> 玉 50627
|
||
"ebony": 50628, # 黑檀首饰 -> 黑檀 50628
|
||
"pearl": 50629, # 珍珠首饰 -> 珍珠 50629
|
||
"white_gold": 50630, # 白金首饰 -> 白金 50630
|
||
"crystal": 50631, # 水晶首饰 -> 水晶 50631
|
||
"amethyst": 50632, # 紫晶首饰 -> 紫晶 50632
|
||
"heavens_tear": 50633,# 天泪首饰 -> 天泪石 50633
|
||
"soul_crystal": 50634,# 灵魂水晶首饰 -> 灵魂水晶 50634
|
||
"ruby": 50635, # 红宝石首饰 -> 红宝石 50635
|
||
"garnet": 50636, # 石榴石首饰 -> 石榴石 50636
|
||
"emerald": 50637, # 绿宝石首饰 -> 绿宝石 50637
|
||
"sapphire": 50638 # 蓝宝石首饰 -> 蓝宝石 50638
|
||
}
|
||
|
||
# 打孔成功率 (0孔打第1孔: 70%, 1孔打第2孔: 50%, 2孔打第3孔: 30%)
|
||
const DRILL_CHANCES: Array[float] = [0.70, 0.50, 0.30]
|
||
|
||
# 宝石阶梯增益百分比 (第1孔: +10%, 第2孔: +10%, 第3孔: +20%)
|
||
const GEM_BONUS_TIERS: Array[float] = [0.10, 0.10, 0.20]
|
||
|
||
static func is_accessory(item: Dictionary) -> bool:
|
||
var vnum: int = int(item.get("vnum", 0))
|
||
var prefix: int = int(floor(float(vnum) / 1000.0))
|
||
return prefix in VALID_ACCESSORY_PREFIXES
|
||
|
||
# 检查是否可用金刚石打孔
|
||
static func can_drill_socket(item: Dictionary) -> Dictionary:
|
||
if not is_accessory(item):
|
||
return {"ok": false, "reason": "NOT_ACCESSORY", "msg": "金刚石只能用于手镯、项链或耳环打孔!"}
|
||
|
||
var socket_count: int = int(item.get("socket_count", 0))
|
||
if socket_count >= MAX_SOCKETS:
|
||
return {"ok": false, "reason": "SOCKETS_FULL", "msg": "该首饰已拥有最大的 3 个宝石孔槽!"}
|
||
|
||
return {"ok": true, "current_sockets": socket_count}
|
||
|
||
# 使用金刚石 50621 打孔
|
||
static func drill_socket(item: Dictionary, diamond_slot_item: Dictionary, force_success: bool = false) -> Dictionary:
|
||
var check = can_drill_socket(item)
|
||
if not check["ok"]:
|
||
return check
|
||
|
||
var dvnum: int = int(diamond_slot_item.get("vnum", 0))
|
||
if dvnum != ITEM_DIAMOND:
|
||
return {"ok": false, "reason": "NOT_DIAMOND", "msg": "打孔必须使用精炼金刚石!"}
|
||
|
||
var current_sockets: int = int(item.get("socket_count", 0))
|
||
var chance: float = DRILL_CHANCES[current_sockets]
|
||
var roll: float = 0.0 if force_success else randf()
|
||
var success: bool = roll < chance
|
||
|
||
if not item.has("sockets"):
|
||
item["sockets"] = [0, 0, 0]
|
||
if not item.has("gem_times"):
|
||
item["gem_times"] = [0.0, 0.0, 0.0]
|
||
|
||
if success:
|
||
item["socket_count"] = current_sockets + 1
|
||
return {
|
||
"ok": true,
|
||
"success": true,
|
||
"socket_count": item["socket_count"],
|
||
"msg": "打孔成功!已开辟出第 %d 个宝石孔槽!" % item["socket_count"]
|
||
}
|
||
else:
|
||
return {
|
||
"ok": true,
|
||
"success": false,
|
||
"socket_count": current_sockets,
|
||
"msg": "打孔失败!金刚石破碎,未能开出孔槽。"
|
||
}
|
||
|
||
# 镶嵌精炼矿石宝石
|
||
static func insert_gem(item: Dictionary, gem_item: Dictionary, required_gem_vnum: int = 0) -> Dictionary:
|
||
if not is_accessory(item):
|
||
return {"ok": false, "reason": "NOT_ACCESSORY"}
|
||
|
||
var socket_count: int = int(item.get("socket_count", 0))
|
||
if socket_count <= 0:
|
||
return {"ok": false, "reason": "NO_SOCKETS_DRILLED", "msg": "该首饰尚未打孔,请先使用金刚石开孔!"}
|
||
|
||
if not item.has("sockets"):
|
||
item["sockets"] = [0, 0, 0]
|
||
if not item.has("gem_times"):
|
||
item["gem_times"] = [0.0, 0.0, 0.0]
|
||
|
||
# 查找第一个空孔
|
||
var empty_slot := -1
|
||
for i in range(socket_count):
|
||
if int(item["sockets"][i]) == 0:
|
||
empty_slot = i
|
||
break
|
||
|
||
if empty_slot == -1:
|
||
return {"ok": false, "reason": "NO_EMPTY_SOCKET", "msg": "所有已开辟的孔槽都已镶嵌满宝石!"}
|
||
|
||
var gem_vnum: int = int(gem_item.get("vnum", 0))
|
||
if gem_vnum < 50622 or gem_vnum > 50638:
|
||
return {"ok": false, "reason": "INVALID_GEM", "msg": "这不是有效的精炼宝石!"}
|
||
|
||
if required_gem_vnum > 0 and gem_vnum != required_gem_vnum:
|
||
return {"ok": false, "reason": "GEM_MISMATCH", "msg": "此宝石与该首饰材质不匹配!"}
|
||
|
||
# 镶嵌成功
|
||
item["sockets"][empty_slot] = gem_vnum
|
||
item["gem_times"][empty_slot] = GEM_LIFETIME_SECS
|
||
|
||
var active_gems := count_active_gems(item)
|
||
var bonus_pct := calculate_bonus_percentage(active_gems)
|
||
|
||
return {
|
||
"ok": true,
|
||
"slot": empty_slot,
|
||
"gem_vnum": gem_vnum,
|
||
"active_gems": active_gems,
|
||
"total_bonus_pct": bonus_pct,
|
||
"lifetime": GEM_LIFETIME_SECS,
|
||
"msg": "宝石成功镶入第 %d 个孔槽!固有属性提升 %d%%!" % [empty_slot + 1, int(round(bonus_pct * 100))]
|
||
}
|
||
|
||
# 统计当前生效的宝石数量
|
||
static func count_active_gems(item: Dictionary) -> int:
|
||
var count := 0
|
||
var sockets: Array = item.get("sockets", [])
|
||
var times: Array = item.get("gem_times", [])
|
||
for i in range(sockets.size()):
|
||
if int(sockets[i]) > 0 and float(times[i]) > 0.0:
|
||
count += 1
|
||
return count
|
||
|
||
# 计算宝石提供的总属性加成百分比 (0~3 颗宝石)
|
||
static func calculate_bonus_percentage(gem_count: int) -> float:
|
||
var pct: float = 0.0
|
||
for i in range(mini(gem_count, GEM_BONUS_TIERS.size())):
|
||
pct += GEM_BONUS_TIERS[i]
|
||
return pct
|
||
|
||
# 佩戴战斗时间自然衰减 (dt: 秒)
|
||
static func update_gem_endurance(item: Dictionary, dt: float) -> Dictionary:
|
||
var expired_gems: Array[int] = []
|
||
if not item.has("sockets") or not item.has("gem_times"):
|
||
return {"expired": expired_gems}
|
||
|
||
var sockets: Array = item["sockets"]
|
||
var times: Array = item["gem_times"]
|
||
|
||
for i in range(sockets.size()):
|
||
if int(sockets[i]) > 0 and float(times[i]) > 0.0:
|
||
times[i] = maxf(0.0, float(times[i]) - dt)
|
||
if float(times[i]) <= 0.0:
|
||
expired_gems.append(int(sockets[i]))
|
||
sockets[i] = 0 # 宝石破碎,孔槽重新清空
|
||
|
||
return {
|
||
"expired": expired_gems,
|
||
"active_gems": count_active_gems(item),
|
||
"total_bonus_pct": calculate_bonus_percentage(count_active_gems(item))
|
||
}
|