Files
rtdd/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py
T

252 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
================================================================================
统一事件分发器(服务端)—— 方案A核心
================================================================================
【事件监听在哪?—— 全模组只有这一个类监听游戏事件】
所有右键/计时相关事件都由本类(FeatureDispatcher)统一监听:
- ServerBlockUseEvent 玩家右键方块 → 按 blockOwner 路由
- ServerItemUseOnEvent 手持物品右键方块表面 → 按 itemUseOnOwner 路由
- ServerItemTryUseEvent 拦截喝药水/吃食物等默认行为(配合右键转换)
- ServerBlockEntityTickEvent 计时方块每tick → 按 timerOwner 路由
其他所有功能服务(AnimalService/BarbecueService 及各自的 TimerService)
【都不监听事件】。它们只是"逻辑处理类",被本类调用:
- 右键服务实现 handleBlockUse / handleItemUseOn,由本类转发
- 计时服务实现 handleBlockEntityTick / startTimer,由本类转发
【为什么要它】
以前每个功能都各自监听 ServerBlockUseEvent,玩家每次右键所有系统都被触发
一遍、各自跑一次数据库,功能越多损耗越大。
现在分发器用"方块名→服务"的 O(1) dict 路由,每个事件只命中一个服务。
【如何新增一个功能】
1. 新建 xxx/xxxData.py 写转换规则
2. 右键转换 → 新建 xxx/service/XxxService.py(继承 BlueFoxServerSystem):
- __init__ 里构建规则字典 + 调 FeatureDispatcher.register(self)
- 实现 getFeatureBlocks() / getItemUseOnRules() / handleBlockUse(...)
3. 计时转换 → 新建 xxx/service/XxxTimerService.py(继承 BlueFoxServerSystem):
- __init__ 里构建计时规则字典 + 调 FeatureDispatcher.registerTimer(self)
- 实现 getTimerBlocks() / handleBlockEntityTick(...) / startTimer(...)
- 右键服务要"启动计时"时,调 FeatureDispatcher.startTimerFor(...) 转发过来
4. 在 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):
"""
统一事件分发器(全模组唯一监听事件的地方)
【类级注册表(所有功能服务共享同一份路由表,O(1) 定位)】
blockOwner = {方块名: 右键服务实例} # ServerBlockUseEvent 用
itemUseOnOwner = {物品名: 右键服务实例} # ServerItemUseOnEvent 用
timerOwner = {方块名: 计时服务实例} # ServerBlockEntityTickEvent 用
"""
# ---- 类级共享路由表(用类变量,任何实例都能读写) ----
blockOwner = {}
blockSet = set()
itemUseOnOwner = {}
itemUseOnSet = set()
timerOwner = {}
timerSet = set()
# ============================================================================
# 注册接口(功能服务在 __init__ 里调用)
# ============================================================================
@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)
@classmethod
def registerTimer(cls, service):
"""
计时服务初始化时调用:把管辖的"计时源方块"注册进计时路由表。
ServerBlockEntityTickEvent 会按此表把 tick 事件路由到对应的计时服务。
"""
for block in service.getTimerBlocks():
cls.timerOwner[block] = service
cls.timerSet.add(block)
@classmethod
def startTimerFor(cls, blockName, dim, pos, playerId=None):
"""
右键服务调用:把"计时源方块"的计时启动任务转发给对应的计时服务。
- 例如:烧烤右键"烤鸡(生)"开始烤制;动物装满饲料盆后自动开始变空计时
- 内部通过 timerOwner 路由表 O(1) 找到计时服务,调它的 startTimer
返回 True=找到了计时服务并处理;False=该方块不属于任何计时服务
"""
service = cls.timerOwner.get(blockName)
if not service:
return False
service.startTimer(dim, pos, blockName, playerId)
return True
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:玩家右键方块 → 按 blockOwner 路由到右键服务
# ============================================================================
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:手持物品右键方块表面 → 按 itemUseOnOwner 路由到右键服务
# ============================================================================
def OnItemUseOn(self, args):
"""
手持物品右键某个方块表面时触发(如手持原版生鸡肉右键地面)。
用手持物品名 O(1) 定位到唯一的功能服务,转发给它的 handleItemUseOn。
取消"使用物品于方块"的字段是 ret(不是 cancel),官方要求双端设置才能
真正取消默认行为。本分发器已在服务端设 ret=True。
"""
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 → 按 timerOwner 路由到计时服务(持久化计时)
# ============================================================================
def OnBlockEntityTick(self, args):
"""
配置了 tick:true 的方块每帧触发(官方频率约每秒20次)。
用于"烤制/饲料变空"等计时转换的持久化:
- 转换开始时把 开始时间戳+时长 写进方块实体数据
- 服务器重启/玩家重进后,tick 恢复,读回时间戳继续计时,
到点即转换(例如烤鸡烤了40秒退出,回来只剩20秒)
路由:用事件自带的 blockName 在 timerOwner 里 O(1) 找到计时服务,
转发给它的 handleBlockEntityTick(省掉每帧读方块,性能更好)。
注意:事件参数是 blockName/dimension/posX/posY/posZ(不是 posDim!)
"""
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.timerOwner.get(blockName) # 计时源方块一定注册在 timerOwner
if not service:
return
service.handleBlockEntityTick(args)