# -*- 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)