- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
214 lines
7.0 KiB
GDScript
214 lines
7.0 KiB
GDScript
# fishing_mining_system.gd —— 钓鱼与采矿休闲生活系统(40250 官方 1:1 对齐)
|
|
# 对齐源码:
|
|
# - 40250 server/game/src/fishing.cpp:180-360 (钓竿、鱼饵、钓鱼状态机、开鱼与蚌壳珍珠)
|
|
# - 40250 server/game/src/mining.cpp:50-180 (矿镐、矿脉 NPC、采矿倒计时与原石产出)
|
|
class_name FishingMiningSystem
|
|
extends RefCounted
|
|
|
|
# 核心道具 Vnum 常量
|
|
const VNUM_ROD_BASE := 27400 # 钓竿 +0
|
|
const VNUM_BAIT_EARTHWORM := 27800 # 鱼饵 (蚯蚓)
|
|
const VNUM_BAIT_PASTE := 27801 # 鱼饵 (面团)
|
|
|
|
const VNUM_FISHBONE := 27990 # 鱼骨
|
|
const VNUM_CLAM := 27987 # 蚌壳
|
|
const VNUM_STONE_PIECE := 27991 # 碎石片
|
|
const VNUM_WHITE_PEARL := 27992 # 白珍珠
|
|
const VNUM_BLUE_PEARL := 27993 # 蓝珍珠
|
|
const VNUM_BLOOD_PEARL := 27994 # 血红珍珠
|
|
|
|
const VNUM_PICKAXE_BASE := 29101 # 矿镐 +0
|
|
|
|
# 经典鱼类列表
|
|
const FISH_TABLE := [
|
|
{"vnum": 27802, "name": "金鲫鱼"},
|
|
{"vnum": 27803, "name": "鲤鱼"},
|
|
{"vnum": 27804, "name": "鲑鱼"},
|
|
{"vnum": 27805, "name": "鳟鱼"},
|
|
{"vnum": 27806, "name": "鲶鱼"},
|
|
]
|
|
|
|
# 矿脉 NPC 与对应原石产出表 (40250 mining.cpp)
|
|
const VEIN_TABLE := {
|
|
20047: {"name": "钻石矿脉", "ore_vnum": 50601, "ore_name": "钻石原石"},
|
|
20048: {"name": "琥珀矿脉", "ore_vnum": 50602, "ore_name": "琥珀原石"},
|
|
20049: {"name": "化石木", "ore_vnum": 50603, "ore_name": "化石原石"},
|
|
20050: {"name": "铜矿脉", "ore_vnum": 50604, "ore_name": "铜原石"},
|
|
20051: {"name": "银矿脉", "ore_vnum": 50605, "ore_name": "银原石"},
|
|
20052: {"name": "金矿脉", "ore_vnum": 50606, "ore_name": "金原石"},
|
|
20053: {"name": "玉矿脉", "ore_vnum": 50607, "ore_name": "玉原石"},
|
|
20054: {"name": "黑檀木", "ore_vnum": 50608, "ore_name": "黑檀原石"},
|
|
20055: {"name": "珍珠矿脉", "ore_vnum": 50609, "ore_name": "珍珠原石"},
|
|
20056: {"name": "白金矿脉", "ore_vnum": 50610, "ore_name": "白金原石"},
|
|
20057: {"name": "水晶矿脉", "ore_vnum": 50611, "ore_name": "水晶原石"},
|
|
20058: {"name": "紫晶矿脉", "ore_vnum": 50612, "ore_name": "紫晶原石"},
|
|
20059: {"name": "天堂泪矿脉", "ore_vnum": 50613, "ore_name": "天堂泪原石"},
|
|
}
|
|
|
|
# --- 钓鱼系统 ---
|
|
|
|
## 检查物品是否为钓竿
|
|
static func is_fishing_rod(vnum: int) -> bool:
|
|
return vnum >= 27400 and vnum <= 27590
|
|
|
|
## 检查物品是否为鱼饵
|
|
static func is_bait(vnum: int) -> bool:
|
|
return vnum == VNUM_BAIT_EARTHWORM or vnum == VNUM_BAIT_PASTE
|
|
|
|
## 检查是否为鲜鱼
|
|
static func is_live_fish(vnum: int) -> bool:
|
|
return vnum >= 27802 and vnum <= 27832
|
|
|
|
## 检查是否为蚌壳
|
|
static func is_clam(vnum: int) -> bool:
|
|
return vnum == VNUM_CLAM
|
|
|
|
## 钓鱼判定 (40250 fishing.cpp:210)
|
|
## has_bait: 是否已挂饵; rod_level: 钓竿等级 (0..9)
|
|
static func perform_fishing(has_bait: bool, rod_level := 0, force_success := false) -> Dictionary:
|
|
if not has_bait:
|
|
return {"ok": false, "code": "NO_BAIT", "msg": "钓钩上没有鱼饵,无法钓鱼!"}
|
|
|
|
# 40250 基础钓鱼成功率:40% + 钓竿等级 * 3%
|
|
var base_rate := 40 + rod_level * 3
|
|
var roll := 0 if force_success else (randi() % 100 + 1)
|
|
|
|
if not force_success and roll > base_rate:
|
|
return {
|
|
"ok": false,
|
|
"code": "FISH_ESCAPED",
|
|
"consumed_bait": true,
|
|
"msg": "鱼饵被咬走了,但鱼溜掉了!"
|
|
}
|
|
|
|
# 成功钓上鲜鱼
|
|
var fish_idx := randi() % FISH_TABLE.size()
|
|
var fish: Dictionary = FISH_TABLE[fish_idx]
|
|
return {
|
|
"ok": true,
|
|
"code": "SUCCESS",
|
|
"consumed_bait": true,
|
|
"fish_vnum": fish["vnum"],
|
|
"fish_name": fish["name"],
|
|
"msg": "钓上了一条活蹦乱跳的【%s】!" % fish["name"]
|
|
}
|
|
|
|
## 打开鲜鱼 (40250 fishing.cpp:330)
|
|
## 鲜鱼开出:死鱼 (50%)、鱼骨 27990 (30%)、蚌壳 27987 (10%)、滑落逃脱 (10%)
|
|
static func open_live_fish(fish_vnum: int) -> Dictionary:
|
|
var roll := randi() % 100
|
|
if roll < 50:
|
|
# 获得死鱼(死鱼 Vnum = 活鱼 + 31)
|
|
var dead_vnum := fish_vnum + 31
|
|
return {
|
|
"ok": true,
|
|
"code": "DEAD_FISH",
|
|
"result_vnum": dead_vnum,
|
|
"msg": "得到了一条死鱼,可以在篝火上烤熟食用。"
|
|
}
|
|
elif roll < 80:
|
|
# 获得鱼骨 (精炼核心材料)
|
|
return {
|
|
"ok": true,
|
|
"code": "FISHBONE",
|
|
"result_vnum": VNUM_FISHBONE,
|
|
"msg": "剖开鲜鱼,在鱼肚里找到了一根坚硬的【鱼骨】!"
|
|
}
|
|
elif roll < 90:
|
|
# 获得珍贵蚌壳
|
|
return {
|
|
"ok": true,
|
|
"code": "CLAM",
|
|
"result_vnum": VNUM_CLAM,
|
|
"msg": "不可思议!在鱼肚里发现了一个紧闭的【蚌壳】!"
|
|
}
|
|
else:
|
|
return {
|
|
"ok": false,
|
|
"code": "ESCAPED",
|
|
"msg": "鲜鱼一摆尾巴跳进水里溜走了!"
|
|
}
|
|
|
|
## 打开蚌壳 (40250 fishing.cpp 蚌壳产出珍珠算法)
|
|
## 碎石 (50%)、白珍珠 27992 (25%)、蓝珍珠 27993 (15%)、血红珍珠 27994 (10%)
|
|
static func open_clam(force_pearl_type := 0) -> Dictionary:
|
|
if force_pearl_type > 0:
|
|
var pname := "白珍珠" if force_pearl_type == VNUM_WHITE_PEARL else ("蓝珍珠" if force_pearl_type == VNUM_BLUE_PEARL else "血红珍珠")
|
|
return {
|
|
"ok": true,
|
|
"code": "PEARL",
|
|
"result_vnum": force_pearl_type,
|
|
"msg": "蚌壳里散发着璀璨光芒,开出了珍稀的【%s】!" % pname
|
|
}
|
|
|
|
var roll := randi() % 100
|
|
if roll < 50:
|
|
return {
|
|
"ok": true,
|
|
"code": "STONE",
|
|
"result_vnum": VNUM_STONE_PIECE,
|
|
"msg": "蚌壳里只有一块无用的碎石片。"
|
|
}
|
|
elif roll < 75:
|
|
return {
|
|
"ok": true,
|
|
"code": "PEARL",
|
|
"result_vnum": VNUM_WHITE_PEARL,
|
|
"msg": "蚌壳里开出了一颗温润光泽的【白珍珠】!"
|
|
}
|
|
elif roll < 90:
|
|
return {
|
|
"ok": true,
|
|
"code": "PEARL",
|
|
"result_vnum": VNUM_BLUE_PEARL,
|
|
"msg": "蚌壳里开出了一颗幽蓝剔透的【蓝珍珠】!"
|
|
}
|
|
else:
|
|
return {
|
|
"ok": true,
|
|
"code": "PEARL",
|
|
"result_vnum": VNUM_BLOOD_PEARL,
|
|
"msg": "天赐奇宝!蚌壳里开出了一颗极品【血红珍珠】!"
|
|
}
|
|
|
|
# --- 采矿系统 ---
|
|
|
|
## 检查是否为矿镐
|
|
static func is_pickaxe(vnum: int) -> bool:
|
|
return vnum >= 29101 and vnum <= 29110
|
|
|
|
## 检查 NPC 是否为矿脉
|
|
static func is_vein(npc_vnum: int) -> bool:
|
|
return VEIN_TABLE.has(npc_vnum)
|
|
|
|
## 获取矿脉信息
|
|
static func get_vein_info(npc_vnum: int) -> Dictionary:
|
|
return VEIN_TABLE.get(npc_vnum, {})
|
|
|
|
## 采矿判定 (40250 mining.cpp:85)
|
|
## pickaxe_level: 矿镐等级 (0..9)
|
|
static func perform_mining(vein_vnum: int, pickaxe_level := 0, force_success := false) -> Dictionary:
|
|
if not is_vein(vein_vnum):
|
|
return {"ok": false, "code": "NOT_A_VEIN", "msg": "目标不是有效的矿脉!"}
|
|
|
|
var vein: Dictionary = VEIN_TABLE[vein_vnum]
|
|
var base_rate := 35 + pickaxe_level * 4
|
|
var roll := 0 if force_success else (randi() % 100 + 1)
|
|
|
|
if not force_success and roll > base_rate:
|
|
return {
|
|
"ok": false,
|
|
"code": "MINING_FAIL",
|
|
"msg": "用力敲击了矿脉,但没有采集到有效矿石。"
|
|
}
|
|
|
|
var count := 1 + (randi() % 3) # 1..3 个原石
|
|
return {
|
|
"ok": true,
|
|
"code": "SUCCESS",
|
|
"ore_vnum": vein["ore_vnum"],
|
|
"ore_name": vein["ore_name"],
|
|
"count": count,
|
|
"msg": "成功从【%s】采集到了 %d 个【%s】!" % [vein["name"], count, vein["ore_name"]]
|
|
}
|