From f79bd3f0e796116bd70a0157b2c2a1d4a5ff3b5c Mon Sep 17 00:00:00 2001 From: wenhe <1046926120@qq.com> Date: Tue, 21 Jul 2026 12:07:40 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E6=96=B9=20=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E6=96=B9=E5=9D=97ceshi1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ceshi/RecipeData.py | 85 +++++ .../ceshi/__init__.py | 0 .../ceshi/client/CookingClient.py | 19 ++ .../ceshi/client/__init__.py | 0 .../ceshi/service/CookingService.py | 316 ++++++++++++++++++ .../ceshi/service/__init__.py | 0 6 files changed, 420 insertions(+) create mode 100644 behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/RecipeData.py create mode 100644 behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/__init__.py create mode 100644 behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/CookingClient.py create mode 100644 behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/__init__.py create mode 100644 behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/CookingService.py create mode 100644 behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/__init__.py diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/RecipeData.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/RecipeData.py new file mode 100644 index 0000000..90673d6 --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/RecipeData.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +""" +配方数据文件 - 所有"右键方块→消耗物品→替换方块"的配方都在这里 +=============================================================== +每加一道新菜,只需要在这里加一条配方,不用写任何Python代码。 + +配方字典字段说明([] = 可选,{} = 特殊类型): +{ + "source": str, # 被右键的方块ID(必填) + "item": str|None, # 玩家手里必须拿的物品,None=空手也行 + [below]: str, # 方块下面必须是什么 + [needOil]: bool, # 物品必须有"油标签" + [target]: str|None, # 替换成什么方块,None=不替换 + [consume]: int, # 消耗物品数量,0=不消耗(默认1) + [giveItem]: str, # 额外给玩家什么物品 + [sound]: str, # 播放什么声音 + [soundPitch]: float, # 声音频率(默认1.0) + [stirCount]: int, # 需要翻炒几次才变身 + [stirCd]: float, # 翻炒冷却时间(秒) + [onDone]: str, # 完成动作:"finish"=标记熟了不替换 + [doneMsg]: str, # 完成后的提示消息 + [failMsg]: str, # 冷却中/失败时的提示 + [dropIfDone]: list, # 熟了后拆掉掉落什么 + [dropIfNot]: list, # 没熟拆掉掉落什么 +} +""" + +# ============================================================ +# 炒菜示例:胡萝卜牛肉(完整一条链) +# ============================================================ +# 流程:空锅 → 加油 → 放牛肉 → 翻炒 → 熟了 → 拆掉拿熟牛肉 +RECIPES = [ + + # ===== 步骤1:空炒锅 + 有油的物品 → 带油煎锅 ===== + { + "source": "rtdd:wok", + "item": None, + "below": "minecraft:campfire", + "needOil": True, + "target": "rtdd:cookinggarlic", + "consume": 0, + "sound": "random.fizz", + "soundPitch": 2.4, + }, + + # ===== 步骤2:带油煎锅 + 生牛肉 → 胡萝卜牛肉锅 ===== + { + "source": "rtdd:cookinggarlic", + "item": "minecraft:beef", + "target": "rtdd:carrotandbeef", + "consume": 1, + "sound": "random.fizz", + "soundPitch": 0.8, + }, + + # ===== 步骤3:胡萝卜牛肉锅 + 木剑翻炒 → 第二阶段 ===== + { + "source": "rtdd:carrotandbeef", + "item": "minecraft:wooden_sword", + "target": "rtdd:carrotandbeef_1", + "consume": 0, + "stirCount": 5, + "stirCd": 1.2, + "sound": "random.fizz", + "soundPitch": 2.1, + "failMsg": "这时候翻动会不好熟的!", + }, + + # ===== 步骤4:第二阶段 + 木剑翻炒 → 标记熟了 ===== + { + "source": "rtdd:carrotandbeef_1", + "item": "minecraft:wooden_sword", + "target": None, + "consume": 0, + "stirCount": 3, + "stirCd": 0.8, + "sound": "random.fizz", + "soundPitch": 2.1, + "onDone": "finish", + "doneMsg": "出锅啦!拿盘子盛出来", + "failMsg": "这时候翻动会不好熟的!", + "dropIfDone": ["minecraft:cooked_beef", "rtdd:wok"], + "dropIfNot": ["minecraft:beef", "rtdd:wok"], + }, +] \ No newline at end of file diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/CookingClient.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/CookingClient.py new file mode 100644 index 0000000..85f0020 --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/CookingClient.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +烹饪客户端(预留) +================= +目前客户端不需要做额外的UI工作。 +如果以后需要进度条、翻炒提示等UI,可以在这里扩展。 +""" +import mod.client.extraClientApi as clientApi + +ClientSystem = clientApi.GetClientSystemCls() + + +class CookingClient(ClientSystem): + def __init__(self, namespace, systemName): + ClientSystem.__init__(self, namespace, systemName) + print "===== CookingClient init =====" + + def Destroy(self): + print "===== CookingClient Destroy =====" \ No newline at end of file diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/CookingService.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/CookingService.py new file mode 100644 index 0000000..b331ee9 --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/CookingService.py @@ -0,0 +1,316 @@ +# -*- 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) \ No newline at end of file diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/__init__.py new file mode 100644 index 0000000..e69de29