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