烧烤 动物养殖 分类 功能实现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

@@ -1,6 +1,9 @@
from BlueFoxTools.ModLoader import * from BlueFoxTools.ModLoader import *
from xiangyemod.service.CookingService import CookingService from xiangyemod.service.CookingService import CookingService
from xiangyemod.service.BlockSwitchService import BlockSwitchService from xiangyemod.service.BlockSwitchService import BlockSwitchService
from xiangyemod.service.FeatureDispatcher import FeatureDispatcher
from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService
from xiangyemod.shaokao.service.BarbecueService import BarbecueService
if not serverApi.IsInServer(): if not serverApi.IsInServer():
pass pass
@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
"""
================================================================================
动物养殖方块转换规则数据
================================================================================
所有"动物养殖"分类下"右键方块→消耗物品→替换方块"的转换规则都在这里。
只被 AnimalService 读取,FeatureDispatcher 通过方块名集合路由到 AnimalService,
不会去查其他功能(烧烤/做菜/工具切换)的数据库。
与 BlockSwitchData.py 的关系:
BlockSwitchData.py = 通用工具方块切换(箭桶、挂肉钩、水槽、书柜等)
AnimalData.py = 动物养殖专属(鸡窝、饲料盆、饲料槽、粪便等)
字段说明(与 BlockSwitchData 基本一致,新增 giveCount/dropItem):
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: 计时型转换需要多少秒(到时间自动替换方块)
================================================================================
"""
ANIMAL_SWITCHES = [
# ============================================================
# 一、鸡窝(产蛋) —— 低级机制:手持鸡蛋右键放置递增
# 对应方块:chickencoop_0(0蛋) ~ chickencoop_5(5蛋)
# ============================================================
# 【放蛋1】鸡窝_1(0蛋) → 鸡窝_2(1蛋)
{
"source": "rtdd:chickencoop_0",
"item": "minecraft:egg",
"target": "rtdd:chickencoop_1",
"consume": 1,
"type": "switch",
"switchCd": 0.15,
"giveItem": None,
},
# 【放蛋2】鸡窝_2(1蛋) → 鸡窝_3(2蛋)
{
"source": "rtdd:chickencoop_1",
"item": "minecraft:egg",
"target": "rtdd:chickencoop_2",
"consume": 1,
"type": "switch",
"switchCd": 0.15,
"giveItem": None,
},
# 【放蛋3】鸡窝_3(2蛋) → 鸡窝_4(3蛋)
{
"source": "rtdd:chickencoop_2",
"item": "minecraft:egg",
"target": "rtdd:chickencoop_3",
"consume": 1,
"type": "switch",
"switchCd": 0.15,
"giveItem": None,
},
# 【放蛋4】鸡窝_4(3蛋) → 鸡窝_5(4蛋)
{
"source": "rtdd:chickencoop_3",
"item": "minecraft:egg",
"target": "rtdd:chickencoop_4",
"consume": 1,
"type": "switch",
"switchCd": 0.15,
"giveItem": None,
},
# 【放蛋5】鸡窝_5(4蛋) → 鸡窝_6(5蛋)
{
"source": "rtdd:chickencoop_4",
"item": "minecraft:egg",
"target": "rtdd:chickencoop_5",
"consume": 1,
"type": "switch",
"switchCd": 0.15,
"giveItem": None,
},
# ============================================================
# 二、鸡窝(取蛋) —— 空手右键取走鸡蛋,以掉落物形式生成
# 数量转换遵循"递增→递减"双向规则:放蛋1→2→3→4→5,取蛋5→4→3→2→1
# 取蛋后一律回到0蛋形态(chickencoop_0)
# ============================================================
# 【取5蛋】鸡窝_6(5蛋) → 鸡窝_1(0蛋),掉落5个鸡蛋
{
"source": "rtdd:chickencoop_5",
"item": None,
"target": "rtdd:chickencoop_0",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "minecraft:egg", "count": 5}],
"giveItem": None,
},
# 【取4蛋】鸡窝_5(4蛋) → 鸡窝_1(0蛋),掉落4个鸡蛋
{
"source": "rtdd:chickencoop_4",
"item": None,
"target": "rtdd:chickencoop_0",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "minecraft:egg", "count": 4}],
"giveItem": None,
},
# 【取3蛋】鸡窝_4(3蛋) → 鸡窝_1(0蛋),掉落3个鸡蛋
{
"source": "rtdd:chickencoop_3",
"item": None,
"target": "rtdd:chickencoop_0",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "minecraft:egg", "count": 3}],
"giveItem": None,
},
# 【取2蛋】鸡窝_3(2蛋) → 鸡窝_1(0蛋),掉落2个鸡蛋
{
"source": "rtdd:chickencoop_2",
"item": None,
"target": "rtdd:chickencoop_0",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "minecraft:egg", "count": 2}],
"giveItem": None,
},
# 【取1蛋】鸡窝_2(1蛋) → 鸡窝_1(0蛋),掉落1个鸡蛋
{
"source": "rtdd:chickencoop_1",
"item": None,
"target": "rtdd:chickencoop_0",
"consume": 0,
"type": "switch",
"dropItem": [{"name": "minecraft:egg", "count": 1}],
"giveItem": None,
},
# ============================================================
# 三、鸡饲料盆 —— 手持种子右键填充,计时变空
# 对应方块:chickenmanger_0(空)、chickenmanger_1(满)
# ============================================================
# 【加饲料】鸡饲料盆_1(空) → 鸡饲料盆_2(满),消耗16个种子
{
"source": "rtdd:chickenmanger_0",
"item": "minecraft:wheat_seeds",
"target": "rtdd:chickenmanger_1",
"consume": 16,
"type": "switch",
"giveItem": None,
},
# 【计时变空】鸡饲料盆_2(满) → 鸡饲料盆_1(空),计时60秒
# 这是一个自动转换:玩家填充后启动计时,到时间自动变空
{
"source": "rtdd:chickenmanger_1",
"item": None,
"target": "rtdd:chickenmanger_0",
"consume": 0,
"type": "timer",
"time": 60,
"giveItem": None,
},
# ============================================================
# 四、饲料槽-小麦 —— 手持小麦右键填充(消耗32个),计时逐级减少
# 对应方块:manger_0(空)、manger_3(满小麦)、manger_2(半量小麦)、manger_1(少量小麦)
# ============================================================
# 【加小麦】饲料槽_1(空) → 饲料槽_2(满小麦),消耗32个小麦
{
"source": "rtdd:manger_0",
"item": "minecraft:wheat",
"target": "rtdd:manger_3",
"consume": 32,
"type": "switch",
"giveItem": None,
},
# 【计时减少1】饲料槽_2(满小麦) → 饲料槽_3(半量小麦),计时60秒
{
"source": "rtdd:manger_3",
"item": None,
"target": "rtdd:manger_2",
"consume": 0,
"type": "timer",
"time": 60,
"giveItem": None,
},
# 【计时减少2】饲料槽_3(半量小麦) → 饲料槽_4(少量小麦),计时60秒
{
"source": "rtdd:manger_2",
"item": None,
"target": "rtdd:manger_1",
"consume": 0,
"type": "timer",
"time": 60,
"giveItem": None,
},
# 【计时减少3】饲料槽_4(少量小麦) → 饲料槽_1(空),计时60秒
{
"source": "rtdd:manger_1",
"item": None,
"target": "rtdd:manger_0",
"consume": 0,
"type": "timer",
"time": 60,
"giveItem": None,
},
# ============================================================
# 五、饲料槽-水 —— 手持水桶右键填充,水桶变空桶
# 对应方块:manger_0(空)、mangerwater(水槽)
# ============================================================
# 【加水】饲料槽_1(空) → 饲料槽_6(水槽),消耗水桶,获得空桶
{
"source": "rtdd:manger_0",
"item": "minecraft:water_bucket",
"target": "rtdd:mangerwater",
"consume": 1,
"type": "switch",
"giveItem": "minecraft:bucket",
"giveCount": 1,
},
# ============================================================
# 六、饲料槽-胡萝卜 —— 手持胡萝卜右键填充(消耗32个),计时逐级减少
# 对应方块:manger_0(空)、pigmanger_1(满胡萝卜)、pigmanger_2(半量胡萝卜)
# ============================================================
# 【加胡萝卜】饲料槽_1(空) → 饲料槽_7(满胡萝卜),消耗32个胡萝卜
{
"source": "rtdd:manger_0",
"item": "minecraft:carrot",
"target": "rtdd:pigmanger_1",
"consume": 32,
"type": "switch",
"giveItem": None,
},
# 【计时减少1】饲料槽_7(满胡萝卜) → 饲料槽_8(半量胡萝卜),计时60秒
{
"source": "rtdd:pigmanger_1",
"item": None,
"target": "rtdd:pigmanger_2",
"consume": 0,
"type": "timer",
"time": 60,
"giveItem": None,
},
# 【计时减少2】饲料槽_8(半量胡萝卜) → 饲料槽_1(空),计时60秒
{
"source": "rtdd:pigmanger_2",
"item": None,
"target": "rtdd:manger_0",
"consume": 0,
"type": "timer",
"time": 60,
"giveItem": None,
},
# ============================================================
# 七、粪便清理 —— 空手右键逐级清理,最后消失
# 对应方块:feces_3(一坨)、feces_2(半坨)、feces_1(仅痕迹)
# 【食用部分后续在总食用功能中实现,本次不实现】
# ============================================================
# 【清理1】粪便(一坨) → 粪便(半坨)
{
"source": "rtdd:feces_3",
"item": None,
"target": "rtdd:feces_2",
"consume": 0,
"type": "switch",
"giveItem": None,
},
# 【清理2】粪便(半坨) → 粪便(仅痕迹)
{
"source": "rtdd:feces_2",
"item": None,
"target": "rtdd:feces_1",
"consume": 0,
"type": "switch",
"giveItem": None,
},
# 【清理3】粪便(仅痕迹) → 消失(空气)
{
"source": "rtdd:feces_1",
"item": None,
"target": "minecraft:air",
"consume": 0,
"type": "switch",
"giveItem": None,
},
]
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""
动物养殖客户端(预留)
=====================
目前动物养殖功能的服务端逻辑已由 AnimalService + FeatureDispatcher 处理,
客户端暂不需要额外的 UI 工作。
如果以后需要饲料余量提示、产蛋进度条等 UI,可以在这里扩展。
"""
import mod.client.extraClientApi as clientApi
ClientSystem = clientApi.GetClientSystemCls()
class AnimalClient(ClientSystem):
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
print "===== AnimalClient init ====="
def Destroy(self):
print "===== AnimalClient Destroy ====="
@@ -0,0 +1,332 @@
# -*- coding: utf-8 -*-
"""
================================================================================
动物养殖引擎(服务端)
================================================================================
只处理"动物养殖"(xy_animal) 分类下的方块转换:鸡窝、饲料盆、饲料槽、粪便。
【与 BlockSwitchService / CookingService 的区别】
那些系统各自监听 ServerBlockUseEvent,右键时会互相跑一遍拦截。
本系统(方案A)改为"统一分发器"架构:
- FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统
- 本类通过 FeatureDispatcher.register(self) 把管辖的方块名注册进去
- 玩家右键方块时,FeatureDispatcher 用 O(1) 的 dict 直接找到本实例
- 本类只查自己的 AnimalData(ANIMAL_SWITCHES),不碰其他功能的数据库
→ 每个右键事件只查一次数据库,功能损耗最小
【本类不做的事】
- 不自己监听 ServerBlockUseEvent(由 FeatureDispatcher 统一转发 handleBlockUse)
- 不处理其他分类的方块(数据只来自 AnimalData)
【转换类型】
- type="switch" : 右键直接转换(放蛋/取蛋/填充饲料/清理粪便/加水)
- type="timer" : 计时自动转换(饲料盆/饲料槽随时间变空),
由本类用 AddTimer 实现,不依赖方块实体 tick
================================================================================
"""
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 ..AnimalData import ANIMAL_SWITCHES
# ---- 统一分发器(本类把自己注册进去,让分发器把右键事件转发过来) ----
from ...service.FeatureDispatcher import FeatureDispatcher
class AnimalService(BlueFoxServerSystem):
"""
动物养殖引擎
【核心数据结构】
animalRecipes = {
(方块名, 物品名): 规则dict, # O(1) 查找
}
animalBlockSet = {方块名, ...} # O(1) 拦截/注册用
animalTimers = {方块名: 规则dict} # 计时型规则的起始方块 → 规则
"""
def __init__(self, namespace, systemName):
"""
初始化:构建自己的转换规则字典,并注册到统一分发器
"""
super(AnimalService, self).__init__(namespace, systemName)
print "===== AnimalService init ====="
# ---- 构建动物养殖规则字典(O(1) 查找) ----
self.animalRecipes = {}
self.animalBlockSet = set()
self.animalTimers = {}
for sw in ANIMAL_SWITCHES:
key = (sw["source"], sw.get("item"))
self.animalRecipes[key] = sw
self.animalBlockSet.add(sw["source"])
# 收集计时型规则的"起始方块",方便转换后自动启动计时
if sw.get("type") == "timer":
self.animalTimers[sw["source"]] = sw
# ---- 计时去重表:key=(dim,x,y,z) -> 上次启动计时的时间戳 ----
self._timerSchedule = {}
# ---- 右键冷却表:防止连点瞬间触发多次 ----
self.cooldown = {}
# ---- 注册到统一分发器(把管辖方块交给 FeatureDispatcher 路由) ----
FeatureDispatcher.register(self)
# ============================================================================
# 给 FeatureDispatcher 的接口
# ============================================================================
def getFeatureBlocks(self):
"""
返回本功能管辖的所有方块名集合。
FeatureDispatcher 靠它构建 方块名→本实例 的 O(1) 路由表。
"""
return self.animalBlockSet
def getItemUseOnRules(self):
"""
返回本功能的"手持物品右键方块表面"规则(当前动物养殖没有,返回空)。
接口统一预留,方便以后扩展(如手持鸡蛋右键地面直接生蛋等)。
"""
return {}
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)
# ---- 查动物养殖规则(O(1)) ----
recipe = self.findRecipe(blockName, itemDict)
if not recipe:
return False
# ---- 冷却检查(防止连点,规则可自定义冷却时间 switchCd) ----
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
return True # 冷却中,虽然不执行但也算"处理了",避免默认行为
# ---- 计时型规则:启动/续计时,不立即转换 ----
if recipe.get("type") == "timer":
self.startTimer(key, recipe, dim, playerId)
return True
# ---- 切换型规则:执行具体转换 ----
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName)
return True
# ============================================================================
# 规则匹配(O(1) 字典查找)
# ============================================================================
def findRecipe(self, blockName, itemDict):
"""
在动物养殖字典中查找匹配规则:
1. 先精确匹配 (方块名, 物品名)
2. 没找到再匹配 (方块名, None) —— 空手也能触发的规则
"""
itemName = itemDict.get("newItemName") if itemDict else None
recipe = self.animalRecipes.get((blockName, itemName))
if recipe:
return recipe
recipe = self.animalRecipes.get((blockName, None))
if recipe:
return recipe
return None
# ============================================================================
# 切换型规则处理
# ============================================================================
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName):
"""
处理"右键直接转换"类规则。
正向(item 不为 None):放蛋/加饲料/加水 → 消耗物品 → 替换方块 → 给奖励 → 续计时
逆向(item 为 None) :取蛋/清理粪便/取回 → 掉落物品 → 替换方块
"""
# ---- 正向操作:往方块里放东西 ----
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.animalTimers:
nextTimer = self.animalTimers[recipe["target"]]
self.startTimer(key, nextTimer, dim, playerId)
return
# ---- 逆向操作:取东西/清理 ----
# 掉落物形式生成(取蛋、取下物品等),优先于 giveItem
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 一次性定时,不依赖方块实体tick)
# ============================================================================
def startTimer(self, key, recipe, dim, playerId=None):
"""
为计时型规则启动一次性定时器。
1 秒内的重复调用会被去重(防止多次右键重复计时)。
到时间后由 _timerDone 检查方块状态并执行转换。
"""
now = time.time()
last = self._timerSchedule.get(key, 0)
if now - last < 1.0:
return
self._timerSchedule[key] = now
t = recipe.get("time", 60)
# 用系统自带 AddTimer 一次性定时
self.AddTimer(t, 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 # 方块已变,放弃本次转换
# 替换方块
self.replaceBlock(pos, dim, recipe["target"], None)
# 若新方块仍是计时源 → 继续下一级计时(如 满→半→少→空)
nextRecipe = self.animalTimers.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):
"""
替换指定位置的方块。
newBlockName="minecraft:air" 表示清除该方块。
playerId 可为 None(计时回调时没有玩家),用 LevelId 创建组件。
"""
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):
"""
dropList 格式:[{"name": "minecraft:egg", "count": 5}, ...]
在方块上方生成掉落物实体,让它自然掉到地上。
"""
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 秒内只响应一次,防止连点刷物品。
放蛋等需要快速连点的规则可在数据里用 switchCd 调小冷却。
"""
now = time.time()
last = self.cooldown.get(key, 0)
if now - last < cd:
return False
self.cooldown[key] = now
return True
@@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
"""
================================================================================
统一事件分发器(服务端)—— 方案A核心
================================================================================
【为什么需要它】
以前每个功能(做菜/工具切换)都各自监听 ServerBlockUseEvent,
玩家每次右键,所有系统都会被触发一遍、各自跑一次数据库拦截判断,
功能越多损耗越大。
【它做什么】
FeatureDispatcher 是"唯一"监听右键相关事件的服务端系统:
- ServerBlockUseEvent 玩家右键方块
- ServerItemUseOnEvent 手持物品右键方块表面(如放置生鸡肉)
- ServerItemTryUseEvent 拦截喝药水/吃食物等默认行为
各功能服务在初始化时通过 FeatureDispatcher.register(self) 上报:
- 自己管辖的方块名集合 → 构建 方块名→服务 的 O(1) 路由表
- 自己处理的手持物品 → 构建 物品名→服务 的 O(1) 路由表
事件进来后,分发器先用 blockName / itemName 在路由表里 O(1) 定位到
唯一的那个功能服务,再调用它的 handleBlockUse / handleItemUseOn。
→ 每个右键事件只命中一个数据库,其余功能的数据库根本不会被查询。
【如何新增一个功能】
1. 新建 xxx/xxxData.py 写转换规则
2. 新建 xxx/service/XxxService.py(继承 BlueFoxServerSystem):
- __init__ 里构建自己的规则字典
- 实现 getFeatureBlocks() / getItemUseOnRules()
- 实现 handleBlockUse(args) / handleItemUseOn(args)
- 调用 FeatureDispatcher.register(self)
3. 在 modMain.py import 这个服务类即可
================================================================================
"""
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
comp = serverApi.GetEngineCompFactory() # 组件工厂
# ---- 继承 BlueFox 框架的系统基类 ----
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
class FeatureDispatcher(BlueFoxServerSystem):
"""
统一事件分发器
【类级注册表(所有功能服务共享同一份路由表)】
blockOwner = {方块名: 功能服务实例}
blockSet = {方块名} # 全部功能方块集合
itemUseOnOwner = {物品名: 功能服务实例}
itemUseOnSet = {物品名} # 全部"手持物品右键方块表面"集合
"""
# ---- 类级共享路由表(用类变量,任何实例都能读写) ----
blockOwner = {}
blockSet = set()
itemUseOnOwner = {}
itemUseOnSet = set()
@classmethod
def register(cls, service):
"""
功能服务初始化时调用,把管辖的方块和物品注册进路由表。
同一方块/物品只会归属最后一个注册的服务(一般不会冲突)。
"""
# 右键方块路由:方块名 → 服务
for block in service.getFeatureBlocks():
cls.blockOwner[block] = service
cls.blockSet.add(block)
# 手持物品右键方块表面路由:物品名 → 服务
for itemName in service.getItemUseOnRules():
cls.itemUseOnOwner[itemName] = service
cls.itemUseOnSet.add(itemName)
def __init__(self, namespace, systemName):
"""
初始化:注册三个事件监听。
注意:本类自身不注册任何功能方块,只负责把事件分发给已注册的服务。
"""
super(FeatureDispatcher, self).__init__(namespace, systemName)
print "===== FeatureDispatcher init ====="
self._cancelItemUse = {} # 将要取消下一次物品使用的玩家ID集合(防止喝药水等)
self.ListenEvent()
def Destroy(self):
print "===== FeatureDispatcher Destroy ====="
self.UnListenEvent()
# ============================================================================
# 事件注册与注销
# ============================================================================
def ListenEvent(self):
"""
注册三个事件:
1. ServerBlockUseEvent —— 玩家右键方块(所有功能的右键转换入口)
2. ServerItemUseOnEvent —— 手持物品右键方块表面(如放置生鸡肉)
3. ServerItemTryUseEvent —— 玩家尝试使用物品(拦截喝药水/吃食物等默认行为)
"""
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemUseOnEvent", self, self.OnItemUseOn)
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemTryUseEvent", self, self.OnItemTryUse)
def UnListenEvent(self):
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemUseOnEvent", self, self.OnItemUseOn)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemTryUseEvent", self, self.OnItemTryUse)
# ============================================================================
# 主入口1:玩家右键方块
# ============================================================================
def OnBlockUse(self, args):
"""
玩家右键方块时触发。
用被右键的方块名 O(1) 定位到唯一的功能服务,转发给它的 handleBlockUse。
服务返回 True = 处理成功 → 取消默认行为(如防止喝药水),并标记拦截下一次物品使用
服务返回 False = 当前组合无匹配规则 → 交还默认行为
"""
blockName = args.get("blockName")
service = self.blockOwner.get(blockName)
if not service:
# 这个方块不属于任何已注册的功能 → 交给原版/其他模组处理
return
handled = service.handleBlockUse(args)
if handled:
args['cancel'] = True
playerId = args.get("playerId")
self._cancelItemUse[playerId] = True
# ============================================================================
# 主入口2:手持物品右键方块表面
# ============================================================================
def OnItemUseOn(self, args):
"""
手持物品右键某个方块表面时触发(如手持原版生鸡肉右键地面)。
用手持物品名 O(1) 定位到唯一的功能服务,转发给它的 handleItemUseOn。
注意:取消"使用物品于方块"的字段是 ret(不是 cancel),
官方要求双端设置才能真正取消默认行为。本分发器已在服务端设 ret=True,
服务端不会执行默认放置/使用逻辑;客户端如需完美取消动画,
可再增加一个监听 ClientItemUseOnEvent 的客户端配对系统。
"""
itemName = args.get("itemName")
service = self.itemUseOnOwner.get(itemName)
if not service:
return
handled = service.handleItemUseOn(args)
if handled:
args['ret'] = True
playerId = args.get("playerId")
self._cancelItemUse[playerId] = True
# ============================================================================
# 主入口3:拦截物品使用(防止喝药水/吃食物等默认行为)
# ============================================================================
def OnItemTryUse(self, args):
"""
玩家尝试使用物品(喝药水、吃食物等)时触发。
如果该玩家刚被标记为"取消下一次物品使用"(_cancelItemUse),
就取消这次默认行为,避免右键转换方块时误喝药水。
"""
playerId = args.get("playerId")
if self._cancelItemUse.pop(playerId, False):
args['cancel'] = True
@@ -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
+5 -4
View File
@@ -47,7 +47,7 @@ todo : 手持种子 消耗16个种子
8. 鸡饲料盆_2(满) -> 鸡饲料盆_1(空) 8. 鸡饲料盆_2(满) -> 鸡饲料盆_1(空)
转换条件:[鸡进食后计时变空]【机制待确认】 转换条件:[鸡进食后计时变空]【机制待确认】
todo : 计时变换 先三分钟 todo : 计时变换 先一分钟
======================================== ========================================
三、饲料槽(牛羊等) 三、饲料槽(牛羊等)
@@ -58,7 +58,7 @@ todo : 手持小麦 消耗32个小麦
10. 饲料槽_2(满小麦) -> 饲料槽_3(半量小麦) -> 饲料槽_4(少量小麦) -> 饲料槽_1(空) 10. 饲料槽_2(满小麦) -> 饲料槽_3(半量小麦) -> 饲料槽_4(少量小麦) -> 饲料槽_1(空)
转换条件:[动物进食后计时逐级减少]【机制待确认】 转换条件:[动物进食后计时逐级减少]【机制待确认】
todo : 计时变换 先三分钟 逐级都是 计时变化 todo : 计时变换 先一分钟 逐级都是 计时变化
11. 饲料槽_1(空) -> 饲料槽_6(水槽) 11. 饲料槽_1(空) -> 饲料槽_6(水槽)
转换条件:[手持 水桶 右键 进行转换,消耗水桶]【操作方式待确认】 转换条件:[手持 水桶 右键 进行转换,消耗水桶]【操作方式待确认】
@@ -70,14 +70,15 @@ todo : 手持胡萝卜 消耗32个胡萝卜
13. 饲料槽_7(满胡萝卜) -> 饲料槽_8(半量胡萝卜) -> 饲料槽_1(空) 13. 饲料槽_7(满胡萝卜) -> 饲料槽_8(半量胡萝卜) -> 饲料槽_1(空)
转换条件:[动物进食后计时逐级减少]【机制待确认】 转换条件:[动物进食后计时逐级减少]【机制待确认】
todo : 计时变换 先三分钟 逐级都是 计时变化 todo : 计时变换 先一分钟 逐级都是 计时变化
======================================== ========================================
四、粪便 四、粪便
14. 粪便(一坨) -> 粪便(半坨) -> 粪便(仅痕迹) -> 无 14. 粪便(一坨) -> 粪便(半坨) -> 粪便(仅痕迹) -> 无
转换条件:[右键 进行转换,取走/清理粪便] 转换条件:[右键 进行转换,取走/清理粪便]
机制 : 右键取走一个等级,逐级清理至消失(暂定,可先不管) todo 这玩意也可食用
【食用部分后续在总食用功能中实现,本次不实现】
======================================== ========================================
五、其他静态方块 五、其他静态方块
+6 -1
View File
@@ -40,7 +40,8 @@
转换条件:[放到烧烤架上,手持右键 2 次进行转换] 转换条件:[放到烧烤架上,手持右键 2 次进行转换]
机制: 放在烧烤架上,右键第1次:生->半熟,右键第2次:半熟->熟(不计时) 机制: 放在烧烤架上,右键第1次:生->半熟,右键第2次:半熟->熟(不计时)
todo 熟的烤串也都可以直接右键食用 todo 熟的烤串也都可以直接右键食用
【食用部分后续在总食用功能中实现,本次不实现】
======================================== ========================================
三、烤鸡(上支杆 + 篝火烤,非烧烤架) 三、烤鸡(上支杆 + 篝火烤,非烧烤架)
======================================== ========================================
@@ -63,6 +64,7 @@ todo 熟的烤串也都可以直接右键食用
12. 烤鸡(整只) -> 烤鸡(3/4只) -> 烤鸡(半只) -> 烤鸡(1/4只) -> (吃完消失) 12. 烤鸡(整只) -> 烤鸡(3/4只) -> 烤鸡(半只) -> 烤鸡(1/4只) -> (吃完消失)
转换条件:[吃东西(右键食用)转换,每吃一份降一级] 转换条件:[吃东西(右键食用)转换,每吃一份降一级]
机制: 右键食用,吃一份: 整只->3/4->半->1/4,最后一份吃完方块消失 机制: 右键食用,吃一份: 整只->3/4->半->1/4,最后一份吃完方块消失
【食用部分后续在总食用功能中实现,本次不实现】
======================================== ========================================
四、烤乳猪(上支杆 + 篝火烤,非烧烤架) 四、烤乳猪(上支杆 + 篝火烤,非烧烤架)
@@ -82,6 +84,7 @@ todo 熟的烤串也都可以直接右键食用
16. 烤乳猪(整只/6份) -> 烤乳猪(5/6只) -> 烤乳猪(4/6只) -> 烤乳猪(3/6只) -> 烤乳猪(2/6只) -> 烤乳猪(1/6只) -> (吃完消失) 16. 烤乳猪(整只/6份) -> 烤乳猪(5/6只) -> 烤乳猪(4/6只) -> 烤乳猪(3/6只) -> 烤乳猪(2/6只) -> 烤乳猪(1/6只) -> (吃完消失)
转换条件:[吃东西(右键食用)转换,每吃一份,剩余份数递减] 转换条件:[吃东西(右键食用)转换,每吃一份,剩余份数递减]
机制: 右键食用,共6份,吃一份剩余递减1/6: 整只->5/6->4/6->3/6->2/6->1/6,最后一份吃完方块消失 机制: 右键食用,共6份,吃一份剩余递减1/6: 整只->5/6->4/6->3/6->2/6->1/6,最后一份吃完方块消失
【食用部分后续在总食用功能中实现,本次不实现】
======================================== ========================================
五、烤鱼(上支杆 + 篝火烤,非烧烤架) 五、烤鱼(上支杆 + 篝火烤,非烧烤架)
@@ -94,6 +97,7 @@ todo 熟的烤串也都可以直接右键食用
转换条件:[支杆下方篝火,计时自动转换] 转换条件:[支杆下方篝火,计时自动转换]
机制: 支杆下方篝火计时约60秒烤熟 机制: 支杆下方篝火计时约60秒烤熟
todo 烤鳕鱼(熟)可以直接右键食用 todo 烤鳕鱼(熟)可以直接右键食用
【食用部分后续在总食用功能中实现,本次不实现】
19. 鲑鱼 -> 烤鲑鱼(生) 19. 鲑鱼 -> 烤鲑鱼(生)
转换条件:[手持 鲑鱼 右键 支杆(b_34) 进行转换,消耗1条] 转换条件:[手持 鲑鱼 右键 支杆(b_34) 进行转换,消耗1条]
机制: 手持鲑鱼右键支杆,上杆待烤,变为烤鲑鱼(生) 机制: 手持鲑鱼右键支杆,上杆待烤,变为烤鲑鱼(生)
@@ -102,6 +106,7 @@ todo 烤鳕鱼(熟)可以直接右键食用
转换条件:[支杆下方篝火,计时自动转换] 转换条件:[支杆下方篝火,计时自动转换]
机制: 支杆下方篝火计时约60秒烤熟 机制: 支杆下方篝火计时约60秒烤熟
todo 烤鲑鱼(熟)可以直接右键食用 todo 烤鲑鱼(熟)可以直接右键食用
【食用部分后续在总食用功能中实现,本次不实现】
======================================== ========================================
六、方块归属说明(均有转换,无静态成品) 六、方块归属说明(均有转换,无静态成品)
======================================== ========================================