- 桥梁与静态物体高度采样修复: - 严格对齐 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
6.7 KiB
GDScript
193 lines
6.7 KiB
GDScript
# compass_treasure_hunt_system.gd —— Metin2 40250 官方魔石地脉探测罗盘与寻宝系统 1:1
|
|
# 对照 40250 服务端 questlua_item.cpp, locale_en/itemdesc.txt:27989
|
|
class_name CompassTreasureHuntSystem
|
|
extends RefCounted
|
|
|
|
signal compass_scanned(target_name: String, distance: float, angle_deg: float, proximity: String, charges_left: int)
|
|
signal treasure_excavated(chest_vnum: int, chest_name: String)
|
|
signal treasure_opened(chest_name: String, rewards: Array)
|
|
|
|
# 40250 官方核心道具 Vnum
|
|
const VNUM_COMPASS_OF_METIN := 27989 # 魔石探测罗盘 (Compass for Metin Stones, 6次充能)
|
|
const VNUM_GOLD_CHEST := 50005 # 黄金宝箱
|
|
const VNUM_SILVER_CHEST := 50006 # 白银宝箱
|
|
const VNUM_GOLD_KEY := 50007 # 黄金钥匙
|
|
const VNUM_SILVER_KEY := 50008 # 白银钥匙
|
|
|
|
const MAX_CHARGES := 6
|
|
|
|
# 探测距离等级
|
|
const PROXIMITY_FAR := "FAR" # > 100m ("微弱微光 - 遥远")
|
|
const PROXIMITY_MEDIUM := "MEDIUM" # 50m ~ 100m ("泛起银光 - 渐近")
|
|
const PROXIMITY_NEAR := "NEAR" # 20m ~ 50m ("强烈明光 - 附近")
|
|
const PROXIMITY_VERY_CLOSE := "VERY_CLOSE" # < 20m ("璀璨耀光 - 极近")
|
|
|
|
# 激活魔石探测罗盘扫描最近的魔石或地脉宝藏
|
|
func scan_nearest_metin(player_pos: Vector2, target_list: Array, inventory: Array, slot_index: int) -> Dictionary:
|
|
if slot_index < 0 or slot_index >= inventory.size() or inventory[slot_index] == null:
|
|
return {"ok": false, "reason": "INVALID_SLOT"}
|
|
|
|
var compass: Dictionary = inventory[slot_index]
|
|
if int(compass.get("vnum", 0)) != VNUM_COMPASS_OF_METIN:
|
|
return {"ok": false, "reason": "NOT_A_COMPASS", "msg": "该物品不是魔石探测罗盘!"}
|
|
|
|
var charges: int = int(compass.get("charges", MAX_CHARGES))
|
|
if charges <= 0:
|
|
inventory[slot_index] = null
|
|
return {"ok": false, "reason": "COMPASS_EXHAUSTED", "msg": "罗盘灵力已耗尽,化作飞灰!"}
|
|
|
|
if target_list.is_empty():
|
|
return {"ok": false, "reason": "NO_TARGETS_FOUND", "msg": "当前地图未探测到任何魔石波动!"}
|
|
|
|
# 寻找最近的目标
|
|
var nearest_target: Dictionary = {}
|
|
var min_dist := INF
|
|
for t in target_list:
|
|
var pos: Vector2 = t.get("pos", Vector2.ZERO)
|
|
var d := player_pos.distance_to(pos)
|
|
if d < min_dist:
|
|
min_dist = d
|
|
nearest_target = t
|
|
|
|
# 扣除 1 次充能计数 (40250 官方 6 次限制)
|
|
charges -= 1
|
|
compass["charges"] = charges
|
|
if charges <= 0:
|
|
inventory[slot_index] = null # 充能用完销毁
|
|
|
|
# 计算方位角与距离等级
|
|
var target_pos: Vector2 = nearest_target.get("pos", Vector2.ZERO)
|
|
var dir_vec: Vector2 = (target_pos - player_pos).normalized()
|
|
var angle := rad_to_deg(dir_vec.angle())
|
|
|
|
var proximity := PROXIMITY_FAR
|
|
var desc := "微弱微光(遥远)"
|
|
if min_dist < 20.0:
|
|
proximity = PROXIMITY_VERY_CLOSE
|
|
desc = "璀璨耀光(极近)"
|
|
elif min_dist < 50.0:
|
|
proximity = PROXIMITY_NEAR
|
|
desc = "强烈明光(附近)"
|
|
elif min_dist < 100.0:
|
|
proximity = PROXIMITY_MEDIUM
|
|
desc = "泛起银光(渐近)"
|
|
|
|
var target_name: String = str(nearest_target.get("name", "魔石"))
|
|
compass_scanned.emit(target_name, min_dist, angle, proximity, charges)
|
|
|
|
return {
|
|
"ok": true,
|
|
"target_name": target_name,
|
|
"target_pos": nearest_target.get("pos", Vector2.ZERO),
|
|
"distance": min_dist,
|
|
"angle_deg": angle,
|
|
"proximity": proximity,
|
|
"proximity_desc": desc,
|
|
"charges_left": charges,
|
|
"msg": "罗盘指针震颤!锁定【%s】,距离 %.1f 米,光芒等级:%s,剩余使用次数:%d。" % [target_name, min_dist, desc, charges]
|
|
}
|
|
|
|
# 在地脉极近坐标处挖掘秘宝
|
|
func excavate_treasure(player_pos: Vector2, treasure_nodes: Array, inventory: Array) -> Dictionary:
|
|
var target_idx := -1
|
|
for i in range(treasure_nodes.size()):
|
|
var node_pos: Vector2 = treasure_nodes[i].get("pos", Vector2.ZERO)
|
|
if player_pos.distance_to(node_pos) <= 5.0: # 5米范围内可挖掘
|
|
target_idx = i
|
|
break
|
|
|
|
if target_idx == -1:
|
|
return {"ok": false, "reason": "NO_TREASURE_NEARBY", "msg": "周围 5 米内未探测到地脉埋藏的宝箱!"}
|
|
|
|
var node: Dictionary = treasure_nodes[target_idx]
|
|
var is_gold: bool = bool(node.get("is_gold", false))
|
|
var chest_vnum = VNUM_GOLD_CHEST if is_gold else VNUM_SILVER_CHEST
|
|
var chest_name = "黄金宝箱" if is_gold else "白银宝箱"
|
|
|
|
# 寻找背包空格放入宝箱
|
|
var free_slot := -1
|
|
for i in range(inventory.size()):
|
|
if inventory[i] == null:
|
|
free_slot = i
|
|
break
|
|
|
|
if free_slot == -1:
|
|
return {"ok": false, "reason": "INVENTORY_FULL", "msg": "背包已满,无法容纳挖出的宝箱!"}
|
|
|
|
inventory[free_slot] = {
|
|
"vnum": chest_vnum,
|
|
"name": chest_name,
|
|
"count": 1
|
|
}
|
|
|
|
treasure_nodes.remove_at(target_idx)
|
|
treasure_excavated.emit(chest_vnum, chest_name)
|
|
|
|
return {
|
|
"ok": true,
|
|
"chest_vnum": chest_vnum,
|
|
"chest_name": chest_name,
|
|
"slot": free_slot,
|
|
"msg": "破土而出!在地脉深处成功发掘出【%s】!" % chest_name
|
|
}
|
|
|
|
# 使用对应的黄金钥匙或白银钥匙开启宝箱
|
|
func open_treasure_chest(chest_slot: int, key_slot: int, inventory: Array) -> Dictionary:
|
|
if chest_slot < 0 or chest_slot >= inventory.size() or inventory[chest_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_CHEST_SLOT"}
|
|
if key_slot < 0 or key_slot >= inventory.size() or inventory[key_slot] == null:
|
|
return {"ok": false, "reason": "INVALID_KEY_SLOT"}
|
|
|
|
var chest: Dictionary = inventory[chest_slot]
|
|
var key: Dictionary = inventory[key_slot]
|
|
|
|
var chest_vnum := int(chest.get("vnum", 0))
|
|
var key_vnum := int(key.get("vnum", 0))
|
|
|
|
var match_ok := false
|
|
if chest_vnum == VNUM_GOLD_CHEST and key_vnum == VNUM_GOLD_KEY:
|
|
match_ok = true
|
|
elif chest_vnum == VNUM_SILVER_CHEST and key_vnum == VNUM_SILVER_KEY:
|
|
match_ok = true
|
|
|
|
if not match_ok:
|
|
return {"ok": false, "reason": "KEY_MISMATCH", "msg": "钥匙与宝箱类型不匹配!黄金宝箱需用黄金钥匙开启。"}
|
|
|
|
# 消耗钥匙与宝箱
|
|
inventory[chest_slot] = null
|
|
var key_cnt := int(key.get("count", 1))
|
|
if key_cnt > 1:
|
|
key["count"] = key_cnt - 1
|
|
else:
|
|
inventory[key_slot] = null
|
|
|
|
# 生成高额宝物奖励
|
|
var rewards: Array = []
|
|
if chest_vnum == VNUM_GOLD_CHEST:
|
|
rewards = [
|
|
{"vnum": 25040, "name": "祝福卷轴", "count": 3},
|
|
{"vnum": 51501, "name": "龙石原石", "count": 2},
|
|
{"vnum": 28430, "name": "+4 灵石", "count": 1}
|
|
]
|
|
else:
|
|
rewards = [
|
|
{"vnum": 25040, "name": "祝福卷轴", "count": 1},
|
|
{"vnum": 50300, "name": "技能书", "count": 1}
|
|
]
|
|
|
|
# 放入背包
|
|
for r in rewards:
|
|
for i in range(inventory.size()):
|
|
if inventory[i] == null:
|
|
inventory[i] = r.duplicate(true)
|
|
break
|
|
|
|
treasure_opened.emit(chest.get("name", "宝箱"), rewards)
|
|
|
|
return {
|
|
"ok": true,
|
|
"chest_name": chest.get("name", "宝箱"),
|
|
"rewards": rewards,
|
|
"msg": "清脆的开锁声响起!开启宝箱,获得珍稀秘宝!"
|
|
}
|