烧烤 动物养殖 分类 功能实现1

This commit is contained in:
wenhe committed 2026-09-01 22:18:06 +08:00
1 parent 98335837dd
commit eac9409571
16 files changed
+1493 -5

No files matched your search

@@ -0,0 +1,289 @@
# -*- coding: utf-8 -*-
"""
================================================================================
烧烤方块转换规则数据
================================================================================
所有"烧烤"(xy_barbecue) 分类下"右键方块→消耗物品→替换方块"的转换规则都在这里。
只被 BarbecueService 读取,FeatureDispatcher 通过方块名集合路由到 BarbecueService,
不会去查其他功能(动物养殖/做菜/工具切换)的数据库。
【烤法区分】
1) 烤串(牛肉串/羊肉串/烤蔬菜串/烤香菇串)
→ 放在"烧烤架"上烤,右键 2 次转换,不计时
→ 牛肉串/羊肉串: 生=_1, 半熟=_0, 熟=_3(注意顺序与蔬菜串不同!)
→ 烤蔬菜串/烤香菇串: 生=_0, 半熟=_1, 熟=_3
2) 烤鸡/烤乳猪/烤鱼
→ "上支杆"烤法: 支杆=b_34, 手持生食材右键支杆上杆
→ 支杆下方放"篝火(原版营火 campfire)", 计时自动转换
→ 烤熟后空手右键"取下" → 以掉落物形式获得整只成品
【食用部分说明】
烤熟成品(整只/整头)的"吃东西逐级分食"转换(如 整只→5/6→…→1/6)
由后续统一的"总食用功能"实现,本次不实现、不收录。
字段说明(与 AnimalData 一致):
source: 被右键的方块ID(必填)
item: 玩家手里必须拿的物品,None=空手也行
target: 替换成什么方块,"minecraft:air"=消失
consume: 消耗物品数量,0=不消耗(默认1)
giveItem: 额外给玩家什么物品,None=不给
giveCount: giveItem 的数量,默认1
dropItem: 转换时以掉落物形式生成的物品列表,优先于 giveItem
格式: [{"name": "xxx", "count": N}, ...]
type: "switch"=右键切换, "timer"=计时自动转换
time: 计时型转换需要多少秒(到时间自动替换方块)
below: 计时型转换要求下方方块(如 ["minecraft:campfire"])
================================================================================
"""
BARBECUE_SWITCHES = [
# ============================================================
# 一、烧烤架(点火/灭火)
# ============================================================
# 【点火-打火石】烧烤架_1(无火) → 烧烤架_2(点燃),打火石不消耗
{
"source": "rtdd:barbecuegrill_0",
"item": "minecraft:flint_and_steel",
"target": "rtdd:barbecuegrill_1",
"consume": 0,
"type": "switch",
"giveItem": None,
},
# 【点火-烈焰弹】烧烤架_1(无火) → 烧烤架_2(点燃),烈焰弹消耗1个
{
"source": "rtdd:barbecuegrill_0",
"item": "minecraft:fire_charge",
"target": "rtdd:barbecuegrill_1",
"consume": 1,
"type": "switch",
"giveItem": None,
},
# 【灭火】烧烤架_2(点燃) → 烧烤架_1(无火),消耗水桶,获得空桶
{
"source": "rtdd:barbecuegrill_1",
"item": "minecraft:water_bucket",
"target": "rtdd:barbecuegrill_0",
"consume": 1,
"type": "switch",
"giveItem": "minecraft:bucket",
"giveCount": 1,
},
# ============================================================
# 二、烤串(放烧烤架上,右键 2 次转换,不计时)
# 【重要】牛肉串/羊肉串的 生=_1 半熟=_0;蔬菜串/香菇串的 生=_0 半熟=_1
# ============================================================
# 牛肉串: 生(_1) → 半熟(_0) → 熟(_3)
# 放在"已点燃的烧烤架"上方才能右键烤制(下方必须是 barbecuegrill_1)
{
"source": "rtdd:beefkebabs_1",
"item": None,
"target": "rtdd:beefkebabs_0",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
{
"source": "rtdd:beefkebabs_0",
"item": None,
"target": "rtdd:beefkebabs_3",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
# 羊肉串: 生(_1) → 半熟(_0) → 熟(_3)
{
"source": "rtdd:lambkebabs_1",
"item": None,
"target": "rtdd:lambkebabs_0",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
{
"source": "rtdd:lambkebabs_0",
"item": None,
"target": "rtdd:lambkebabs_3",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
# 烤蔬菜串: 生(_0) → 半熟(_1) → 熟(_3)
{
"source": "rtdd:roasted_vegetables_0",
"item": None,
"target": "rtdd:roasted_vegetables_1",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
{
"source": "rtdd:roasted_vegetables_1",
"item": None,
"target": "rtdd:roasted_vegetables_3",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
# 烤香菇串: 生(_0) → 半熟(_1) → 熟(_3)
{
"source": "rtdd:roastedmushrooms_0",
"item": None,
"target": "rtdd:roastedmushrooms_1",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
{
"source": "rtdd:roastedmushrooms_1",
"item": None,
"target": "rtdd:roastedmushrooms_3",
"consume": 0,
"type": "switch",
"switchCd": 0.15,
"below": ["rtdd:barbecuegrill_1"],
"giveItem": None,
},
# ============================================================
# 三、上杆(手持生食材右键 支杆b_34)
# ============================================================
# 【上烤鸡】支杆 + 生鸡肉(放置后方块物品) → 烤鸡(上烤架_生)
{
"source": "rtdd:b_34",
"item": "rtdd:roastchickenplace_0",
"target": "rtdd:roastchicken_0",
"consume": 1,
"type": "switch",
"giveItem": None,
},
# 【上烤乳猪】支杆 + 死猪 → 烤乳猪(生)
{
"source": "rtdd:b_34",
"item": "rtdd:dead_pig",
"target": "rtdd:roastpig_0",
"consume": 1,
"type": "switch",
"giveItem": None,
},
# 【上烤鳕鱼】支杆 + 鳕鱼 → 烤鳕鱼(生)
{
"source": "rtdd:b_34",
"item": "minecraft:cod",
"target": "rtdd:grilledcod_0",
"consume": 1,
"type": "switch",
"giveItem": None,
},
# 【上烤鲑鱼】支杆 + 鲑鱼 → 烤鲑鱼(生)
{
"source": "rtdd:b_34",
"item": "minecraft:salmon",
"target": "rtdd:grilled_fish",
"consume": 1,
"type": "switch",
"giveItem": None,
},
# ============================================================
# 四、计时烤制(上杆后,支杆下方篝火,计时自动转换)
# ============================================================
# 【烤熟】烤鸡(上烤架_生) → 烤鸡(上烤架_熟),下方需篝火,约60秒
{
"source": "rtdd:roastchicken_0",
"item": None,
"target": "rtdd:roastchicken_1",
"consume": 0,
"type": "timer",
"time": 60,
"below": ["minecraft:campfire"],
"giveItem": None,
},
# 【烤熟】烤乳猪(生) → 烤乳猪(熟),下方需篝火,约60秒
{
"source": "rtdd:roastpig_0",
"item": None,
"target": "rtdd:roastpig_1",
"consume": 0,
"type": "timer",
"time": 60,
"below": ["minecraft:campfire"],
"giveItem": None,
},
# 【烤熟】烤鳕鱼(生) → 烤鳕鱼(熟),下方需篝火,约60秒
{
"source": "rtdd:grilledcod_0",
"item": None,
"target": "rtdd:grilledcod_1",
"consume": 0,
"type": "timer",
"time": 60,
"below": ["minecraft:campfire"],
"giveItem": None,
},
# 【烤熟】烤鲑鱼(生) → 烤鲑鱼(熟),下方需篝火,约60秒
{
"source": "rtdd:grilled_fish",
"item": None,
"target": "rtdd:grilled_salmon",
"consume": 0,
"type": "timer",
"time": 60,
"below": ["minecraft:campfire"],
"giveItem": None,
},
# ============================================================
# 五、取下(空手右键烤熟方块,支杆恢复,成品以掉落物获得)
# ============================================================
# 【取下烤鸡】烤鸡(上烤架_熟) → 支杆 + 掉落 烤鸡(整只)
{
"source": "rtdd:roastchicken_1",
"item": None,
"target": "rtdd:b_34",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "rtdd:roast_chickenplace_1", "count": 1}],
"giveItem": None,
},
# 【取下烤乳猪】烤乳猪(熟) → 支杆 + 掉落 烤乳猪(整只)
{
"source": "rtdd:roastpig_1",
"item": None,
"target": "rtdd:b_34",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "rtdd:roastpignew_1", "count": 1}],
"giveItem": None,
},
]
# ============================================================
# 六、手持物品右键"方块表面"生成规则(ServerItemUseOnEvent)
# ============================================================
# 目前只有一条:手持"原版生鸡肉"右键任意方块表面 → 在其表面生成
# "生鸡肉(原版生鸡肉右键放置后)"(roastchickenplace_0) 方块,消耗1个。
# 该规则由 FeatureDispatcher 转发 ServerItemUseOnEvent 到 BarbecueService。
ITEM_USE_ON_RULES = [
{
"item": "minecraft:chicken", # 手持的原版生鸡肉
"target": "rtdd:roastchickenplace_0", # 生成的目标方块
"consume": 1, # 消耗1个生鸡肉
},
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""
烧烤客户端(预留)
=================
目前烧烤功能的服务端逻辑已由 BarbecueService + FeatureDispatcher 处理,
客户端暂不需要额外的 UI 工作。
如果以后需要烤制进度提示、烤串状态展示等 UI,可以在这里扩展。
"""
import mod.client.extraClientApi as clientApi
ClientSystem = clientApi.GetClientSystemCls()
class BarbecueClient(ClientSystem):
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
print "===== BarbecueClient init ====="
def Destroy(self):
print "===== BarbecueClient Destroy ====="
@@ -0,0 +1,341 @@
# -*- coding: utf-8 -*-
"""
================================================================================
烧烤引擎(服务端)
================================================================================
只处理"烧烤"(xy_barbecue) 分类下的方块转换:烧烤架、烤串、烤鸡、烤乳猪、烤鱼。
【与 BlockSwitchService / CookingService 的区别】
本系统(方案A)采用"统一分发器"架构:
- FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统
- 本类通过 FeatureDispatcher.register(self) 把管辖方块注册进去
- 玩家右键方块时,FeatureDispatcher 用 O(1) dict 直接找到本实例
- 本类只查自己的 BarbecueData(BARBECUE_SWITCHES),不碰其他功能数据库
【本类额外处理的规则】
- 手持"原版生鸡肉"右键方块表面(ServerItemUseOnEvent) → 生成"生鸡肉(放置后)"方块
该规则通过 FeatureDispatcher 的 itemUseOn 路由转发到本类 handleItemUseOn
【转换类型】
- type="switch" : 右键直接转换(点火/灭火/烤串2次/上杆/取下)
- type="timer" : 计时自动转换(上杆后下方篝火烤熟),
由本类用 AddTimer 实现,启动前检查下方是否为篝火
================================================================================
"""
import time
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
comp = serverApi.GetEngineCompFactory() # 组件工厂
# ---- 继承 BlueFox 框架的系统基类(提供 AddTimer / CreateEngineItemEntity 等方法) ----
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
from ....BlueFoxTools.ToolsConfig import LevelId
# ---- 只导入烧烤自己的数据库 ----
from ..BarbecueData import BARBECUE_SWITCHES, ITEM_USE_ON_RULES
# ---- 统一分发器 ----
from ...service.FeatureDispatcher import FeatureDispatcher
# ---- 方块表面方向 → 相邻坐标偏移(0下 1上 2北 3南 4西 5东) ----
FACE_OFFSET = {
0: (0, -1, 0),
1: (0, 1, 0),
2: (0, 0, -1),
3: (0, 0, 1),
4: (-1, 0, 0),
5: (1, 0, 0),
}
class BarbecueService(BlueFoxServerSystem):
"""
烧烤引擎
【核心数据结构】
barbecueRecipes = {
(方块名, 物品名): 规则dict, # O(1) 查找
}
barbecueBlockSet = {方块名, ...} # O(1) 拦截/注册用
barbecueTimers = {方块名: 规则dict} # 计时型起始方块 → 规则
itemUseOnRecipes = {物品名: 规则dict} # 手持物品右键方块表面 → 生成规则
"""
def __init__(self, namespace, systemName):
super(BarbecueService, self).__init__(namespace, systemName)
print "===== BarbecueService init ====="
# ---- 构建烧烤规则字典(O(1) 查找) ----
self.barbecueRecipes = {}
self.barbecueBlockSet = set()
self.barbecueTimers = {}
for sw in BARBECUE_SWITCHES:
key = (sw["source"], sw.get("item"))
self.barbecueRecipes[key] = sw
self.barbecueBlockSet.add(sw["source"])
if sw.get("type") == "timer":
self.barbecueTimers[sw["source"]] = sw
# ---- 手持物品右键方块表面规则:物品名 → 规则 ----
self.itemUseOnRecipes = {}
for rule in ITEM_USE_ON_RULES:
self.itemUseOnRecipes[rule["item"]] = rule
# ---- 计时去重表 + 右键冷却表 ----
self._timerSchedule = {}
self.cooldown = {}
# ---- 注册到统一分发器 ----
FeatureDispatcher.register(self)
# ============================================================================
# 给 FeatureDispatcher 的接口
# ============================================================================
def getFeatureBlocks(self):
"""返回本功能管辖的所有方块名集合(右键路由用)"""
return self.barbecueBlockSet
def getItemUseOnRules(self):
"""返回手持物品右键方块表面的规则:{物品名: 规则dict}(供分发器路由)"""
return self.itemUseOnRecipes
def handleBlockUse(self, args):
"""
由 FeatureDispatcher 转发的右键方块入口。
只有 blockName 在本功能方块集合里才会被调用。
返回 True=已处理,False=当前组合无匹配规则。
"""
blockName = args.get("blockName")
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
pos = (x, y, z)
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
key = (dim, x, y, z)
itemDict = self.getCarriedItem(playerId)
recipe = self.findRecipe(blockName, itemDict)
if not recipe:
return False
# 冷却检查(烤串"右键2次"用 switchCd 调小冷却以便快速连点)
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
return True
# ---- 下方方块检查(timer 需篝火,烤串需点燃的烧烤架) ----
if not self.checkBelow(recipe, dim, pos):
# 下方不是要求的方法 → 提示玩家,不执行(但也算处理,取消默认行为)
if recipe.get("type") == "timer":
tip = "§c支杆下方需要放一个篝火(营火)才能烤制!"
else:
tip = "§c烤串需要放在点燃的烧烤架上才能烤制!"
comp.CreateMsg(playerId).NotifyOneMessage(playerId, tip)
return True
# 计时型:启动计时
if recipe.get("type") == "timer":
self.startTimer(key, recipe, dim)
return True
# 切换型
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName)
return True
def handleItemUseOn(self, args):
"""
由 FeatureDispatcher 转发的"手持物品右键方块表面"事件。
当前用于:手持原版生鸡肉右键任意方块表面 → 在其表面生成生鸡肉(放置后)方块。
返回 True=已处理,False=当前组合无匹配规则。
"""
itemName = args.get("itemName")
playerId = args.get("playerId")
rule = self.itemUseOnRecipes.get(itemName)
if not rule:
return False
x, y, z = args.get("x"), args.get("y"), args.get("z")
face = args.get("face", 1)
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
# 计算被点方块表面相邻的目标格
off = FACE_OFFSET.get(face, (0, 1, 0))
targetPos = (x + off[0], y + off[1], z + off[2])
# 目标格必须为空(可放置)
try:
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(targetPos, dim)
if cur and cur.get("name") != "minecraft:air":
return False
except Exception:
return False
# 消耗 1 个生鸡肉
itemDict = self.getCarriedItem(playerId)
if not itemDict:
return False
self.consumeItem(playerId, itemDict, rule.get("consume", 1))
# 生成生鸡肉(放置后)方块
self.replaceBlock(targetPos, dim, rule["target"], playerId)
return True
# ============================================================================
# 规则匹配(O(1))
# ============================================================================
def findRecipe(self, blockName, itemDict):
itemName = itemDict.get("newItemName") if itemDict else None
recipe = self.barbecueRecipes.get((blockName, itemName))
if recipe:
return recipe
recipe = self.barbecueRecipes.get((blockName, None))
if recipe:
return recipe
return None
# ============================================================================
# 切换型规则处理
# ============================================================================
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName):
"""
正向(item 非 None):点火/上杆 → 消耗 → 替换 → 给奖励 → 续计时
逆向(item 为 None):烤串2次/取下 → 掉落 → 替换
"""
# ---- 正向 ----
if recipe.get("item") is not None:
if recipe.get("consume", 1) > 0:
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
if recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
# 上杆后若新方块是计时源 → 尝试启动计时(下方需有篝火)
if recipe.get("target") in self.barbecueTimers:
nextTimer = self.barbecueTimers[recipe["target"]]
if self.checkBelow(nextTimer, dim, pos):
self.startTimer(key, nextTimer, dim)
return
# ---- 逆向 ----
if recipe.get("dropItem"):
self.dropItems(dim, pos, recipe["dropItem"])
elif recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# ============================================================================
# 计时型规则处理(AddTimer 一次性定时,启动前检查下方篝火)
# ============================================================================
def checkBelow(self, recipe, dim, pos):
"""
检查方块下方是否为 recipe["below"] 中指定的方块。
没写 below 字段 → 视为满足。
"""
below = recipe.get("below")
if not below:
return True
belowPos = (pos[0], pos[1] - 1, pos[2])
try:
b = comp.CreateBlockInfo(LevelId).GetBlockNew(belowPos, dim)
return b.get("name") in below
except Exception:
return False
def startTimer(self, key, recipe, dim):
"""启动一次性定时器,1秒内去重,到点由 _timerDone 执行转换"""
now = time.time()
last = self._timerSchedule.get(key, 0)
if now - last < 1.0:
return
self._timerSchedule[key] = now
self.AddTimer(recipe.get("time", 60), self._timerDone, key, recipe, dim)
def _timerDone(self, key, recipe, dim):
"""计时到点:确认方块仍是计时源,且下方仍有篝火,才执行转换"""
pos = (key[1], key[2], key[3])
try:
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
if not cur or cur.get("name") != recipe["source"]:
return
if not self.checkBelow(recipe, dim, pos):
return # 篝火被拿走了,暂停烤制
self.replaceBlock(pos, dim, recipe["target"], None)
# 若新方块仍是计时源 → 继续下一级(当前烧烤无多级计时,预留)
nextRecipe = self.barbecueTimers.get(recipe["target"])
if nextRecipe:
self.startTimer(key, nextRecipe, dim)
except Exception:
pass
# ============================================================================
# 工具方法
# ============================================================================
def getCarriedItem(self, playerId):
itemComp = comp.CreateItem(playerId)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
def consumeItem(self, playerId, itemDict, amount=1):
if itemDict is None:
return
count = itemDict.get("count", 0)
if count <= amount:
comp.CreateItem(playerId).SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": "minecraft:air", "count": 0, "auxValue": 0})
else:
itemDict["count"] = count - amount
comp.CreateItem(playerId).SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict["count"],
"auxValue": itemDict.get("newAuxValue", 0)})
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
creator = playerId if playerId is not None else LevelId
if aux is None:
try:
oldBlock = comp.CreateBlockInfo(creator).GetBlockNew(pos, dim)
aux = oldBlock.get("aux", 0)
except Exception:
aux = 0
comp.CreateBlockInfo(creator).SetBlockNew(
pos,
blockDict={"name": newBlockName, "aux": aux},
oldBlockHandling=0,
dimensionId=dim)
def giveItem(self, playerId, itemName, count=1, auxValue=0):
comp.CreateItem(playerId).SpawnItemToPlayerInv(
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
def dropItems(self, dim, pos, dropList):
"""以掉落物形式生成物品(取下烤鸡/烤乳猪等)"""
for d in dropList:
itemDict = {
"itemName": d["name"],
"count": d.get("count", 1),
"auxValue": d.get("aux", 0),
}
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
def checkCooldown(self, key, cd=0.5):
"""右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品"""
now = time.time()
last = self.cooldown.get(key, 0)
if now - last < cd:
return False
self.cooldown[key] = now
return True