# -*- 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() # 从配方数据文件导入所有配方 from ..RecipeData import RECIPES class CookingService(ServerSystem): """ 通用烹饪引擎 内部数据: 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): """ 在 RECIPES 列表里找匹配的配方。 匹配条件:source 对上 + item 对上(或 item 为 None) """ itemName = itemDict.get("newItemName") if itemDict else None for recipe in RECIPES: if recipe["source"] != blockName: continue # item 为 None 表示不限制物品类型 if recipe["item"] is not None and recipe["item"] != itemName: continue return recipe return None # ==================== 配方执行(普通替换) ==================== def _executeRecipe(self, recipe, playerId, pos, dim, itemDict): """ 执行一条普通配方:检查条件 → 消耗物品 → 替换方块 → 给物品 → 播声音 """ # 1. 检查附加条件 if not self._checkConditions(recipe, playerId, pos, dim, itemDict): return # 2. 消耗物品(扣除数量、去掉油标签) if recipe.get("consume", 1) > 0: self._consumeItem(playerId, itemDict, recipe.get("consume", 1)) if recipe.get("needOil"): self._removeOil(itemDict, playerId) # 3. 替换方块 if recipe.get("target"): self._replaceBlock(pos, dim, recipe["target"], playerId) # 4. 给物品 if recipe.get("giveItem"): self._giveItem(playerId, recipe["giveItem"]) # 5. 播放声音 if recipe.get("sound"): self._playSound(playerId, recipe["sound"], recipe.get("soundPitch", 1.0)) # ==================== 翻炒处理 ==================== def _handleStir(self, key, recipe, playerId, pos, dim, itemDict): """ 处理需要翻炒的配方。 翻炒逻辑: 1. 检查冷却时间 2. 没到冷却 → 提示"这时候翻动会不好熟的!" 3. 到了冷却 → 翻炒次数+1 → 播声音+挥剑动画 4. 翻够次数 → 执行替换或标记熟了 """ now = time.time() data = self.stirData.get(key, {"count": 0, "lastTime": 0, "finished": False, "dropIfDone": recipe.get("dropIfDone", []), "dropIfNot": recipe.get("dropIfNot", [])}) # 冷却检查 if now - data["lastTime"] < recipe.get("stirCd", 1.0): if recipe.get("failMsg"): comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["failMsg"]) return # 翻炒次数+1 data["count"] += 1 data["lastTime"] = now self.stirData[key] = data # 播放声音和挥剑动画 if recipe.get("sound"): self._playSound(playerId, recipe["sound"], recipe.get("soundPitch", 1.0)) self._playAttack(playerId) # 判断是否翻够了 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): """ 检查配方的附加条件。 返回 True = 通过,False = 不通过。 """ # 检查下面是否有指定方块(如营火) 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): """消耗玩家手里的物品(减少数量)""" 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("auxValue", 0)}) def _replaceBlock(self, pos, dim, newBlockName, playerId): """替换方块(一行搞定!)""" comp.CreateBlockInfo(playerId).SetBlockNew( pos, {"name": newBlockName}, 0, dim) def _giveItem(self, playerId, itemName, count=1): """给玩家背包里塞一个物品""" comp.CreateItem(playerId).SpawnItemToPlayerInv( {"itemName": itemName, "count": count, "auxValue": 0}, playerId) def _playSound(self, playerId, soundName, pitch=1.0): """在玩家位置播放声音""" pos = comp.CreatePlayer(playerId).GetPos() comp.CreateGame(playerId).PlaySound( soundName, pos, pitch, 1.0, False) def _playAttack(self, playerId): """让玩家播放攻击动画(挥剑)""" # 通过引擎的 Attack 组件触发动画 try: comp.CreatePlayer(playerId).Attack() except: pass # ==================== 油标签操作 ==================== def _getItemOil(self, itemDict): """读取物品上的油标签""" try: itemComp = comp.CreateItem(serverApi.GetLevelId()) userData = itemComp.GetItemUserData(itemDict, "rtdd") return userData.get("oil", False) if userData else False except: return False def _removeOil(self, itemDict, playerId): """去掉物品上的油标签""" try: itemComp = comp.CreateItem(serverApi.GetLevelId()) userData = itemComp.GetItemUserData(itemDict, "rtdd") if userData: userData["oil"] = False itemComp.SetItemUserData(itemDict, "rtdd", userData) # 更新玩家手里的物品 comp.CreateItem(playerId).SetEntityItem( serverApi.GetMinecraftEnum().ItemPosType.CARRIED, {"itemName": itemDict["newItemName"], "count": itemDict.get("count", 1), "auxValue": itemDict.get("auxValue", 0), "userData": {"rtdd": userData}}) except: pass # ==================== 拆方块处理 ==================== def OnDestroy(self, args): """ 玩家拆方块时触发。 如果有翻炒数据且标记了熟了 → 掉落熟的东西 如果没熟 → 掉落生的东西 """ blockName = args.get("blockName") 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)