# 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)