338 lines
13 KiB
Python
338 lines
13 KiB
Python
# -*- 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
|
||
|
||
# 从内存或方块数据加载翻炒进度
|
||
stirInfo = self.stirData.get(key)
|
||
if stirInfo is None:
|
||
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 {
|
||
"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
|
||
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
|
||
|
||
# ==================== 翻炒数据持久化 ====================
|
||
|
||
def loadStirData(self, playerId, pos, dim, recipe):
|
||
"""从方块上读取之前保存的翻炒进度"""
|
||
try:
|
||
blockComp = comp.CreateBlockInfo(playerId)
|
||
blockData = blockComp.GetBlockEntityData(pos, dim)
|
||
if blockData and "rtdd_sc" in blockData:
|
||
return {
|
||
"count": blockData.get("rtdd_sc", 0),
|
||
"lastTime": blockData.get("rtdd_slt", 0.0),
|
||
"finished": blockData.get("rtdd_sf", False),
|
||
"dropIfDone": recipe.get("dropIfDone", []),
|
||
"dropIfNot": recipe.get("dropIfNot", []),
|
||
}
|
||
except:
|
||
pass
|
||
return {}
|
||
|
||
def saveStirData(self, playerId, pos, dim, data):
|
||
"""把翻炒进度保存到方块上,退游戏再进也不会丢"""
|
||
try:
|
||
comp.CreateBlockInfo(playerId).SetBlockEntityData(pos, dim, {
|
||
"rtdd_sc": data["count"],
|
||
"rtdd_slt": data["lastTime"],
|
||
"rtdd_sf": data["finished"],
|
||
})
|
||
except:
|
||
pass
|
||
|
||
def clearStirData(self, key, playerId, pos, dim):
|
||
"""清除内存和方块上的翻炒数据"""
|
||
self.stirData.pop(key, None)
|
||
try:
|
||
comp.CreateBlockInfo(playerId).SetBlockEntityData(pos, dim, {})
|
||
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:
|
||
self.giveItem(playerId, itemName)
|
||
|
||
self.clearStirData(key, playerId, (x, y, z), dim) |