Files
mtgodot-poc/project/herbal_dew_system.gd
T
shenandshen 66d217b313 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

229 lines
5.9 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)