- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
193 lines
7.1 KiB
GDScript
193 lines
7.1 KiB
GDScript
# mining_refinery_smelt_system.gd —— Metin2 40250 矿石采掘熔炼与首饰宝石镶嵌提纯炉系统 1:1
|
|
# 对照 40250 服务端 mining.cpp, char_item.cpp, item_manager.cpp
|
|
class_name MiningRefinerySmeltSystem
|
|
extends RefCounted
|
|
|
|
signal ore_smelted(success: bool, gem_vnum: int, gem_name: String)
|
|
signal jewelry_socket_opened(slot: int, socket_index: int)
|
|
signal gem_embedded(slot: int, gem_name: String, stat_boost: float)
|
|
|
|
const SMELT_ORE_COUNT := 100 # 熔炼需 100 颗原石
|
|
const SMELT_COST_GOLD := 10000 # 熔炼消耗 10,000 金币
|
|
const MAX_JEWELRY_SOCKETS := 3 # 首饰最多开 3 个孔
|
|
|
|
# 原石与精炼宝石对照表
|
|
const SMELT_TABLE: Dictionary = {
|
|
50621: {"name": "钻石原石", "gem_vnum": 50634, "gem_name": "纯净钻石", "is_diamond": true},
|
|
50623: {"name": "乌木原石", "gem_vnum": 50636, "gem_name": "乌木精炼石", "is_diamond": false},
|
|
50626: {"name": "黄金原石", "gem_vnum": 50639, "gem_name": "黄金精炼石", "is_diamond": false},
|
|
50628: {"name": "白金原石", "gem_vnum": 50641, "gem_name": "白金精炼石", "is_diamond": false},
|
|
50630: {"name": "天之泪原石", "gem_vnum": 50643, "gem_name": "天之泪精炼石", "is_diamond": false}
|
|
}
|
|
|
|
# 熔炼原石生成首饰宝石
|
|
func smelt_ore(ore_slot: int, stone_slot: int, inventory: Array, player_data: Dictionary, is_guild_alchemist: bool = false, force_success: bool = false) -> Dictionary:
|
|
if ore_slot < 0 or ore_slot >= inventory.size() or inventory[ore_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_ORE_SLOT"}
|
|
if stone_slot < 0 or stone_slot >= inventory.size() or inventory[stone_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_STONE_SLOT"}
|
|
|
|
var ore_item = inventory[ore_slot]
|
|
var stone_item = inventory[stone_slot]
|
|
|
|
var ore_vnum = int(ore_item.get("vnum", 0))
|
|
if not SMELT_TABLE.has(ore_vnum):
|
|
return {"ok": false, "reason": "NOT_AN_ORE", "msg": "该物品不是可熔炼的原矿石!"}
|
|
|
|
var ore_count = int(ore_item.get("count", 1))
|
|
if ore_count < SMELT_ORE_COUNT:
|
|
return {"ok": false, "reason": "NOT_ENOUGH_ORE", "msg": "原矿数量不足!需要 100 颗,当前仅有 %d 颗。" % ore_count}
|
|
|
|
var gold = int(player_data.get("gold", 0))
|
|
if gold < SMELT_COST_GOLD:
|
|
return {"ok": false, "reason": "NOT_ENOUGH_GOLD", "msg": "熔炼所需金币不足!需要 10,000 金币。"}
|
|
|
|
# 消耗材料与金币
|
|
player_data["gold"] = gold - SMELT_COST_GOLD
|
|
if ore_count > SMELT_ORE_COUNT:
|
|
ore_item["count"] = ore_count - SMELT_ORE_COUNT
|
|
else:
|
|
inventory[ore_slot] = null
|
|
|
|
var stone_cnt = int(stone_item.get("count", 1))
|
|
if stone_cnt > 1:
|
|
stone_item["count"] = stone_cnt - 1
|
|
else:
|
|
inventory[stone_slot] = null
|
|
|
|
# 成功率计算 (普通提纯炉 60%,公会炼金炉 70%)
|
|
var rate = 70 if is_guild_alchemist else 60
|
|
var roll = randi_range(1, 100)
|
|
var is_succ = force_success or (roll <= rate)
|
|
|
|
var target_gem = SMELT_TABLE[ore_vnum]
|
|
var gem_vnum = target_gem["gem_vnum"]
|
|
var gem_name = target_gem["gem_name"]
|
|
|
|
if is_succ:
|
|
# 入包精炼宝石
|
|
_add_gem_to_inventory(gem_vnum, gem_name, inventory)
|
|
ore_smelted.emit(true, gem_vnum, gem_name)
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"gem_vnum": gem_vnum,
|
|
"gem_name": gem_name,
|
|
"msg": "提纯炉烈焰喷涌!成功熔炼出高纯度【%s】!" % gem_name
|
|
}
|
|
else:
|
|
ore_smelted.emit(false, gem_vnum, gem_name)
|
|
return {
|
|
"ok": true,
|
|
"success": false,
|
|
"msg": "熔炼失败!火候不纯,矿石与灵石化为了灰烬。"
|
|
}
|
|
|
|
# 使用纯净钻石 (50634) 为首饰打孔
|
|
func open_jewelry_socket(diamond_slot: int, target_slot: int, inventory: Array, force_success: bool = false) -> Dictionary:
|
|
if diamond_slot < 0 or diamond_slot >= inventory.size() or inventory[diamond_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_DIAMOND_SLOT"}
|
|
if target_slot < 0 or target_slot >= inventory.size() or inventory[target_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_TARGET_SLOT"}
|
|
|
|
var d_item = inventory[diamond_slot]
|
|
var target = inventory[target_slot]
|
|
|
|
if int(d_item.get("vnum", 0)) != 50634:
|
|
return {"ok": false, "reason": "NOT_A_DIAMOND", "msg": "打孔需要使用【纯净钻石】(50634)!"}
|
|
|
|
if not target.has("jewelry_sockets"):
|
|
target["jewelry_sockets"] = []
|
|
|
|
var sockets: Array = target["jewelry_sockets"]
|
|
if sockets.size() >= MAX_JEWELRY_SOCKETS:
|
|
return {"ok": false, "reason": "MAX_SOCKETS_REACHED", "msg": "该首饰已开辟 3 个宝石孔位,达到上限!"}
|
|
|
|
# 消耗 1 颗钻石
|
|
var cnt = int(d_item.get("count", 1))
|
|
if cnt > 1:
|
|
d_item["count"] = cnt - 1
|
|
else:
|
|
inventory[diamond_slot] = null
|
|
|
|
# 阶梯成功率: 第 1 孔 100%,第 2 孔 80%,第 3 孔 60%
|
|
var rates = [100, 80, 60]
|
|
var rate = rates[sockets.size()]
|
|
var roll = randi_range(1, 100)
|
|
var is_succ = force_success or (roll <= rate)
|
|
|
|
if not is_succ:
|
|
return {"ok": true, "success": false, "msg": "打孔失败!钻石碎裂,未能开辟新孔位。"}
|
|
|
|
sockets.append(null) # 开辟一个空孔位
|
|
jewelry_socket_opened.emit(target_slot, sockets.size())
|
|
|
|
return {
|
|
"ok": true,
|
|
"success": true,
|
|
"socket_index": sockets.size(),
|
|
"total_sockets": sockets.size(),
|
|
"msg": "打孔成功!首饰开辟了第 %d 个宝石镶嵌孔位!" % sockets.size()
|
|
}
|
|
|
|
# 将精炼宝石镶入首饰已开孔位
|
|
func embed_gem_into_socket(gem_slot: int, target_slot: int, inventory: Array) -> Dictionary:
|
|
if gem_slot < 0 or gem_slot >= inventory.size() or inventory[gem_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_GEM_SLOT"}
|
|
if target_slot < 0 or target_slot >= inventory.size() or inventory[target_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_TARGET_SLOT"}
|
|
|
|
var gem_item = inventory[gem_slot]
|
|
var target = inventory[target_slot]
|
|
|
|
if not target.has("jewelry_sockets") or target["jewelry_sockets"].is_empty():
|
|
return {"ok": false, "reason": "NO_SOCKETS", "msg": "该首饰尚未开孔!请先使用纯净钻石打孔。"}
|
|
|
|
var sockets: Array = target["jewelry_sockets"]
|
|
var empty_idx = -1
|
|
for i in range(sockets.size()):
|
|
if sockets[i] == null:
|
|
empty_idx = i
|
|
break
|
|
|
|
if empty_idx == -1:
|
|
return {"ok": false, "reason": "ALL_SOCKETS_OCCUPIED", "msg": "所有宝石孔位已镶满!"}
|
|
|
|
var gem_vnum = int(gem_item.get("vnum", 0))
|
|
var gem_name = gem_item.get("name", "宝石")
|
|
|
|
# 消耗 1 颗宝石
|
|
var cnt = int(gem_item.get("count", 1))
|
|
if cnt > 1:
|
|
gem_item["count"] = cnt - 1
|
|
else:
|
|
inventory[gem_slot] = null
|
|
|
|
# 镶入孔位 (每颗宝石提供 +10% 首饰属性增强)
|
|
sockets[empty_idx] = {
|
|
"vnum": gem_vnum,
|
|
"name": gem_name,
|
|
"stat_boost": 0.10
|
|
}
|
|
|
|
gem_embedded.emit(target_slot, gem_name, 0.10)
|
|
|
|
return {
|
|
"ok": true,
|
|
"socket_idx": empty_idx,
|
|
"gem_name": gem_name,
|
|
"stat_boost": 0.10,
|
|
"msg": "【%s】完美嵌入第 %d 孔!激活 +10%% 首饰属性加成!" % [gem_name, empty_idx + 1]
|
|
}
|
|
|
|
func _add_gem_to_inventory(vnum: int, iname: String, inventory: Array) -> void:
|
|
for i in range(inventory.size()):
|
|
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == vnum:
|
|
inventory[i]["count"] = int(inventory[i].get("count", 1)) + 1
|
|
return
|
|
|
|
for i in range(inventory.size()):
|
|
if inventory[i] == null:
|
|
inventory[i] = {"vnum": vnum, "name": iname, "count": 1}
|
|
return
|