Files
rtdd/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/CookingService.py
T
2026-07-21 16:20:52 +08:00

282 lines
10 KiB
Python
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.
# -*- coding: utf-8 -*-
"""
通用烹饪引擎(服务端)
=====================
读取 RecipeData.py 里的配方,自动处理所有"右键方块→消耗物品→替换方块"的逻辑。
设计思路:
- 不再为每个方块写一个类,而是写一个通用引擎
- 所有配方存在 RecipeData.py,引擎读取配方来执行
- 加新菜 = 在 RecipeData.py 加一条配方,这里不用改一行代码
用法:
在 modMain.py 里注册这个系统即可:
serverApi.RegisterSystem("rtdd", "CookingService",
"Script_NeteaseModousIY5aSF.ceshi.service.CookingService.CookingService")
"""
import time
import mod.server.extraServerApi as serverApi
ServerSystem = serverApi.GetServerSystemCls()
comp = serverApi.GetEngineCompFactory()
# 继承 BlueFoxServerSystem 后,LoadMod() 会自动发现并注册本系统
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
# 从配方数据文件导入所有配方
from ..RecipeData import RECIPES
def getUserDataField(itemDict, *keys):
"""
读取物品 dict 里的 userData 嵌套字段(纯本地操作,不调API)。
例如 getUserDataField(itemDict, 'rtdd', 'oil') → 返回 itemDict['userData']['rtdd']['oil']
如果中间任何一层的 key 不存在,自动创建空 dict。
"""
itemDict['userData'] = itemDict.get('userData', {})
if itemDict.get('userData') is None:
itemDict['userData'] = {}
node = itemDict['userData']
for key in keys:
node[key] = node.get(key, {})
node = node[key]
return node
class CookingService(BlueFoxServerSystem):
"""
通用烹饪引擎
内部数据:
stirData = {
(dim, x, y, z): {
"count": int, # 已经翻炒了多少次
"lastTime": float, # 上次翻炒的时间戳
"finished": bool, # 是否已经熟了
"dropIfDone": list, # 熟了掉落什么
"dropIfNot": list, # 没熟掉落什么
}
}
"""
def __init__(self, namespace, systemName):
super(CookingService, self).__init__(namespace, systemName)
print "===== CookingService init ====="
self.stirData = {} # 翻炒数据字典
self.ListenEvent()
def Destroy(self):
print "===== CookingService Destroy ====="
self.UnListenEvent()
def ListenEvent(self):
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerPlayerTryDestroyBlockEvent", self, self.OnDestroy)
def UnListenEvent(self):
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerPlayerTryDestroyBlockEvent", self, self.OnDestroy)
# ==================== 主入口:玩家右键方块 ====================
def OnBlockUse(self, args):
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
if recipe.get("stirCount"):
self.handleStir(key, recipe, playerId, pos, dim, itemDict)
return
self.executeRecipe(recipe, playerId, pos, dim, itemDict)
# ==================== 配方匹配 ====================
def findRecipe(self, blockName, itemDict):
itemName = itemDict.get("newItemName") if itemDict else None
for recipe in RECIPES:
if recipe["source"] != blockName:
continue
if recipe["item"] is not None and recipe["item"] != itemName:
continue
return recipe
return None
# ==================== 配方执行(普通替换) ====================
def executeRecipe(self, recipe, playerId, pos, dim, itemDict):
if not self.checkConditions(recipe, playerId, pos, dim, itemDict):
return
if recipe.get("consume", 1) > 0:
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
if recipe.get("needOil"):
self.removeOil(itemDict, playerId)
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
if recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"])
# ==================== 翻炒处理 ====================
def handleStir(self, key, recipe, playerId, pos, dim, itemDict):
now = time.time()
data = self.stirData.get(key) or {
"count": 0, "lastTime": 0.0, "finished": False,
"dropIfDone": recipe.get("dropIfDone", []),
"dropIfNot": recipe.get("dropIfNot", [])
}
# 冷却检查
if now - data["lastTime"] < recipe.get("stirCd", 1.0):
return
# 翻炒次数+1
data["count"] += 1
data["lastTime"] = now
self.stirData[key] = data
# 提示进度
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "翻炒中... %s/%s" % (data["count"], recipe["stirCount"]))
# 判断是否翻够了
if data["count"] >= recipe["stirCount"]:
if recipe.get("onDone") == "finish":
data["finished"] = True
self.stirData[key] = data
if recipe.get("doneMsg"):
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["doneMsg"])
elif recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
self.stirData.pop(key, None)
# ==================== 条件检查 ====================
def checkConditions(self, recipe, playerId, pos, dim, itemDict):
# 检查下面是否有指定方块
if recipe.get("below"):
belowPos = (pos[0], pos[1] - 1, pos[2])
blockComp = comp.CreateBlockInfo(playerId)
try:
belowBlock = blockComp.GetBlockNew(belowPos, dim)
if belowBlock.get("name") != recipe["below"]:
return False
except:
return False
# 检查物品是否有油标签
if recipe.get("needOil") and itemDict:
if not self.getItemOil(itemDict):
return False
return True
# ==================== 工具方法 ====================
def getCarriedItem(self, playerId):
"""获取玩家主手拿的物品"""
itemComp = comp.CreateItem(playerId)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
def consumeItem(self, playerId, itemDict, amount=1):
"""
消耗玩家手里的物品(减少数量)。
注意:SetEntityItem 接收的 key 是 itemName/auxValue/count,
而 GetPlayerItem 返回的 key 是 newItemName/newAuxValue/count。
"""
count = itemDict.get("count", 0)
if count <= amount:
comp.CreateItem(playerId).SetInvItemNum(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 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):
"""替换方块(对照 BlockBlock.py 标准写法:命名参数 + 带 aux)"""
comp.CreateBlockInfo(playerId).SetBlockNew(
pos,
blockDict={"name": newBlockName, "aux": 0},
oldBlockHandling=0,
dimensionId=dim)
def giveItem(self, playerId, itemName, count=1):
"""给玩家背包里塞一个物品"""
comp.CreateItem(playerId).SpawnItemToPlayerInv(
{"itemName": itemName, "count": count, "auxValue": 0}, playerId)
# ==================== 油标签操作(纯本地 dict 操作,不调 API) ====================
def getItemOil(self, itemDict):
"""读取物品上的油标签"""
try:
return getUserDataField(itemDict, 'rtdd').get('oil', False)
except:
return False
def removeOil(self, itemDict, playerId):
"""去掉物品上的油标签,并同步到玩家手里"""
try:
getUserDataField(itemDict, 'rtdd')['oil'] = False
# 更新玩家手里的物品(userData 跟着 itemDict 一起写回去)
itemComp = comp.CreateItem(playerId)
itemComp.SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict.get("count", 1),
"auxValue": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {})})
except:
pass
# ==================== 拆方块处理 ====================
def OnDestroy(self, args):
"""
玩家拆方块时触发。
如果有翻炒数据且标记了熟了 → 掉落熟的东西
如果没熟 → 掉落生的东西
"""
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
key = (dim, x, y, z)
data = self.stirData.get(key)
if not data:
return
if data.get("finished"):
dropItems = data.get("dropIfDone", [])
else:
dropItems = data.get("dropIfNot", [])
for itemName in dropItems:
self.giveItem(playerId, itemName)
self.stirData.pop(key, None)