# -*- 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 from ...BlueFoxTools.ToolsConfig import LevelId # ---- 方块实体数据组件(计时持久化:记录开始时间戳,重启后继续计时) ---- blockEntityDataComp = comp.CreateBlockEntityData(LevelId) 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 —— 玩家尝试使用物品(拦截喝药水/吃食物等默认行为) 4. ServerBlockEntityTickEvent —— 计时方块每tick触发(烤制/饲料变空等,持久化计时) """ 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) self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerBlockEntityTickEvent", self, self.OnBlockEntityTick) 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) self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerBlockEntityTickEvent", self, self.OnBlockEntityTick) # ============================================================================ # 主入口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 # ============================================================================ # 主入口4:计时方块 tick(持久化计时) # ============================================================================ def OnBlockEntityTick(self, args): """ 配置了 tick:true 的方块每帧触发(官方频率约每秒20次)。 用于"烤制/饲料变空"等计时转换的持久化: - 转换开始时把 开始时间戳+时长 写进方块实体数据 - 服务器重启/玩家重进后,tick 恢复,读回时间戳继续计时, 到点即转换(例如烤鸡烤了40秒退出,回来只剩20秒) 注意:该事件参数是 blockName/dimension/posX/posY/posZ(不是 posDim!) 直接用事件自带的 blockName 路由,省掉每帧读方块,性能更好。 """ posX = args.get("posX") posY = args.get("posY") posZ = args.get("posZ") dimension = args.get("dimension") if posX is None or posY is None or posZ is None or dimension is None: return blockName = args.get("blockName", "") service = self.blockOwner.get(blockName) # 计时源方块一定注册在路由表里 if not service: return service.handleBlockEntityTick(args)