diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/modMain.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/modMain.py index abc1995..38526be 100644 --- a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/modMain.py +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/modMain.py @@ -1,6 +1,9 @@ from BlueFoxTools.ModLoader import * from xiangyemod.service.CookingService import CookingService from xiangyemod.service.BlockSwitchService import BlockSwitchService +from xiangyemod.service.FeatureDispatcher import FeatureDispatcher +from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService +from xiangyemod.shaokao.service.BarbecueService import BarbecueService if not serverApi.IsInServer(): pass diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/AnimalData.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/AnimalData.py new file mode 100644 index 0000000..2f2098e --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/AnimalData.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +""" +================================================================================ +动物养殖方块转换规则数据 +================================================================================ +所有"动物养殖"分类下"右键方块→消耗物品→替换方块"的转换规则都在这里。 +只被 AnimalService 读取,FeatureDispatcher 通过方块名集合路由到 AnimalService, +不会去查其他功能(烧烤/做菜/工具切换)的数据库。 + +与 BlockSwitchData.py 的关系: + BlockSwitchData.py = 通用工具方块切换(箭桶、挂肉钩、水槽、书柜等) + AnimalData.py = 动物养殖专属(鸡窝、饲料盆、饲料槽、粪便等) + +字段说明(与 BlockSwitchData 基本一致,新增 giveCount/dropItem): + source: 被右键的方块ID(必填) + item: 玩家手里必须拿的物品,None=空手也行 + target: 替换成什么方块,"minecraft:air"=消失 + consume: 消耗物品数量,0=不消耗(默认1) + giveItem: 额外给玩家什么物品,None=不给 + giveCount: giveItem 的数量,默认1 + dropItem: 转换时以掉落物形式生成的物品列表,优先于 giveItem + 格式: [{"name": "xxx", "count": N}, ...] + 做成掉落物而不是直接塞背包,符合"取下/获得"的体验 + type: "switch"=右键切换, "timer"=计时自动转换 + time: 计时型转换需要多少秒(到时间自动替换方块) +================================================================================ +""" + +ANIMAL_SWITCHES = [ + + # ============================================================ + # 一、鸡窝(产蛋) —— 低级机制:手持鸡蛋右键放置递增 + # 对应方块:chickencoop_0(0蛋) ~ chickencoop_5(5蛋) + # ============================================================ + # 【放蛋1】鸡窝_1(0蛋) → 鸡窝_2(1蛋) + { + "source": "rtdd:chickencoop_0", + "item": "minecraft:egg", + "target": "rtdd:chickencoop_1", + "consume": 1, + "type": "switch", + "switchCd": 0.15, + "giveItem": None, + }, + # 【放蛋2】鸡窝_2(1蛋) → 鸡窝_3(2蛋) + { + "source": "rtdd:chickencoop_1", + "item": "minecraft:egg", + "target": "rtdd:chickencoop_2", + "consume": 1, + "type": "switch", + "switchCd": 0.15, + "giveItem": None, + }, + # 【放蛋3】鸡窝_3(2蛋) → 鸡窝_4(3蛋) + { + "source": "rtdd:chickencoop_2", + "item": "minecraft:egg", + "target": "rtdd:chickencoop_3", + "consume": 1, + "type": "switch", + "switchCd": 0.15, + "giveItem": None, + }, + # 【放蛋4】鸡窝_4(3蛋) → 鸡窝_5(4蛋) + { + "source": "rtdd:chickencoop_3", + "item": "minecraft:egg", + "target": "rtdd:chickencoop_4", + "consume": 1, + "type": "switch", + "switchCd": 0.15, + "giveItem": None, + }, + # 【放蛋5】鸡窝_5(4蛋) → 鸡窝_6(5蛋) + { + "source": "rtdd:chickencoop_4", + "item": "minecraft:egg", + "target": "rtdd:chickencoop_5", + "consume": 1, + "type": "switch", + "switchCd": 0.15, + "giveItem": None, + }, + + # ============================================================ + # 二、鸡窝(取蛋) —— 空手右键取走鸡蛋,以掉落物形式生成 + # 数量转换遵循"递增→递减"双向规则:放蛋1→2→3→4→5,取蛋5→4→3→2→1 + # 取蛋后一律回到0蛋形态(chickencoop_0) + # ============================================================ + # 【取5蛋】鸡窝_6(5蛋) → 鸡窝_1(0蛋),掉落5个鸡蛋 + { + "source": "rtdd:chickencoop_5", + "item": None, + "target": "rtdd:chickencoop_0", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "minecraft:egg", "count": 5}], + "giveItem": None, + }, + # 【取4蛋】鸡窝_5(4蛋) → 鸡窝_1(0蛋),掉落4个鸡蛋 + { + "source": "rtdd:chickencoop_4", + "item": None, + "target": "rtdd:chickencoop_0", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "minecraft:egg", "count": 4}], + "giveItem": None, + }, + # 【取3蛋】鸡窝_4(3蛋) → 鸡窝_1(0蛋),掉落3个鸡蛋 + { + "source": "rtdd:chickencoop_3", + "item": None, + "target": "rtdd:chickencoop_0", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "minecraft:egg", "count": 3}], + "giveItem": None, + }, + # 【取2蛋】鸡窝_3(2蛋) → 鸡窝_1(0蛋),掉落2个鸡蛋 + { + "source": "rtdd:chickencoop_2", + "item": None, + "target": "rtdd:chickencoop_0", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "minecraft:egg", "count": 2}], + "giveItem": None, + }, + # 【取1蛋】鸡窝_2(1蛋) → 鸡窝_1(0蛋),掉落1个鸡蛋 + { + "source": "rtdd:chickencoop_1", + "item": None, + "target": "rtdd:chickencoop_0", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "minecraft:egg", "count": 1}], + "giveItem": None, + }, + + # ============================================================ + # 三、鸡饲料盆 —— 手持种子右键填充,计时变空 + # 对应方块:chickenmanger_0(空)、chickenmanger_1(满) + # ============================================================ + # 【加饲料】鸡饲料盆_1(空) → 鸡饲料盆_2(满),消耗16个种子 + { + "source": "rtdd:chickenmanger_0", + "item": "minecraft:wheat_seeds", + "target": "rtdd:chickenmanger_1", + "consume": 16, + "type": "switch", + "giveItem": None, + }, + # 【计时变空】鸡饲料盆_2(满) → 鸡饲料盆_1(空),计时60秒 + # 这是一个自动转换:玩家填充后启动计时,到时间自动变空 + { + "source": "rtdd:chickenmanger_1", + "item": None, + "target": "rtdd:chickenmanger_0", + "consume": 0, + "type": "timer", + "time": 60, + "giveItem": None, + }, + + # ============================================================ + # 四、饲料槽-小麦 —— 手持小麦右键填充(消耗32个),计时逐级减少 + # 对应方块:manger_0(空)、manger_3(满小麦)、manger_2(半量小麦)、manger_1(少量小麦) + # ============================================================ + # 【加小麦】饲料槽_1(空) → 饲料槽_2(满小麦),消耗32个小麦 + { + "source": "rtdd:manger_0", + "item": "minecraft:wheat", + "target": "rtdd:manger_3", + "consume": 32, + "type": "switch", + "giveItem": None, + }, + # 【计时减少1】饲料槽_2(满小麦) → 饲料槽_3(半量小麦),计时60秒 + { + "source": "rtdd:manger_3", + "item": None, + "target": "rtdd:manger_2", + "consume": 0, + "type": "timer", + "time": 60, + "giveItem": None, + }, + # 【计时减少2】饲料槽_3(半量小麦) → 饲料槽_4(少量小麦),计时60秒 + { + "source": "rtdd:manger_2", + "item": None, + "target": "rtdd:manger_1", + "consume": 0, + "type": "timer", + "time": 60, + "giveItem": None, + }, + # 【计时减少3】饲料槽_4(少量小麦) → 饲料槽_1(空),计时60秒 + { + "source": "rtdd:manger_1", + "item": None, + "target": "rtdd:manger_0", + "consume": 0, + "type": "timer", + "time": 60, + "giveItem": None, + }, + + # ============================================================ + # 五、饲料槽-水 —— 手持水桶右键填充,水桶变空桶 + # 对应方块:manger_0(空)、mangerwater(水槽) + # ============================================================ + # 【加水】饲料槽_1(空) → 饲料槽_6(水槽),消耗水桶,获得空桶 + { + "source": "rtdd:manger_0", + "item": "minecraft:water_bucket", + "target": "rtdd:mangerwater", + "consume": 1, + "type": "switch", + "giveItem": "minecraft:bucket", + "giveCount": 1, + }, + + # ============================================================ + # 六、饲料槽-胡萝卜 —— 手持胡萝卜右键填充(消耗32个),计时逐级减少 + # 对应方块:manger_0(空)、pigmanger_1(满胡萝卜)、pigmanger_2(半量胡萝卜) + # ============================================================ + # 【加胡萝卜】饲料槽_1(空) → 饲料槽_7(满胡萝卜),消耗32个胡萝卜 + { + "source": "rtdd:manger_0", + "item": "minecraft:carrot", + "target": "rtdd:pigmanger_1", + "consume": 32, + "type": "switch", + "giveItem": None, + }, + # 【计时减少1】饲料槽_7(满胡萝卜) → 饲料槽_8(半量胡萝卜),计时60秒 + { + "source": "rtdd:pigmanger_1", + "item": None, + "target": "rtdd:pigmanger_2", + "consume": 0, + "type": "timer", + "time": 60, + "giveItem": None, + }, + # 【计时减少2】饲料槽_8(半量胡萝卜) → 饲料槽_1(空),计时60秒 + { + "source": "rtdd:pigmanger_2", + "item": None, + "target": "rtdd:manger_0", + "consume": 0, + "type": "timer", + "time": 60, + "giveItem": None, + }, + + # ============================================================ + # 七、粪便清理 —— 空手右键逐级清理,最后消失 + # 对应方块:feces_3(一坨)、feces_2(半坨)、feces_1(仅痕迹) + # 【食用部分后续在总食用功能中实现,本次不实现】 + # ============================================================ + # 【清理1】粪便(一坨) → 粪便(半坨) + { + "source": "rtdd:feces_3", + "item": None, + "target": "rtdd:feces_2", + "consume": 0, + "type": "switch", + "giveItem": None, + }, + # 【清理2】粪便(半坨) → 粪便(仅痕迹) + { + "source": "rtdd:feces_2", + "item": None, + "target": "rtdd:feces_1", + "consume": 0, + "type": "switch", + "giveItem": None, + }, + # 【清理3】粪便(仅痕迹) → 消失(空气) + { + "source": "rtdd:feces_1", + "item": None, + "target": "minecraft:air", + "consume": 0, + "type": "switch", + "giveItem": None, + }, +] diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/client/AnimalClient.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/client/AnimalClient.py new file mode 100644 index 0000000..118f009 --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/client/AnimalClient.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +动物养殖客户端(预留) +===================== +目前动物养殖功能的服务端逻辑已由 AnimalService + FeatureDispatcher 处理, +客户端暂不需要额外的 UI 工作。 +如果以后需要饲料余量提示、产蛋进度条等 UI,可以在这里扩展。 +""" +import mod.client.extraClientApi as clientApi + +ClientSystem = clientApi.GetClientSystemCls() + + +class AnimalClient(ClientSystem): + def __init__(self, namespace, systemName): + ClientSystem.__init__(self, namespace, systemName) + print "===== AnimalClient init =====" + + def Destroy(self): + print "===== AnimalClient Destroy =====" diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/client/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/service/AnimalService.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/service/AnimalService.py new file mode 100644 index 0000000..fb8d4cf --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/service/AnimalService.py @@ -0,0 +1,332 @@ +# -*- coding: utf-8 -*- +""" +================================================================================ +动物养殖引擎(服务端) +================================================================================ +只处理"动物养殖"(xy_animal) 分类下的方块转换:鸡窝、饲料盆、饲料槽、粪便。 + +【与 BlockSwitchService / CookingService 的区别】 + 那些系统各自监听 ServerBlockUseEvent,右键时会互相跑一遍拦截。 + 本系统(方案A)改为"统一分发器"架构: + - FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统 + - 本类通过 FeatureDispatcher.register(self) 把管辖的方块名注册进去 + - 玩家右键方块时,FeatureDispatcher 用 O(1) 的 dict 直接找到本实例 + - 本类只查自己的 AnimalData(ANIMAL_SWITCHES),不碰其他功能的数据库 + → 每个右键事件只查一次数据库,功能损耗最小 + +【本类不做的事】 + - 不自己监听 ServerBlockUseEvent(由 FeatureDispatcher 统一转发 handleBlockUse) + - 不处理其他分类的方块(数据只来自 AnimalData) + +【转换类型】 + - type="switch" : 右键直接转换(放蛋/取蛋/填充饲料/清理粪便/加水) + - type="timer" : 计时自动转换(饲料盆/饲料槽随时间变空), + 由本类用 AddTimer 实现,不依赖方块实体 tick +================================================================================ +""" +import time +import mod.server.extraServerApi as serverApi + +# ---- 网易API基础对象 ---- +ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类 +comp = serverApi.GetEngineCompFactory() # 组件工厂 + +# ---- 继承 BlueFox 框架的系统基类(提供 AddTimer / CreateEngineItemEntity 等方法) ---- +from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem +from ....BlueFoxTools.ToolsConfig import LevelId + +# ---- 只导入动物养殖自己的数据库 ---- +from ..AnimalData import ANIMAL_SWITCHES + +# ---- 统一分发器(本类把自己注册进去,让分发器把右键事件转发过来) ---- +from ...service.FeatureDispatcher import FeatureDispatcher + + +class AnimalService(BlueFoxServerSystem): + """ + 动物养殖引擎 + + 【核心数据结构】 + animalRecipes = { + (方块名, 物品名): 规则dict, # O(1) 查找 + } + animalBlockSet = {方块名, ...} # O(1) 拦截/注册用 + animalTimers = {方块名: 规则dict} # 计时型规则的起始方块 → 规则 + """ + + def __init__(self, namespace, systemName): + """ + 初始化:构建自己的转换规则字典,并注册到统一分发器 + """ + super(AnimalService, self).__init__(namespace, systemName) + print "===== AnimalService init =====" + + # ---- 构建动物养殖规则字典(O(1) 查找) ---- + self.animalRecipes = {} + self.animalBlockSet = set() + self.animalTimers = {} + for sw in ANIMAL_SWITCHES: + key = (sw["source"], sw.get("item")) + self.animalRecipes[key] = sw + self.animalBlockSet.add(sw["source"]) + # 收集计时型规则的"起始方块",方便转换后自动启动计时 + if sw.get("type") == "timer": + self.animalTimers[sw["source"]] = sw + + # ---- 计时去重表:key=(dim,x,y,z) -> 上次启动计时的时间戳 ---- + self._timerSchedule = {} + + # ---- 右键冷却表:防止连点瞬间触发多次 ---- + self.cooldown = {} + + # ---- 注册到统一分发器(把管辖方块交给 FeatureDispatcher 路由) ---- + FeatureDispatcher.register(self) + + # ============================================================================ + # 给 FeatureDispatcher 的接口 + # ============================================================================ + + def getFeatureBlocks(self): + """ + 返回本功能管辖的所有方块名集合。 + FeatureDispatcher 靠它构建 方块名→本实例 的 O(1) 路由表。 + """ + return self.animalBlockSet + + def getItemUseOnRules(self): + """ + 返回本功能的"手持物品右键方块表面"规则(当前动物养殖没有,返回空)。 + 接口统一预留,方便以后扩展(如手持鸡蛋右键地面直接生蛋等)。 + """ + return {} + + def handleBlockUse(self, args): + """ + 由 FeatureDispatcher 转发的右键方块入口。 + 只有 blockName 在本功能的方块集合里才会被调用。 + + 返回 True = 本功能处理了这个右键(分发器会取消默认行为) + 返回 False = 当前组合没有匹配规则(分发器不拦截,交还默认行为) + """ + 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) + + # ---- 查动物养殖规则(O(1)) ---- + recipe = self.findRecipe(blockName, itemDict) + if not recipe: + return False + + # ---- 冷却检查(防止连点,规则可自定义冷却时间 switchCd) ---- + if not self.checkCooldown(key, recipe.get("switchCd", 0.5)): + return True # 冷却中,虽然不执行但也算"处理了",避免默认行为 + + # ---- 计时型规则:启动/续计时,不立即转换 ---- + if recipe.get("type") == "timer": + self.startTimer(key, recipe, dim, playerId) + return True + + # ---- 切换型规则:执行具体转换 ---- + self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName) + return True + + # ============================================================================ + # 规则匹配(O(1) 字典查找) + # ============================================================================ + + def findRecipe(self, blockName, itemDict): + """ + 在动物养殖字典中查找匹配规则: + 1. 先精确匹配 (方块名, 物品名) + 2. 没找到再匹配 (方块名, None) —— 空手也能触发的规则 + """ + itemName = itemDict.get("newItemName") if itemDict else None + recipe = self.animalRecipes.get((blockName, itemName)) + if recipe: + return recipe + recipe = self.animalRecipes.get((blockName, None)) + if recipe: + return recipe + return None + + # ============================================================================ + # 切换型规则处理 + # ============================================================================ + + def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName): + """ + 处理"右键直接转换"类规则。 + + 正向(item 不为 None):放蛋/加饲料/加水 → 消耗物品 → 替换方块 → 给奖励 → 续计时 + 逆向(item 为 None) :取蛋/清理粪便/取回 → 掉落物品 → 替换方块 + """ + # ---- 正向操作:往方块里放东西 ---- + if recipe.get("item") is not None: + # 消耗物品 + if recipe.get("consume", 1) > 0: + self.consumeItem(playerId, itemDict, recipe.get("consume", 1)) + + # 替换方块 + if recipe.get("target"): + self.replaceBlock(pos, dim, recipe["target"], playerId) + + # 给玩家物品(如加水给空桶)——直接塞背包 + if recipe.get("giveItem"): + self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1)) + + # ★ 如果新方块是某个计时链的起始方块 → 自动启动计时(如刚装满的饲料盆) + if recipe.get("target") in self.animalTimers: + nextTimer = self.animalTimers[recipe["target"]] + self.startTimer(key, nextTimer, dim, playerId) + + return + + # ---- 逆向操作:取东西/清理 ---- + # 掉落物形式生成(取蛋、取下物品等),优先于 giveItem + if recipe.get("dropItem"): + self.dropItems(dim, pos, recipe["dropItem"]) + elif recipe.get("giveItem"): + self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1)) + + # 替换方块(可能是空气=消失) + if recipe.get("target"): + self.replaceBlock(pos, dim, recipe["target"], playerId) + + # ============================================================================ + # 计时型规则处理(AddTimer 一次性定时,不依赖方块实体tick) + # ============================================================================ + + def startTimer(self, key, recipe, dim, playerId=None): + """ + 为计时型规则启动一次性定时器。 + 1 秒内的重复调用会被去重(防止多次右键重复计时)。 + 到时间后由 _timerDone 检查方块状态并执行转换。 + """ + now = time.time() + last = self._timerSchedule.get(key, 0) + if now - last < 1.0: + return + self._timerSchedule[key] = now + + t = recipe.get("time", 60) + # 用系统自带 AddTimer 一次性定时 + self.AddTimer(t, self._timerDone, key, recipe, dim) + + def _timerDone(self, key, recipe, dim): + """ + 定时器到点回调: + 检查当前方块是否仍是计时源方块(防止玩家中途改变了方块), + 是则替换为下一状态;若新状态仍是计时源则继续下一级计时。 + """ + pos = (key[1], key[2], key[3]) + try: + cur = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim) + if not cur or cur.get("name") != recipe["source"]: + return # 方块已变,放弃本次转换 + # 替换方块 + self.replaceBlock(pos, dim, recipe["target"], None) + # 若新方块仍是计时源 → 继续下一级计时(如 满→半→少→空) + nextRecipe = self.animalTimers.get(recipe["target"]) + if nextRecipe: + self.startTimer(key, nextRecipe, dim) + except Exception: + pass + + # ============================================================================ + # 工具方法:获取玩家主手物品 + # ============================================================================ + + def getCarriedItem(self, playerId): + itemComp = comp.CreateItem(playerId) + return itemComp.GetPlayerItem( + serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0) + + # ============================================================================ + # 工具方法:消耗物品 + # ============================================================================ + + def consumeItem(self, playerId, itemDict, amount=1): + if itemDict is None: + return + count = itemDict.get("count", 0) + if count <= amount: + comp.CreateItem(playerId).SetEntityItem( + serverApi.GetMinecraftEnum().ItemPosType.CARRIED, + {"itemName": "minecraft:air", "count": 0, "auxValue": 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, aux=None): + """ + 替换指定位置的方块。 + newBlockName="minecraft:air" 表示清除该方块。 + playerId 可为 None(计时回调时没有玩家),用 LevelId 创建组件。 + """ + creator = playerId if playerId is not None else LevelId + if aux is None: + try: + oldBlock = comp.CreateBlockInfo(creator).GetBlockNew(pos, dim) + aux = oldBlock.get("aux", 0) + except Exception: + aux = 0 + comp.CreateBlockInfo(creator).SetBlockNew( + pos, + blockDict={"name": newBlockName, "aux": aux}, + oldBlockHandling=0, + dimensionId=dim) + + # ============================================================================ + # 工具方法:给玩家物品(塞背包) + # ============================================================================ + + def giveItem(self, playerId, itemName, count=1, auxValue=0): + comp.CreateItem(playerId).SpawnItemToPlayerInv( + {"itemName": itemName, "count": count, "auxValue": auxValue}, playerId) + + # ============================================================================ + # 工具方法:以掉落物形式生成物品(取蛋/取下物品等,符合"获得"体验) + # ============================================================================ + + def dropItems(self, dim, pos, dropList): + """ + dropList 格式:[{"name": "minecraft:egg", "count": 5}, ...] + 在方块上方生成掉落物实体,让它自然掉到地上。 + """ + for d in dropList: + itemDict = { + "itemName": d["name"], + "count": d.get("count", 1), + "auxValue": d.get("aux", 0), + } + self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2])) + + # ============================================================================ + # 工具方法:冷却检查 + # ============================================================================ + + def checkCooldown(self, key, cd=0.5): + """ + 右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品。 + 放蛋等需要快速连点的规则可在数据里用 switchCd 调小冷却。 + """ + now = time.time() + last = self.cooldown.get(key, 0) + if now - last < cd: + return False + self.cooldown[key] = now + return True diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/service/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/dongwuyangzhi/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py new file mode 100644 index 0000000..5e558da --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +""" +================================================================================ +统一事件分发器(服务端)—— 方案A核心 +================================================================================ +【为什么需要它】 + 以前每个功能(做菜/工具切换)都各自监听 ServerBlockUseEvent, + 玩家每次右键,所有系统都会被触发一遍、各自跑一次数据库拦截判断, + 功能越多损耗越大。 + +【它做什么】 + FeatureDispatcher 是"唯一"监听右键相关事件的服务端系统: + - ServerBlockUseEvent 玩家右键方块 + - ServerItemUseOnEvent 手持物品右键方块表面(如放置生鸡肉) + - ServerItemTryUseEvent 拦截喝药水/吃食物等默认行为 + + 各功能服务在初始化时通过 FeatureDispatcher.register(self) 上报: + - 自己管辖的方块名集合 → 构建 方块名→服务 的 O(1) 路由表 + - 自己处理的手持物品 → 构建 物品名→服务 的 O(1) 路由表 + + 事件进来后,分发器先用 blockName / itemName 在路由表里 O(1) 定位到 + 唯一的那个功能服务,再调用它的 handleBlockUse / handleItemUseOn。 + → 每个右键事件只命中一个数据库,其余功能的数据库根本不会被查询。 + +【如何新增一个功能】 + 1. 新建 xxx/xxxData.py 写转换规则 + 2. 新建 xxx/service/XxxService.py(继承 BlueFoxServerSystem): + - __init__ 里构建自己的规则字典 + - 实现 getFeatureBlocks() / getItemUseOnRules() + - 实现 handleBlockUse(args) / handleItemUseOn(args) + - 调用 FeatureDispatcher.register(self) + 3. 在 modMain.py import 这个服务类即可 +================================================================================ +""" +import mod.server.extraServerApi as serverApi + +# ---- 网易API基础对象 ---- +ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类 +comp = serverApi.GetEngineCompFactory() # 组件工厂 + +# ---- 继承 BlueFox 框架的系统基类 ---- +from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem + + +class FeatureDispatcher(BlueFoxServerSystem): + """ + 统一事件分发器 + + 【类级注册表(所有功能服务共享同一份路由表)】 + blockOwner = {方块名: 功能服务实例} + blockSet = {方块名} # 全部功能方块集合 + itemUseOnOwner = {物品名: 功能服务实例} + itemUseOnSet = {物品名} # 全部"手持物品右键方块表面"集合 + """ + + # ---- 类级共享路由表(用类变量,任何实例都能读写) ---- + blockOwner = {} + blockSet = set() + itemUseOnOwner = {} + itemUseOnSet = set() + + @classmethod + def register(cls, service): + """ + 功能服务初始化时调用,把管辖的方块和物品注册进路由表。 + 同一方块/物品只会归属最后一个注册的服务(一般不会冲突)。 + """ + # 右键方块路由:方块名 → 服务 + for block in service.getFeatureBlocks(): + cls.blockOwner[block] = service + cls.blockSet.add(block) + + # 手持物品右键方块表面路由:物品名 → 服务 + for itemName in service.getItemUseOnRules(): + cls.itemUseOnOwner[itemName] = service + cls.itemUseOnSet.add(itemName) + + def __init__(self, namespace, systemName): + """ + 初始化:注册三个事件监听。 + 注意:本类自身不注册任何功能方块,只负责把事件分发给已注册的服务。 + """ + super(FeatureDispatcher, self).__init__(namespace, systemName) + print "===== FeatureDispatcher init =====" + self._cancelItemUse = {} # 将要取消下一次物品使用的玩家ID集合(防止喝药水等) + self.ListenEvent() + + def Destroy(self): + print "===== FeatureDispatcher Destroy =====" + self.UnListenEvent() + + # ============================================================================ + # 事件注册与注销 + # ============================================================================ + + def ListenEvent(self): + """ + 注册三个事件: + 1. ServerBlockUseEvent —— 玩家右键方块(所有功能的右键转换入口) + 2. ServerItemUseOnEvent —— 手持物品右键方块表面(如放置生鸡肉) + 3. ServerItemTryUseEvent —— 玩家尝试使用物品(拦截喝药水/吃食物等默认行为) + """ + self.ListenForEvent(serverApi.GetEngineNamespace(), + serverApi.GetEngineSystemName(), + "ServerBlockUseEvent", self, self.OnBlockUse) + self.ListenForEvent(serverApi.GetEngineNamespace(), + serverApi.GetEngineSystemName(), + "ServerItemUseOnEvent", self, self.OnItemUseOn) + self.ListenForEvent(serverApi.GetEngineNamespace(), + serverApi.GetEngineSystemName(), + "ServerItemTryUseEvent", self, self.OnItemTryUse) + + def UnListenEvent(self): + self.UnListenForEvent(serverApi.GetEngineNamespace(), + serverApi.GetEngineSystemName(), + "ServerBlockUseEvent", self, self.OnBlockUse) + self.UnListenForEvent(serverApi.GetEngineNamespace(), + serverApi.GetEngineSystemName(), + "ServerItemUseOnEvent", self, self.OnItemUseOn) + self.UnListenForEvent(serverApi.GetEngineNamespace(), + serverApi.GetEngineSystemName(), + "ServerItemTryUseEvent", self, self.OnItemTryUse) + + # ============================================================================ + # 主入口1:玩家右键方块 + # ============================================================================ + + def OnBlockUse(self, args): + """ + 玩家右键方块时触发。 + 用被右键的方块名 O(1) 定位到唯一的功能服务,转发给它的 handleBlockUse。 + + 服务返回 True = 处理成功 → 取消默认行为(如防止喝药水),并标记拦截下一次物品使用 + 服务返回 False = 当前组合无匹配规则 → 交还默认行为 + """ + blockName = args.get("blockName") + service = self.blockOwner.get(blockName) + if not service: + # 这个方块不属于任何已注册的功能 → 交给原版/其他模组处理 + return + + handled = service.handleBlockUse(args) + if handled: + args['cancel'] = True + playerId = args.get("playerId") + self._cancelItemUse[playerId] = True + + # ============================================================================ + # 主入口2:手持物品右键方块表面 + # ============================================================================ + + def OnItemUseOn(self, args): + """ + 手持物品右键某个方块表面时触发(如手持原版生鸡肉右键地面)。 + 用手持物品名 O(1) 定位到唯一的功能服务,转发给它的 handleItemUseOn。 + + 注意:取消"使用物品于方块"的字段是 ret(不是 cancel), + 官方要求双端设置才能真正取消默认行为。本分发器已在服务端设 ret=True, + 服务端不会执行默认放置/使用逻辑;客户端如需完美取消动画, + 可再增加一个监听 ClientItemUseOnEvent 的客户端配对系统。 + """ + itemName = args.get("itemName") + service = self.itemUseOnOwner.get(itemName) + if not service: + return + + handled = service.handleItemUseOn(args) + if handled: + args['ret'] = True + playerId = args.get("playerId") + self._cancelItemUse[playerId] = True + + # ============================================================================ + # 主入口3:拦截物品使用(防止喝药水/吃食物等默认行为) + # ============================================================================ + + def OnItemTryUse(self, args): + """ + 玩家尝试使用物品(喝药水、吃食物等)时触发。 + 如果该玩家刚被标记为"取消下一次物品使用"(_cancelItemUse), + 就取消这次默认行为,避免右键转换方块时误喝药水。 + """ + playerId = args.get("playerId") + if self._cancelItemUse.pop(playerId, False): + args['cancel'] = True diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/BarbecueData.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/BarbecueData.py new file mode 100644 index 0000000..31cad0a --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/BarbecueData.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +""" +================================================================================ +烧烤方块转换规则数据 +================================================================================ +所有"烧烤"(xy_barbecue) 分类下"右键方块→消耗物品→替换方块"的转换规则都在这里。 +只被 BarbecueService 读取,FeatureDispatcher 通过方块名集合路由到 BarbecueService, +不会去查其他功能(动物养殖/做菜/工具切换)的数据库。 + +【烤法区分】 + 1) 烤串(牛肉串/羊肉串/烤蔬菜串/烤香菇串) + → 放在"烧烤架"上烤,右键 2 次转换,不计时 + → 牛肉串/羊肉串: 生=_1, 半熟=_0, 熟=_3(注意顺序与蔬菜串不同!) + → 烤蔬菜串/烤香菇串: 生=_0, 半熟=_1, 熟=_3 + 2) 烤鸡/烤乳猪/烤鱼 + → "上支杆"烤法: 支杆=b_34, 手持生食材右键支杆上杆 + → 支杆下方放"篝火(原版营火 campfire)", 计时自动转换 + → 烤熟后空手右键"取下" → 以掉落物形式获得整只成品 + +【食用部分说明】 + 烤熟成品(整只/整头)的"吃东西逐级分食"转换(如 整只→5/6→…→1/6) + 由后续统一的"总食用功能"实现,本次不实现、不收录。 + +字段说明(与 AnimalData 一致): + source: 被右键的方块ID(必填) + item: 玩家手里必须拿的物品,None=空手也行 + target: 替换成什么方块,"minecraft:air"=消失 + consume: 消耗物品数量,0=不消耗(默认1) + giveItem: 额外给玩家什么物品,None=不给 + giveCount: giveItem 的数量,默认1 + dropItem: 转换时以掉落物形式生成的物品列表,优先于 giveItem + 格式: [{"name": "xxx", "count": N}, ...] + type: "switch"=右键切换, "timer"=计时自动转换 + time: 计时型转换需要多少秒(到时间自动替换方块) + below: 计时型转换要求下方方块(如 ["minecraft:campfire"]) +================================================================================ +""" + +BARBECUE_SWITCHES = [ + + # ============================================================ + # 一、烧烤架(点火/灭火) + # ============================================================ + # 【点火-打火石】烧烤架_1(无火) → 烧烤架_2(点燃),打火石不消耗 + { + "source": "rtdd:barbecuegrill_0", + "item": "minecraft:flint_and_steel", + "target": "rtdd:barbecuegrill_1", + "consume": 0, + "type": "switch", + "giveItem": None, + }, + # 【点火-烈焰弹】烧烤架_1(无火) → 烧烤架_2(点燃),烈焰弹消耗1个 + { + "source": "rtdd:barbecuegrill_0", + "item": "minecraft:fire_charge", + "target": "rtdd:barbecuegrill_1", + "consume": 1, + "type": "switch", + "giveItem": None, + }, + # 【灭火】烧烤架_2(点燃) → 烧烤架_1(无火),消耗水桶,获得空桶 + { + "source": "rtdd:barbecuegrill_1", + "item": "minecraft:water_bucket", + "target": "rtdd:barbecuegrill_0", + "consume": 1, + "type": "switch", + "giveItem": "minecraft:bucket", + "giveCount": 1, + }, + + # ============================================================ + # 二、烤串(放烧烤架上,右键 2 次转换,不计时) + # 【重要】牛肉串/羊肉串的 生=_1 半熟=_0;蔬菜串/香菇串的 生=_0 半熟=_1 + # ============================================================ + # 牛肉串: 生(_1) → 半熟(_0) → 熟(_3) + # 放在"已点燃的烧烤架"上方才能右键烤制(下方必须是 barbecuegrill_1) + { + "source": "rtdd:beefkebabs_1", + "item": None, + "target": "rtdd:beefkebabs_0", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + { + "source": "rtdd:beefkebabs_0", + "item": None, + "target": "rtdd:beefkebabs_3", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + # 羊肉串: 生(_1) → 半熟(_0) → 熟(_3) + { + "source": "rtdd:lambkebabs_1", + "item": None, + "target": "rtdd:lambkebabs_0", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + { + "source": "rtdd:lambkebabs_0", + "item": None, + "target": "rtdd:lambkebabs_3", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + # 烤蔬菜串: 生(_0) → 半熟(_1) → 熟(_3) + { + "source": "rtdd:roasted_vegetables_0", + "item": None, + "target": "rtdd:roasted_vegetables_1", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + { + "source": "rtdd:roasted_vegetables_1", + "item": None, + "target": "rtdd:roasted_vegetables_3", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + # 烤香菇串: 生(_0) → 半熟(_1) → 熟(_3) + { + "source": "rtdd:roastedmushrooms_0", + "item": None, + "target": "rtdd:roastedmushrooms_1", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + { + "source": "rtdd:roastedmushrooms_1", + "item": None, + "target": "rtdd:roastedmushrooms_3", + "consume": 0, + "type": "switch", + "switchCd": 0.15, + "below": ["rtdd:barbecuegrill_1"], + "giveItem": None, + }, + + # ============================================================ + # 三、上杆(手持生食材右键 支杆b_34) + # ============================================================ + # 【上烤鸡】支杆 + 生鸡肉(放置后方块物品) → 烤鸡(上烤架_生) + { + "source": "rtdd:b_34", + "item": "rtdd:roastchickenplace_0", + "target": "rtdd:roastchicken_0", + "consume": 1, + "type": "switch", + "giveItem": None, + }, + # 【上烤乳猪】支杆 + 死猪 → 烤乳猪(生) + { + "source": "rtdd:b_34", + "item": "rtdd:dead_pig", + "target": "rtdd:roastpig_0", + "consume": 1, + "type": "switch", + "giveItem": None, + }, + # 【上烤鳕鱼】支杆 + 鳕鱼 → 烤鳕鱼(生) + { + "source": "rtdd:b_34", + "item": "minecraft:cod", + "target": "rtdd:grilledcod_0", + "consume": 1, + "type": "switch", + "giveItem": None, + }, + # 【上烤鲑鱼】支杆 + 鲑鱼 → 烤鲑鱼(生) + { + "source": "rtdd:b_34", + "item": "minecraft:salmon", + "target": "rtdd:grilled_fish", + "consume": 1, + "type": "switch", + "giveItem": None, + }, + + # ============================================================ + # 四、计时烤制(上杆后,支杆下方篝火,计时自动转换) + # ============================================================ + # 【烤熟】烤鸡(上烤架_生) → 烤鸡(上烤架_熟),下方需篝火,约60秒 + { + "source": "rtdd:roastchicken_0", + "item": None, + "target": "rtdd:roastchicken_1", + "consume": 0, + "type": "timer", + "time": 60, + "below": ["minecraft:campfire"], + "giveItem": None, + }, + # 【烤熟】烤乳猪(生) → 烤乳猪(熟),下方需篝火,约60秒 + { + "source": "rtdd:roastpig_0", + "item": None, + "target": "rtdd:roastpig_1", + "consume": 0, + "type": "timer", + "time": 60, + "below": ["minecraft:campfire"], + "giveItem": None, + }, + # 【烤熟】烤鳕鱼(生) → 烤鳕鱼(熟),下方需篝火,约60秒 + { + "source": "rtdd:grilledcod_0", + "item": None, + "target": "rtdd:grilledcod_1", + "consume": 0, + "type": "timer", + "time": 60, + "below": ["minecraft:campfire"], + "giveItem": None, + }, + # 【烤熟】烤鲑鱼(生) → 烤鲑鱼(熟),下方需篝火,约60秒 + { + "source": "rtdd:grilled_fish", + "item": None, + "target": "rtdd:grilled_salmon", + "consume": 0, + "type": "timer", + "time": 60, + "below": ["minecraft:campfire"], + "giveItem": None, + }, + + # ============================================================ + # 五、取下(空手右键烤熟方块,支杆恢复,成品以掉落物获得) + # ============================================================ + # 【取下烤鸡】烤鸡(上烤架_熟) → 支杆 + 掉落 烤鸡(整只) + { + "source": "rtdd:roastchicken_1", + "item": None, + "target": "rtdd:b_34", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "rtdd:roast_chickenplace_1", "count": 1}], + "giveItem": None, + }, + # 【取下烤乳猪】烤乳猪(熟) → 支杆 + 掉落 烤乳猪(整只) + { + "source": "rtdd:roastpig_1", + "item": None, + "target": "rtdd:b_34", + "consume": 0, + "type": "switch", + "dropItem": [{"name": "rtdd:roastpignew_1", "count": 1}], + "giveItem": None, + }, +] + + +# ============================================================ +# 六、手持物品右键"方块表面"生成规则(ServerItemUseOnEvent) +# ============================================================ +# 目前只有一条:手持"原版生鸡肉"右键任意方块表面 → 在其表面生成 +# "生鸡肉(原版生鸡肉右键放置后)"(roastchickenplace_0) 方块,消耗1个。 +# 该规则由 FeatureDispatcher 转发 ServerItemUseOnEvent 到 BarbecueService。 +ITEM_USE_ON_RULES = [ + { + "item": "minecraft:chicken", # 手持的原版生鸡肉 + "target": "rtdd:roastchickenplace_0", # 生成的目标方块 + "consume": 1, # 消耗1个生鸡肉 + }, +] diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/client/BarbecueClient.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/client/BarbecueClient.py new file mode 100644 index 0000000..65b70a0 --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/client/BarbecueClient.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +烧烤客户端(预留) +================= +目前烧烤功能的服务端逻辑已由 BarbecueService + FeatureDispatcher 处理, +客户端暂不需要额外的 UI 工作。 +如果以后需要烤制进度提示、烤串状态展示等 UI,可以在这里扩展。 +""" +import mod.client.extraClientApi as clientApi + +ClientSystem = clientApi.GetClientSystemCls() + + +class BarbecueClient(ClientSystem): + def __init__(self, namespace, systemName): + ClientSystem.__init__(self, namespace, systemName) + print "===== BarbecueClient init =====" + + def Destroy(self): + print "===== BarbecueClient Destroy =====" diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/client/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/service/BarbecueService.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/service/BarbecueService.py new file mode 100644 index 0000000..edfb97d --- /dev/null +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/service/BarbecueService.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +""" +================================================================================ +烧烤引擎(服务端) +================================================================================ +只处理"烧烤"(xy_barbecue) 分类下的方块转换:烧烤架、烤串、烤鸡、烤乳猪、烤鱼。 + +【与 BlockSwitchService / CookingService 的区别】 + 本系统(方案A)采用"统一分发器"架构: + - FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统 + - 本类通过 FeatureDispatcher.register(self) 把管辖方块注册进去 + - 玩家右键方块时,FeatureDispatcher 用 O(1) dict 直接找到本实例 + - 本类只查自己的 BarbecueData(BARBECUE_SWITCHES),不碰其他功能数据库 + +【本类额外处理的规则】 + - 手持"原版生鸡肉"右键方块表面(ServerItemUseOnEvent) → 生成"生鸡肉(放置后)"方块 + 该规则通过 FeatureDispatcher 的 itemUseOn 路由转发到本类 handleItemUseOn + +【转换类型】 + - type="switch" : 右键直接转换(点火/灭火/烤串2次/上杆/取下) + - type="timer" : 计时自动转换(上杆后下方篝火烤熟), + 由本类用 AddTimer 实现,启动前检查下方是否为篝火 +================================================================================ +""" +import time +import mod.server.extraServerApi as serverApi + +# ---- 网易API基础对象 ---- +ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类 +comp = serverApi.GetEngineCompFactory() # 组件工厂 + +# ---- 继承 BlueFox 框架的系统基类(提供 AddTimer / CreateEngineItemEntity 等方法) ---- +from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem +from ....BlueFoxTools.ToolsConfig import LevelId + +# ---- 只导入烧烤自己的数据库 ---- +from ..BarbecueData import BARBECUE_SWITCHES, ITEM_USE_ON_RULES + +# ---- 统一分发器 ---- +from ...service.FeatureDispatcher import FeatureDispatcher + +# ---- 方块表面方向 → 相邻坐标偏移(0下 1上 2北 3南 4西 5东) ---- +FACE_OFFSET = { + 0: (0, -1, 0), + 1: (0, 1, 0), + 2: (0, 0, -1), + 3: (0, 0, 1), + 4: (-1, 0, 0), + 5: (1, 0, 0), +} + + +class BarbecueService(BlueFoxServerSystem): + """ + 烧烤引擎 + + 【核心数据结构】 + barbecueRecipes = { + (方块名, 物品名): 规则dict, # O(1) 查找 + } + barbecueBlockSet = {方块名, ...} # O(1) 拦截/注册用 + barbecueTimers = {方块名: 规则dict} # 计时型起始方块 → 规则 + itemUseOnRecipes = {物品名: 规则dict} # 手持物品右键方块表面 → 生成规则 + """ + + def __init__(self, namespace, systemName): + super(BarbecueService, self).__init__(namespace, systemName) + print "===== BarbecueService init =====" + + # ---- 构建烧烤规则字典(O(1) 查找) ---- + self.barbecueRecipes = {} + self.barbecueBlockSet = set() + self.barbecueTimers = {} + for sw in BARBECUE_SWITCHES: + key = (sw["source"], sw.get("item")) + self.barbecueRecipes[key] = sw + self.barbecueBlockSet.add(sw["source"]) + if sw.get("type") == "timer": + self.barbecueTimers[sw["source"]] = sw + + # ---- 手持物品右键方块表面规则:物品名 → 规则 ---- + self.itemUseOnRecipes = {} + for rule in ITEM_USE_ON_RULES: + self.itemUseOnRecipes[rule["item"]] = rule + + # ---- 计时去重表 + 右键冷却表 ---- + self._timerSchedule = {} + self.cooldown = {} + + # ---- 注册到统一分发器 ---- + FeatureDispatcher.register(self) + + # ============================================================================ + # 给 FeatureDispatcher 的接口 + # ============================================================================ + + def getFeatureBlocks(self): + """返回本功能管辖的所有方块名集合(右键路由用)""" + return self.barbecueBlockSet + + def getItemUseOnRules(self): + """返回手持物品右键方块表面的规则:{物品名: 规则dict}(供分发器路由)""" + return self.itemUseOnRecipes + + def handleBlockUse(self, args): + """ + 由 FeatureDispatcher 转发的右键方块入口。 + 只有 blockName 在本功能方块集合里才会被调用。 + 返回 True=已处理,False=当前组合无匹配规则。 + """ + 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 False + + # 冷却检查(烤串"右键2次"用 switchCd 调小冷却以便快速连点) + if not self.checkCooldown(key, recipe.get("switchCd", 0.5)): + return True + + # ---- 下方方块检查(timer 需篝火,烤串需点燃的烧烤架) ---- + if not self.checkBelow(recipe, dim, pos): + # 下方不是要求的方法 → 提示玩家,不执行(但也算处理,取消默认行为) + if recipe.get("type") == "timer": + tip = "§c支杆下方需要放一个篝火(营火)才能烤制!" + else: + tip = "§c烤串需要放在点燃的烧烤架上才能烤制!" + comp.CreateMsg(playerId).NotifyOneMessage(playerId, tip) + return True + + # 计时型:启动计时 + if recipe.get("type") == "timer": + self.startTimer(key, recipe, dim) + return True + + # 切换型 + self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName) + return True + + def handleItemUseOn(self, args): + """ + 由 FeatureDispatcher 转发的"手持物品右键方块表面"事件。 + 当前用于:手持原版生鸡肉右键任意方块表面 → 在其表面生成生鸡肉(放置后)方块。 + 返回 True=已处理,False=当前组合无匹配规则。 + """ + itemName = args.get("itemName") + playerId = args.get("playerId") + rule = self.itemUseOnRecipes.get(itemName) + if not rule: + return False + + x, y, z = args.get("x"), args.get("y"), args.get("z") + face = args.get("face", 1) + dim = comp.CreateDimension(playerId).GetEntityDimensionId() + + # 计算被点方块表面相邻的目标格 + off = FACE_OFFSET.get(face, (0, 1, 0)) + targetPos = (x + off[0], y + off[1], z + off[2]) + + # 目标格必须为空(可放置) + try: + cur = comp.CreateBlockInfo(LevelId).GetBlockNew(targetPos, dim) + if cur and cur.get("name") != "minecraft:air": + return False + except Exception: + return False + + # 消耗 1 个生鸡肉 + itemDict = self.getCarriedItem(playerId) + if not itemDict: + return False + self.consumeItem(playerId, itemDict, rule.get("consume", 1)) + + # 生成生鸡肉(放置后)方块 + self.replaceBlock(targetPos, dim, rule["target"], playerId) + return True + + # ============================================================================ + # 规则匹配(O(1)) + # ============================================================================ + + def findRecipe(self, blockName, itemDict): + itemName = itemDict.get("newItemName") if itemDict else None + recipe = self.barbecueRecipes.get((blockName, itemName)) + if recipe: + return recipe + recipe = self.barbecueRecipes.get((blockName, None)) + if recipe: + return recipe + return None + + # ============================================================================ + # 切换型规则处理 + # ============================================================================ + + def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName): + """ + 正向(item 非 None):点火/上杆 → 消耗 → 替换 → 给奖励 → 续计时 + 逆向(item 为 None):烤串2次/取下 → 掉落 → 替换 + """ + # ---- 正向 ---- + if recipe.get("item") is not None: + if recipe.get("consume", 1) > 0: + self.consumeItem(playerId, itemDict, recipe.get("consume", 1)) + + if recipe.get("target"): + self.replaceBlock(pos, dim, recipe["target"], playerId) + + if recipe.get("giveItem"): + self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1)) + + # 上杆后若新方块是计时源 → 尝试启动计时(下方需有篝火) + if recipe.get("target") in self.barbecueTimers: + nextTimer = self.barbecueTimers[recipe["target"]] + if self.checkBelow(nextTimer, dim, pos): + self.startTimer(key, nextTimer, dim) + + return + + # ---- 逆向 ---- + if recipe.get("dropItem"): + self.dropItems(dim, pos, recipe["dropItem"]) + elif recipe.get("giveItem"): + self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1)) + + if recipe.get("target"): + self.replaceBlock(pos, dim, recipe["target"], playerId) + + # ============================================================================ + # 计时型规则处理(AddTimer 一次性定时,启动前检查下方篝火) + # ============================================================================ + + def checkBelow(self, recipe, dim, pos): + """ + 检查方块下方是否为 recipe["below"] 中指定的方块。 + 没写 below 字段 → 视为满足。 + """ + below = recipe.get("below") + if not below: + return True + belowPos = (pos[0], pos[1] - 1, pos[2]) + try: + b = comp.CreateBlockInfo(LevelId).GetBlockNew(belowPos, dim) + return b.get("name") in below + except Exception: + return False + + def startTimer(self, key, recipe, dim): + """启动一次性定时器,1秒内去重,到点由 _timerDone 执行转换""" + now = time.time() + last = self._timerSchedule.get(key, 0) + if now - last < 1.0: + return + self._timerSchedule[key] = now + self.AddTimer(recipe.get("time", 60), self._timerDone, key, recipe, dim) + + def _timerDone(self, key, recipe, dim): + """计时到点:确认方块仍是计时源,且下方仍有篝火,才执行转换""" + pos = (key[1], key[2], key[3]) + try: + cur = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim) + if not cur or cur.get("name") != recipe["source"]: + return + if not self.checkBelow(recipe, dim, pos): + return # 篝火被拿走了,暂停烤制 + self.replaceBlock(pos, dim, recipe["target"], None) + # 若新方块仍是计时源 → 继续下一级(当前烧烤无多级计时,预留) + nextRecipe = self.barbecueTimers.get(recipe["target"]) + if nextRecipe: + self.startTimer(key, nextRecipe, dim) + except Exception: + pass + + # ============================================================================ + # 工具方法 + # ============================================================================ + + def getCarriedItem(self, playerId): + itemComp = comp.CreateItem(playerId) + return itemComp.GetPlayerItem( + serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0) + + def consumeItem(self, playerId, itemDict, amount=1): + if itemDict is None: + return + count = itemDict.get("count", 0) + if count <= amount: + comp.CreateItem(playerId).SetEntityItem( + serverApi.GetMinecraftEnum().ItemPosType.CARRIED, + {"itemName": "minecraft:air", "count": 0, "auxValue": 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, aux=None): + creator = playerId if playerId is not None else LevelId + if aux is None: + try: + oldBlock = comp.CreateBlockInfo(creator).GetBlockNew(pos, dim) + aux = oldBlock.get("aux", 0) + except Exception: + aux = 0 + comp.CreateBlockInfo(creator).SetBlockNew( + pos, + blockDict={"name": newBlockName, "aux": aux}, + oldBlockHandling=0, + dimensionId=dim) + + def giveItem(self, playerId, itemName, count=1, auxValue=0): + comp.CreateItem(playerId).SpawnItemToPlayerInv( + {"itemName": itemName, "count": count, "auxValue": auxValue}, playerId) + + def dropItems(self, dim, pos, dropList): + """以掉落物形式生成物品(取下烤鸡/烤乳猪等)""" + for d in dropList: + itemDict = { + "itemName": d["name"], + "count": d.get("count", 1), + "auxValue": d.get("aux", 0), + } + self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2])) + + def checkCooldown(self, key, cd=0.5): + """右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品""" + now = time.time() + last = self.cooldown.get(key, 0) + if now - last < cd: + return False + self.cooldown[key] = now + return True diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/service/__init__.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/shaokao/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tuilun/动物养殖转化关系.txt b/tuilun/动物养殖转化关系.txt index d9d59f8..22c9618 100644 --- a/tuilun/动物养殖转化关系.txt +++ b/tuilun/动物养殖转化关系.txt @@ -47,7 +47,7 @@ todo : 手持种子 消耗16个种子 8. 鸡饲料盆_2(满) -> 鸡饲料盆_1(空) 转换条件:[鸡进食后计时变空]【机制待确认】 -todo : 计时变换 先三分钟 +todo : 计时变换 先一分钟 ======================================== 三、饲料槽(牛羊等) @@ -58,7 +58,7 @@ todo : 手持小麦 消耗32个小麦 10. 饲料槽_2(满小麦) -> 饲料槽_3(半量小麦) -> 饲料槽_4(少量小麦) -> 饲料槽_1(空) 转换条件:[动物进食后计时逐级减少]【机制待确认】 -todo : 计时变换 先三分钟 逐级都是 计时变化 +todo : 计时变换 先一分钟 逐级都是 计时变化 11. 饲料槽_1(空) -> 饲料槽_6(水槽) 转换条件:[手持 水桶 右键 进行转换,消耗水桶]【操作方式待确认】 @@ -70,14 +70,15 @@ todo : 手持胡萝卜 消耗32个胡萝卜 13. 饲料槽_7(满胡萝卜) -> 饲料槽_8(半量胡萝卜) -> 饲料槽_1(空) 转换条件:[动物进食后计时逐级减少]【机制待确认】 -todo : 计时变换 先三分钟 逐级都是 计时变化 +todo : 计时变换 先一分钟 逐级都是 计时变化 ======================================== 四、粪便 14. 粪便(一坨) -> 粪便(半坨) -> 粪便(仅痕迹) -> 无 转换条件:[右键 进行转换,取走/清理粪便] -机制 : 右键取走一个等级,逐级清理至消失(暂定,可先不管) +todo 这玩意也可食用 +【食用部分后续在总食用功能中实现,本次不实现】 ======================================== 五、其他静态方块 diff --git a/tuilun/烧烤转化关系.txt b/tuilun/烧烤转化关系.txt index b4dbb25..a46e9fc 100644 --- a/tuilun/烧烤转化关系.txt +++ b/tuilun/烧烤转化关系.txt @@ -40,7 +40,8 @@ 转换条件:[放到烧烤架上,手持右键 2 次进行转换] 机制: 放在烧烤架上,右键第1次:生->半熟,右键第2次:半熟->熟(不计时) -todo 熟的烤串也都可以直接右键食用 +todo 熟的烤串也都可以直接右键食用 +【食用部分后续在总食用功能中实现,本次不实现】 ======================================== 三、烤鸡(上支杆 + 篝火烤,非烧烤架) ======================================== @@ -63,6 +64,7 @@ todo 熟的烤串也都可以直接右键食用 12. 烤鸡(整只) -> 烤鸡(3/4只) -> 烤鸡(半只) -> 烤鸡(1/4只) -> (吃完消失) 转换条件:[吃东西(右键食用)转换,每吃一份降一级] 机制: 右键食用,吃一份: 整只->3/4->半->1/4,最后一份吃完方块消失 +【食用部分后续在总食用功能中实现,本次不实现】 ======================================== 四、烤乳猪(上支杆 + 篝火烤,非烧烤架) @@ -82,6 +84,7 @@ todo 熟的烤串也都可以直接右键食用 16. 烤乳猪(整只/6份) -> 烤乳猪(5/6只) -> 烤乳猪(4/6只) -> 烤乳猪(3/6只) -> 烤乳猪(2/6只) -> 烤乳猪(1/6只) -> (吃完消失) 转换条件:[吃东西(右键食用)转换,每吃一份,剩余份数递减] 机制: 右键食用,共6份,吃一份剩余递减1/6: 整只->5/6->4/6->3/6->2/6->1/6,最后一份吃完方块消失 +【食用部分后续在总食用功能中实现,本次不实现】 ======================================== 五、烤鱼(上支杆 + 篝火烤,非烧烤架) @@ -94,6 +97,7 @@ todo 熟的烤串也都可以直接右键食用 转换条件:[支杆下方篝火,计时自动转换] 机制: 支杆下方篝火计时约60秒烤熟 todo 烤鳕鱼(熟)可以直接右键食用 +【食用部分后续在总食用功能中实现,本次不实现】 19. 鲑鱼 -> 烤鲑鱼(生) 转换条件:[手持 鲑鱼 右键 支杆(b_34) 进行转换,消耗1条] 机制: 手持鲑鱼右键支杆,上杆待烤,变为烤鲑鱼(生) @@ -102,6 +106,7 @@ todo 烤鳕鱼(熟)可以直接右键食用 转换条件:[支杆下方篝火,计时自动转换] 机制: 支杆下方篝火计时约60秒烤熟 todo 烤鲑鱼(熟)可以直接右键食用 +【食用部分后续在总食用功能中实现,本次不实现】 ======================================== 六、方块归属说明(均有转换,无静态成品) ========================================