- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
229 lines
5.9 KiB
GDScript
229 lines
5.9 KiB
GDScript
# herbal_dew_system.gd —— Metin2 40250 白高草药炼丹酿造与 6 色神圣露水系统 1:1
|
||
# 对照 40250 服务端 char_item.cpp, blend.txt, potion.quest 及 NPC 20018 Baek-Go
|
||
class_name HerbalDewSystem
|
||
extends RefCounted
|
||
|
||
const NPC_BAEK_GO := 20018
|
||
const ITEM_EMPTY_BOTTLE := 70030
|
||
|
||
# 6 色神圣露水定义 (50821~50826)
|
||
const DEW_TABLE: Dictionary = {
|
||
50821: {
|
||
"name": "绿露水",
|
||
"color": "green",
|
||
"stat": "magic_defense",
|
||
"stat_desc": "魔法防御力",
|
||
"min_val": 30,
|
||
"max_val": 70,
|
||
"duration": 600.0, # 10 分钟
|
||
"water_vnum": 50721
|
||
},
|
||
50822: {
|
||
"name": "红露水",
|
||
"color": "red",
|
||
"stat": "critical_pct",
|
||
"stat_desc": "致命一击几率",
|
||
"min_val": 8,
|
||
"max_val": 20,
|
||
"duration": 600.0,
|
||
"water_vnum": 50722
|
||
},
|
||
50823: {
|
||
"name": "蓝露水",
|
||
"color": "blue",
|
||
"stat": "att_grade",
|
||
"stat_desc": "物理攻击力",
|
||
"min_val": 50,
|
||
"max_val": 120,
|
||
"duration": 600.0,
|
||
"water_vnum": 50723
|
||
},
|
||
50824: {
|
||
"name": "白露水",
|
||
"color": "white",
|
||
"stat": "def_grade",
|
||
"stat_desc": "物理防御力",
|
||
"min_val": 40,
|
||
"max_val": 100,
|
||
"duration": 600.0,
|
||
"water_vnum": 50724
|
||
},
|
||
50825: {
|
||
"name": "粉露水",
|
||
"color": "pink",
|
||
"stat": "penetrate_pct",
|
||
"stat_desc": "穿透一击几率",
|
||
"min_val": 8,
|
||
"max_val": 20,
|
||
"duration": 600.0,
|
||
"water_vnum": 50725
|
||
},
|
||
50826: {
|
||
"name": "黄露水",
|
||
"color": "yellow",
|
||
"stat": "att_speed",
|
||
"stat_desc": "攻击速度",
|
||
"min_val": 2,
|
||
"max_val": 8,
|
||
"duration": 600.0,
|
||
"water_vnum": 50726
|
||
}
|
||
}
|
||
|
||
# 草药提炼对应表 (herb_vnum -> water_vnum)
|
||
const HERB_TO_WATER: Dictionary = {
|
||
50701: 50721, # 桃花 -> Sim-Water
|
||
50702: 50722, # 风铃草 -> Dok-Water
|
||
50703: 50723, # 柿子花 -> Bo-Water
|
||
50704: 50724, # 高山石斛 -> Young-Water
|
||
50705: 50725, # 紫丁香 -> Zin-Water
|
||
50708: 50726 # 桑椹 -> Sam-Water
|
||
}
|
||
|
||
# 正在生效的露水增益: dew_vnum -> { name, stat, value, duration }
|
||
var active_dews: Dictionary = {}
|
||
|
||
# 1. 在白高处提取草药药水 (原料草药 -> 中间提取液)
|
||
static func brew_water(inventory: Array, herb_vnum: int) -> Dictionary:
|
||
if not HERB_TO_WATER.has(herb_vnum):
|
||
return {"ok": false, "reason": "INVALID_HERB", "msg": "此草药无法用于提炼!"}
|
||
|
||
var target_water: int = int(HERB_TO_WATER[herb_vnum])
|
||
|
||
# 消耗草药
|
||
var herb_slot := -1
|
||
for i in range(inventory.size()):
|
||
var it = inventory[i]
|
||
if it != null and int(it.get("vnum", 0)) == herb_vnum:
|
||
herb_slot = i
|
||
break
|
||
|
||
if herb_slot == -1:
|
||
return {"ok": false, "reason": "MISSING_HERB", "msg": "背包中缺少所需草药!"}
|
||
|
||
inventory[herb_slot] = null
|
||
|
||
# 寻找背包空位生成药水提取液
|
||
var empty_slot := -1
|
||
for i in range(inventory.size()):
|
||
if inventory[i] == null:
|
||
empty_slot = i
|
||
break
|
||
|
||
if empty_slot == -1:
|
||
return {"ok": false, "reason": "INVENTORY_FULL", "msg": "背包已满!"}
|
||
|
||
inventory[empty_slot] = {"vnum": target_water, "count": 1}
|
||
return {"ok": true, "water_vnum": target_water, "slot": empty_slot}
|
||
|
||
# 2. 在白高处合成露水 (药水提取液 + 空瓶 70030 -> 神圣露水)
|
||
static func brew_dew(inventory: Array, dew_vnum: int, force_max_roll: bool = false) -> Dictionary:
|
||
if not DEW_TABLE.has(dew_vnum):
|
||
return {"ok": false, "reason": "INVALID_DEW"}
|
||
|
||
var cfg: Dictionary = DEW_TABLE[dew_vnum]
|
||
var req_water: int = int(cfg["water_vnum"])
|
||
|
||
var water_slot := -1
|
||
var bottle_slot := -1
|
||
|
||
for i in range(inventory.size()):
|
||
var it = inventory[i]
|
||
if it != null:
|
||
var v: int = int(it.get("vnum", 0))
|
||
if v == req_water and water_slot == -1:
|
||
water_slot = i
|
||
elif v == ITEM_EMPTY_BOTTLE and bottle_slot == -1:
|
||
bottle_slot = i
|
||
|
||
if water_slot == -1:
|
||
return {"ok": false, "reason": "MISSING_WATER", "msg": "缺少对应的草药提取水剂!"}
|
||
if bottle_slot == -1:
|
||
return {"ok": false, "reason": "MISSING_BOTTLE", "msg": "缺少用于盛装露水的空药瓶 (70030)!"}
|
||
|
||
# 扣除水剂与空药瓶
|
||
inventory[water_slot] = null
|
||
inventory[bottle_slot] = null
|
||
|
||
# 抽取露水品质数值 (min_val ~ max_val)
|
||
var min_v: int = int(cfg["min_val"])
|
||
var max_v: int = int(cfg["max_val"])
|
||
var roll_val: int = max_v if force_max_roll else randi_range(min_v, max_v)
|
||
|
||
var dew_item: Dictionary = {
|
||
"vnum": dew_vnum,
|
||
"name": cfg["name"],
|
||
"value": roll_val,
|
||
"duration": float(cfg["duration"])
|
||
}
|
||
|
||
# 存入背包
|
||
var stored_slot := -1
|
||
for i in range(inventory.size()):
|
||
if inventory[i] == null:
|
||
inventory[i] = dew_item
|
||
stored_slot = i
|
||
break
|
||
|
||
return {
|
||
"ok": true,
|
||
"dew_vnum": dew_vnum,
|
||
"name": cfg["name"],
|
||
"value": roll_val,
|
||
"slot": stored_slot,
|
||
"stat_desc": cfg["stat_desc"],
|
||
"msg": "成功酿造出【%s】!品质增幅:%s +%d!" % [cfg["name"], cfg["stat_desc"], roll_val]
|
||
}
|
||
|
||
# 3. 饮用露水获得增益
|
||
func drink_dew(item: Dictionary) -> Dictionary:
|
||
var vnum: int = int(item.get("vnum", 0))
|
||
if not DEW_TABLE.has(vnum):
|
||
return {"ok": false, "reason": "NOT_A_DEW"}
|
||
|
||
var cfg: Dictionary = DEW_TABLE[vnum]
|
||
var roll_val: int = int(item.get("value", cfg["min_val"]))
|
||
|
||
active_dews[vnum] = {
|
||
"name": cfg["name"],
|
||
"stat": cfg["stat"],
|
||
"value": roll_val,
|
||
"duration": float(cfg["duration"]),
|
||
"total_duration": float(cfg["duration"])
|
||
}
|
||
|
||
return {
|
||
"ok": true,
|
||
"dew_vnum": vnum,
|
||
"name": cfg["name"],
|
||
"stat": cfg["stat"],
|
||
"value": roll_val,
|
||
"duration": float(cfg["duration"])
|
||
}
|
||
|
||
# 逐帧更新倒计时
|
||
func update(dt: float) -> Array[int]:
|
||
var expired: Array[int] = []
|
||
for vnum in active_dews.keys():
|
||
var data: Dictionary = active_dews[vnum]
|
||
data["duration"] = maxf(0.0, float(data["duration"]) - dt)
|
||
if float(data["duration"]) <= 0.0:
|
||
expired.append(vnum)
|
||
|
||
for vnum in expired:
|
||
active_dews.erase(vnum)
|
||
|
||
return expired
|
||
|
||
# 查询某项属性在当前露水中的总加成
|
||
func get_stat_bonus(stat_name: String) -> int:
|
||
var total := 0
|
||
for vnum in active_dews:
|
||
var data: Dictionary = active_dews[vnum]
|
||
if String(data.get("stat", "")) == stat_name:
|
||
total += int(data.get("value", 0))
|
||
return total
|
||
|
||
func has_dew(vnum: int) -> bool:
|
||
return active_dews.has(vnum)
|