Files
rtdd/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/CookingService.py
T

356 lines
13 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 ...BlueFoxTools.ToolsConfig import LevelId
# 方块实体数据操作组件(和老系统 Function.py 用的同一个 LevelId,操作同一份数据)
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
# 从配方数据文件导入所有配方
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
# 从内存或方块数据加载翻炒进度
stirInfo = self.stirData.get(key, {})
if not stirInfo:
# 内存里没有,尝试从方块实体数据加载(服务器重启后恢复数据)
stirInfo = self.loadStirData(playerId, pos, dim, recipe)
if stirInfo:
self.stirData[key] = stirInfo
if recipe.get("needFinished") is True and not stirInfo.get("finished"):
return
if recipe.get("needFinished") is False and stirInfo.get("finished"):
return
if recipe.get("stirCount"):
self.handleStir(key, recipe, playerId, pos, dim, itemDict)
return
self.executeRecipe(recipe, playerId, pos, dim, itemDict, key)
# ==================== 配方匹配 ====================
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, key):
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)
self.clearStirData(key, playerId, pos, dim)
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 self.loadStirData(playerId, pos, dim, recipe)
if not data:
data = {
"count": 0, "lastTime": 0.0, "finished": False,
"dropIfDone": recipe.get("dropIfDone", []),
"dropIfNot": recipe.get("dropIfNot", [])
}
self.stirData[key] = data
# 冷却检查
if now - data["lastTime"] < recipe.get("stirCd", 1.0):
return
# 翻炒次数+1
data["count"] += 1
data["lastTime"] = now
self.stirData[key] = data
self.saveStirData(playerId, pos, dim, 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
self.saveStirData(playerId, pos, dim, data)
if recipe.get("doneMsg"):
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["doneMsg"])
elif recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
self.clearStirData(key, playerId, pos, dim)
# ==================== 条件检查 ====================
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
# ==================== 翻炒数据持久化(存方块里,不存JSON文件) ====================
def loadStirData(self, playerId, pos, dim, recipe):
"""
从方块的实体数据里读取翻炒进度。
注意:GetBlockEntityData 返回的是 BlockEntityData 对象,不是普通 dict,
必须用方括号 [] 取值,不能用 .get()。
"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
stirCount = blockData["rtdd_sc"]
return {
"count": stirCount,
"lastTime": blockData["rtdd_slt"],
"finished": blockData["rtdd_sf"],
"dropIfDone": recipe.get("dropIfDone", []),
"dropIfNot": recipe.get("dropIfNot", []),
}
except:
return {}
def saveStirData(self, playerId, pos, dim, data):
"""
把翻炒进度写入方块的实体数据。
用方括号 [] 直接赋值,网易API会自动保存。
"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
blockData["rtdd_sc"] = data["count"]
blockData["rtdd_slt"] = data["lastTime"]
blockData["rtdd_sf"] = data["finished"]
except:
pass
def clearStirData(self, key, playerId, pos, dim):
"""
清除方块上的翻炒数据(方块被替换或拆掉时调用)。
"""
self.stirData.pop(key, None)
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
blockData["rtdd_sc"] = 0
blockData["rtdd_slt"] = 0.0
blockData["rtdd_sf"] = False
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:
# 尝试从方块数据加载
data = self.loadStirData(playerId, (x, y, z), dim, {})
if not data:
return
if data.get("finished"):
dropItems = data.get("dropIfDone", [])
else:
dropItems = data.get("dropIfNot", [])
for itemName in dropItems:
itemDict = {"itemName": itemName, "count": 1, "auxValue": 0}
self.CreateEngineItemEntity(itemDict, dim, (x, y + 0.5, z))
self.clearStirData(key, playerId, (x, y, z), dim)