Files
mtgodot-poc/project/fishing_cooking_camp_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

215 lines
6.3 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.
# fishing_cooking_camp_system.gd —— Metin2 40250 野外钓鱼烹饪与生火露营增益系统 1:1
# 对照 40250 服务端 fishing.cpp, char_item.cpp, affect.h
class_name FishingCookingCampSystem
extends RefCounted
signal campfire_lit(pos: Vector3, duration: float)
signal campfire_extinguished()
signal fish_cooked(raw_vnum: int, cooked_vnum: int, cooked_name: String)
signal food_buff_applied(buff_id: String, buff_name: String, duration: float)
signal food_buff_expired(buff_id: String)
const CAMPFIRE_KIT_VNUM := 27600 # 营火工具 (Campfire)
const CAMPFIRE_DURATION := 300.0 # 营火持续 5 分钟
const FOOD_BUFF_DURATION := 1800.0 # 食用增益 30 分钟 (1800 秒)
# 鱼类与烹饪食谱
const RECIPES: Dictionary = {
27802: { # 生鲤鱼 -> 烤鲤鱼
"cooked_vnum": 27832,
"cooked_name": "烤鲤鱼",
"buff_id": "buff_grilled_carp",
"stat_key": "max_hp",
"stat_val": 1000,
"desc": "最大生命上限 +1000,持续 30 分钟"
},
27803: { # 生草鱼 -> 烤草鱼
"cooked_vnum": 27833,
"cooked_name": "烤草鱼",
"buff_id": "buff_grilled_grass_carp",
"stat_key": "move_speed",
"stat_val": 15,
"desc": "移动速度 +15,持续 30 分钟"
},
27815: { # 生红鳟鱼 -> 烤红鳟鱼
"cooked_vnum": 27845,
"cooked_name": "烤红鳟鱼",
"buff_id": "buff_grilled_trout",
"stat_key": "atk",
"stat_val": 50,
"desc": "攻击力 +50,持续 30 分钟"
}
}
# 烤鱼物品映射
const COOKED_FOODS: Dictionary = {
27832: {"buff_id": "buff_grilled_carp", "name": "烤鲤鱼", "stat_key": "max_hp", "stat_val": 1000},
27833: {"buff_id": "buff_grilled_grass_carp", "name": "烤草鱼", "stat_key": "move_speed", "stat_val": 15},
27845: {"buff_id": "buff_grilled_trout", "name": "烤红鳟鱼", "stat_key": "atk", "stat_val": 50}
}
var is_campfire_active: bool = false
var campfire_timer: float = 0.0
var campfire_pos: Vector3 = Vector3.ZERO
# 活跃中的食物 Buff: buff_id -> { "remaining": float, "stat_key": String, "stat_val": int }
var active_buffs: Dictionary = {}
# 点燃野外营火
func light_campfire(pos: Vector3, inventory: Array) -> Dictionary:
var kit_slot = -1
for i in range(inventory.size()):
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == CAMPFIRE_KIT_VNUM:
kit_slot = i
break
if kit_slot == -1:
return {"ok": false, "reason": "NO_CAMPFIRE_KIT", "msg": "背包中没有【营火工具】(27600)"}
# 消耗 1 个营火工具
var item = inventory[kit_slot]
var cnt = int(item.get("count", 1))
if cnt > 1:
item["count"] = cnt - 1
else:
inventory[kit_slot] = null
is_campfire_active = true
campfire_timer = CAMPFIRE_DURATION
campfire_pos = pos
campfire_lit.emit(pos, CAMPFIRE_DURATION)
return {
"ok": true,
"pos": pos,
"duration": CAMPFIRE_DURATION,
"msg": "营火已点燃!散发着温暖火光,可在此烧烤烹饪渔获!"
}
# 在营火旁烹饪烤鱼
func cook_fish(slot: int, inventory: Array) -> Dictionary:
if not is_campfire_active:
return {"ok": false, "reason": "NO_ACTIVE_CAMPFIRE", "msg": "周围没有生起的营火,无法烹饪!"}
if slot < 0 or slot >= inventory.size() or inventory[slot] == null:
return {"ok": false, "reason": "INVALID_SLOT"}
var raw_item = inventory[slot]
var raw_vnum = int(raw_item.get("vnum", 0))
if not RECIPES.has(raw_vnum):
return {"ok": false, "reason": "NOT_COOKABLE", "msg": "此物品无法在营火上烹饪!"}
var r = RECIPES[raw_vnum]
# 消耗 1 条生鱼
var cnt = int(raw_item.get("count", 1))
if cnt > 1:
raw_item["count"] = cnt - 1
else:
inventory[slot] = null
# 入包烤鱼
var cooked_vnum: int = r["cooked_vnum"]
var cooked_name: String = r["cooked_name"]
# 寻找可叠加的空格或同类
var placed := false
for i in range(inventory.size()):
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == cooked_vnum:
inventory[i]["count"] = int(inventory[i].get("count", 1)) + 1
placed = true
break
if not placed:
for i in range(inventory.size()):
if inventory[i] == null:
inventory[i] = {
"vnum": cooked_vnum,
"name": cooked_name,
"count": 1
}
placed = true
break
fish_cooked.emit(raw_vnum, cooked_vnum, cooked_name)
return {
"ok": true,
"cooked_name": cooked_name,
"desc": r["desc"],
"msg": "滋滋作响!成功烤制出色香味俱全的【%s】!" % cooked_name
}
# 食用烤鱼获得 30 分钟强力 Buff
func eat_cooked_fish(slot: int, inventory: Array, player_data: Dictionary) -> Dictionary:
if slot < 0 or slot >= inventory.size() or inventory[slot] == null:
return {"ok": false, "reason": "INVALID_SLOT"}
var item = inventory[slot]
var vnum = int(item.get("vnum", 0))
if not COOKED_FOODS.has(vnum):
return {"ok": false, "reason": "NOT_EDIBLE_COOKED_FISH"}
var cfg = COOKED_FOODS[vnum]
var bid: String = cfg["buff_id"]
var stat_k: String = cfg["stat_key"]
var stat_v: int = cfg["stat_val"]
# 消耗 1 条烤鱼
var cnt = int(item.get("count", 1))
if cnt > 1:
item["count"] = cnt - 1
else:
inventory[slot] = null
# 如果已有同类 Buff,先刷新时长并避免重复叠加属性
if active_buffs.has(bid):
active_buffs[bid]["remaining"] = FOOD_BUFF_DURATION
else:
player_data[stat_k] = int(player_data.get(stat_k, 0)) + stat_v
active_buffs[bid] = {
"remaining": FOOD_BUFF_DURATION,
"stat_key": stat_k,
"stat_val": stat_v,
"name": cfg["name"]
}
food_buff_applied.emit(bid, cfg["name"], FOOD_BUFF_DURATION)
return {
"ok": true,
"buff_id": bid,
"buff_name": cfg["name"],
"stat_key": stat_k,
"stat_val": stat_v,
"current_stat": player_data[stat_k],
"msg": "品尝了美味的【%s】!精神焕发,获得了【%s +%d】持续 30 分钟!" % [cfg["name"], stat_k, stat_v]
}
# 帧心跳更新营火与食物 Buff
func update(delta: float, player_data: Dictionary) -> void:
# 营火计时
if is_campfire_active:
campfire_timer -= delta
if campfire_timer <= 0.0:
is_campfire_active = false
campfire_extinguished.emit()
# 食物 Buff 计时与过期反扣
var expired_ids := []
for bid in active_buffs.keys():
var b = active_buffs[bid]
b["remaining"] -= delta
if b["remaining"] <= 0.0:
expired_ids.append(bid)
for bid in expired_ids:
var b = active_buffs[bid]
var stat_k = b["stat_key"]
var stat_v = b["stat_val"]
player_data[stat_k] = max(0, int(player_data.get(stat_k, 0)) - stat_v)
active_buffs.erase(bid)
food_buff_expired.emit(bid)