烧烤 动物养殖 分类 转换功能 计时转换 退出重新判断tick 解耦
This commit is contained in:
1 parent
f359008ac3
commit
991239bc26
4 files changed
+264
-432
No files matched your search
+68
-196
@@ -1,27 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
动物养殖引擎(服务端)
|
||||
动物养殖·右键转换引擎(服务端)
|
||||
================================================================================
|
||||
只处理"动物养殖"(xy_animal) 分类下的方块转换:鸡窝、饲料盆、饲料槽、粪便。
|
||||
只处理"动物养殖"(xy_animal) 分类下的【右键切换型】转换:
|
||||
放蛋/取蛋、填充饲料、加水、清理粪便。
|
||||
|
||||
【与 BlockSwitchService / CookingService 的区别】
|
||||
那些系统各自监听 ServerBlockUseEvent,右键时会互相跑一遍拦截。
|
||||
本系统(方案A)改为"统一分发器"架构:
|
||||
- FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统
|
||||
- 本类通过 FeatureDispatcher.register(self) 把管辖的方块名注册进去
|
||||
- 玩家右键方块时,FeatureDispatcher 用 O(1) 的 dict 直接找到本实例
|
||||
- 本类只查自己的 AnimalData(ANIMAL_SWITCHES),不碰其他功能的数据库
|
||||
→ 每个右键事件只查一次数据库,功能损耗最小
|
||||
【与计时转换解耦】
|
||||
同分类的"计时自动转换"(饲料盆/饲料槽随时间变空)已拆到独立类:
|
||||
AnimalTimerService —— 处理 ServerBlockEntityTickEvent 转发的计时逻辑
|
||||
本类只负责右键事件,两者通过 FeatureDispatcher 协作:
|
||||
- 本类右键碰到"计时源方块"(如装满的饲料盆)→ 调 FeatureDispatcher.startTimerFor
|
||||
转发给 AnimalTimerService 启动计时
|
||||
- 计时到点转换完全由 AnimalTimerService 负责,本类不碰任何计时数据
|
||||
|
||||
【本类不做的事】
|
||||
- 不自己监听 ServerBlockUseEvent(由 FeatureDispatcher 统一转发 handleBlockUse)
|
||||
- 不处理其他分类的方块(数据只来自 AnimalData)
|
||||
【事件监听在哪?】
|
||||
本类【不】监听任何事件!
|
||||
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
|
||||
- ServerBlockUseEvent → OnBlockUse → 按 blockOwner 路由到本类的 handleBlockUse
|
||||
- ServerBlockEntityTickEvent → OnBlockEntityTick → 按 timerOwner 路由到 AnimalTimerService
|
||||
本类只实现被分发器调用的接口方法,不关心事件来源。
|
||||
|
||||
【转换类型】
|
||||
- type="switch" : 右键直接转换(放蛋/取蛋/填充饲料/清理粪便/加水)
|
||||
- type="timer" : 计时自动转换(饲料盆/饲料槽随时间变空),
|
||||
由本类用 AddTimer 实现,不依赖方块实体 tick
|
||||
【本类怎么做(自上而下的方法系列)】
|
||||
handleBlockUse ← 入口:分发器转发右键事件
|
||||
├─ findRecipe ← 匹配 (方块名, 物品名) 规则
|
||||
├─ checkCooldown ← 连点冷却(防刷提示/刷物品)
|
||||
├─ forwardStartTimer ← 计时源方块 → 转发给计时服务启动计时
|
||||
└─ handleSwitch ← 切换型规则具体执行
|
||||
├─ consumeItem ← 消耗物品
|
||||
├─ replaceBlock ← 替换方块
|
||||
├─ giveItem ← 直接给物品(塞背包)
|
||||
├─ dropItems ← 掉落物形式给物品(取蛋/清理)
|
||||
└─ startTimerFor← 新方块是计时源 → 转发给计时服务自动开始变空
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
@@ -35,9 +45,6 @@ comp = serverApi.GetEngineCompFactory() # 组件工厂
|
||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||
|
||||
# ---- 方块实体数据组件(计时持久化:把开始时间戳+时长写进方块,重启后继续计时) ----
|
||||
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
|
||||
|
||||
# ---- 只导入动物养殖自己的数据库 ----
|
||||
from ..AnimalData import ANIMAL_SWITCHES
|
||||
|
||||
@@ -47,34 +54,29 @@ from ...service.FeatureDispatcher import FeatureDispatcher
|
||||
|
||||
class AnimalService(BlueFoxServerSystem):
|
||||
"""
|
||||
动物养殖引擎
|
||||
动物养殖·右键转换引擎
|
||||
|
||||
【核心数据结构】
|
||||
animalRecipes = {
|
||||
(方块名, 物品名): 规则dict, # O(1) 查找
|
||||
}
|
||||
animalBlockSet = {方块名, ...} # O(1) 拦截/注册用
|
||||
animalTimers = {方块名: 规则dict} # 计时型规则的起始方块 → 规则
|
||||
animalRecipes = {(方块名, 物品名): 规则dict} # O(1) 查找,含 switch+timer 规则
|
||||
animalBlockSet = {方块名, ...} # O(1) 拦截/注册用(所有源方块)
|
||||
cooldown = {key: 时间戳} # 连点冷却表
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
"""
|
||||
初始化:构建自己的转换规则字典,并注册到统一分发器
|
||||
初始化:构建规则字典,并注册到统一分发器(让分发器把右键事件转发过来)。
|
||||
"""
|
||||
super(AnimalService, self).__init__(namespace, systemName)
|
||||
print "===== AnimalService init ====="
|
||||
|
||||
# ---- 构建动物养殖规则字典(O(1) 查找) ----
|
||||
# ---- 构建规则字典 ----
|
||||
# switch 和 timer 规则都收进来:右键"计时源方块"时才能识别并转发给计时服务
|
||||
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
|
||||
|
||||
# ---- 右键冷却表:防止连点瞬间触发多次 ----
|
||||
self.cooldown = {}
|
||||
@@ -83,7 +85,7 @@ class AnimalService(BlueFoxServerSystem):
|
||||
FeatureDispatcher.register(self)
|
||||
|
||||
# ============================================================================
|
||||
# 给 FeatureDispatcher 的接口
|
||||
# 一、给 FeatureDispatcher 的接口
|
||||
# ============================================================================
|
||||
|
||||
def getFeatureBlocks(self):
|
||||
@@ -115,29 +117,28 @@ class AnimalService(BlueFoxServerSystem):
|
||||
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
|
||||
key = (dim, x, y, z)
|
||||
|
||||
# ---- 拿玩家主手物品 ----
|
||||
# 拿玩家主手物品
|
||||
itemDict = self.getCarriedItem(playerId)
|
||||
|
||||
# ---- 查动物养殖规则(O(1)) ----
|
||||
# 查规则(O(1))
|
||||
recipe = self.findRecipe(blockName, itemDict)
|
||||
if not recipe:
|
||||
return False
|
||||
|
||||
# ---- 冷却检查(防止连点,规则可自定义冷却时间 switchCd) ----
|
||||
# 冷却检查(防止连点,规则可自定义冷却时间 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
|
||||
return self.forwardStartTimer(blockName, dim, pos, playerId)
|
||||
|
||||
# ---- 切换型规则:执行具体转换 ----
|
||||
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName)
|
||||
# 切换型规则:就地执行具体转换
|
||||
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key)
|
||||
return True
|
||||
|
||||
# ============================================================================
|
||||
# 规则匹配(O(1) 字典查找)
|
||||
# 二、规则匹配(O(1) 字典查找)
|
||||
# ============================================================================
|
||||
|
||||
def findRecipe(self, blockName, itemDict):
|
||||
@@ -150,16 +151,24 @@ class AnimalService(BlueFoxServerSystem):
|
||||
recipe = self.animalRecipes.get((blockName, itemName))
|
||||
if recipe:
|
||||
return recipe
|
||||
recipe = self.animalRecipes.get((blockName, None))
|
||||
if recipe:
|
||||
return recipe
|
||||
return None
|
||||
return self.animalRecipes.get((blockName, None))
|
||||
|
||||
# ============================================================================
|
||||
# 切换型规则处理
|
||||
# 三、计时转发(把"计时源方块"的启动任务交给 AnimalTimerService)
|
||||
# ============================================================================
|
||||
|
||||
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName):
|
||||
def forwardStartTimer(self, blockName, dim, pos, playerId):
|
||||
"""
|
||||
右键碰到"计时源方块"(如装满的饲料盆)时,把启动计时转交给计时服务。
|
||||
通过 FeatureDispatcher.startTimerFor 按方块名 O(1) 路由到 AnimalTimerService。
|
||||
"""
|
||||
return FeatureDispatcher.startTimerFor(blockName, dim, pos, playerId)
|
||||
|
||||
# ============================================================================
|
||||
# 四、切换型规则处理
|
||||
# ============================================================================
|
||||
|
||||
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key):
|
||||
"""
|
||||
处理"右键直接转换"类规则。
|
||||
|
||||
@@ -180,10 +189,10 @@ class AnimalService(BlueFoxServerSystem):
|
||||
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)
|
||||
# ★ 新方块若是"计时源"(如刚装满的饲料盆)→ 转发给计时服务自动开始变空
|
||||
target = recipe.get("target")
|
||||
if target and target != "minecraft:air":
|
||||
FeatureDispatcher.startTimerFor(target, dim, pos, playerId)
|
||||
|
||||
# 转换成功提示
|
||||
if recipe.get("tip"):
|
||||
@@ -207,144 +216,7 @@ class AnimalService(BlueFoxServerSystem):
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
||||
|
||||
# ============================================================================
|
||||
# 计时型规则处理(方块实体 tick 持久化计时)
|
||||
# ============================================================================
|
||||
|
||||
def startTimer(self, key, recipe, dim, playerId=None):
|
||||
"""
|
||||
启动计时:把 计时标识+开始时间戳+时长 写入方块实体数据。
|
||||
- 已处于本规则计时中的方块不会重复重置时间(防止空手右键刷新计时)
|
||||
- 到点判断由 FeatureDispatcher 转发的 ServerBlockEntityTickEvent 完成
|
||||
- 时间戳存在方块上 → 服务器重启/玩家重进后继续计时
|
||||
"""
|
||||
pos = (key[1], key[2], key[3])
|
||||
# 去重:如果该方块已经在本规则计时中,不重置(保持原剩余时间)
|
||||
try:
|
||||
data = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if data:
|
||||
if data["rtdd_tid"] == recipe["source"]:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
self._writeTimerData(dim, pos, recipe, playerId)
|
||||
# 计时开始提示
|
||||
if playerId and recipe.get("startTip"):
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["startTip"])
|
||||
|
||||
def handleBlockEntityTick(self, args):
|
||||
"""FeatureDispatcher 转发:计时方块每 tick 检查是否到点"""
|
||||
dim = args.get("dimension")
|
||||
pos = (args.get("posX"), args.get("posY"), args.get("posZ"))
|
||||
blockName = args.get("blockName", "")
|
||||
self._tickProcess(dim, pos, blockName)
|
||||
|
||||
def _tickProcess(self, dim, pos, blockName):
|
||||
"""
|
||||
tick 主逻辑:
|
||||
1) 按事件自带的当前方块名判断是否计时源(创造直接放置也会进来)
|
||||
2) 是计时源但没有计时数据 → 自动补写计时(放置即开始计时)
|
||||
3) 有计时数据 → 到点执行转换(重启后时间戳照常生效)
|
||||
方块被拆掉后 tick 停止、数据随方块消失,不会在原位置继续计时转换。
|
||||
注意:GetBlockEntityData 返回的对象不支持 .get(),这里统一用 [] 读取。
|
||||
"""
|
||||
try:
|
||||
recipe = self.animalTimers.get(blockName)
|
||||
if not recipe:
|
||||
return # 当前方块不是计时源(tick 只对计时源方块开启)
|
||||
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
|
||||
# 读取计时标识;没有或与当前方块不符
|
||||
try:
|
||||
tid = blockData["rtdd_tid"]
|
||||
except Exception:
|
||||
tid = ""
|
||||
if tid != blockName:
|
||||
# 放置即自动计时:默认开启(饲料盆/饲料槽)
|
||||
# 烧烤(autoStart=False)必须右键激活,这里不补写
|
||||
if recipe.get("autoStart", True):
|
||||
self._writeTimerData(dim, pos, recipe, None)
|
||||
return
|
||||
|
||||
tstart = blockData["rtdd_tstart"]
|
||||
tdur = blockData["rtdd_tdur"]
|
||||
|
||||
if time.time() - tstart >= tdur:
|
||||
self._finishTimer(dim, pos, recipe, blockData)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _finishTimer(self, dim, pos, recipe, data):
|
||||
"""
|
||||
计时到点:确认方块仍是计时源后替换方块。
|
||||
- 若新方块是下一级计时源 → 写入新计时数据继续
|
||||
- 若整条链结束 → 清除计时数据,并给启动者发送完成提示
|
||||
"""
|
||||
try:
|
||||
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
|
||||
if cur and cur.get("name") != recipe["source"]:
|
||||
# 方块已被玩家改变 → 清掉过期计时
|
||||
self._clearTimerData(dim, pos)
|
||||
return
|
||||
# 替换方块
|
||||
self.replaceBlock(pos, dim, recipe["target"], None)
|
||||
nextRecipe = self.animalTimers.get(recipe["target"])
|
||||
if nextRecipe:
|
||||
# 下一级计时(换新的开始时间戳)
|
||||
self._writeTimerData(dim, pos, nextRecipe, data["rtdd_tp"])
|
||||
else:
|
||||
# 整条链结束:清数据 + 完成提示
|
||||
self._clearTimerData(dim, pos)
|
||||
doneTip = recipe.get("doneTip")
|
||||
if doneTip:
|
||||
self._notifyIfOnline(data["rtdd_tp"], doneTip)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ---- 计时数据读写(存在方块实体数据里,重启不丢) ----
|
||||
|
||||
def _writeTimerData(self, dim, pos, recipe, playerId=None):
|
||||
"""把计时信息写进方块实体数据"""
|
||||
try:
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
now = time.time()
|
||||
blockData["rtdd_tid"] = recipe["source"]
|
||||
blockData["rtdd_tstart"] = now
|
||||
blockData["rtdd_tdur"] = recipe.get("time", 60)
|
||||
blockData["rtdd_tp"] = playerId if playerId else ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _clearTimerData(self, dim, pos):
|
||||
"""清除方块上的计时数据"""
|
||||
try:
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
blockData["rtdd_tid"] = ""
|
||||
blockData["rtdd_tstart"] = 0
|
||||
blockData["rtdd_tdur"] = 0
|
||||
blockData["rtdd_tp"] = ""
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _notifyIfOnline(self, playerId, tip):
|
||||
"""若启动者在线,发送完成提示(离线则跳过)"""
|
||||
if not tip or not playerId:
|
||||
return
|
||||
try:
|
||||
ids = comp.CreateGame(LevelId).GetAllPlayerIds()
|
||||
if playerId in ids:
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, tip)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:获取玩家主手物品
|
||||
# 五、工具方法:获取玩家主手物品
|
||||
# ============================================================================
|
||||
|
||||
def getCarriedItem(self, playerId):
|
||||
@@ -353,7 +225,7 @@ class AnimalService(BlueFoxServerSystem):
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:消耗物品
|
||||
# 六、工具方法:消耗物品
|
||||
# ============================================================================
|
||||
|
||||
def consumeItem(self, playerId, itemDict, amount=1):
|
||||
@@ -373,7 +245,7 @@ class AnimalService(BlueFoxServerSystem):
|
||||
"auxValue": itemDict.get("newAuxValue", 0)})
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:替换方块
|
||||
# 七、工具方法:替换方块
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
|
||||
@@ -396,7 +268,7 @@ class AnimalService(BlueFoxServerSystem):
|
||||
dimensionId=dim)
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:给玩家物品(塞背包)
|
||||
# 八、工具方法:给玩家物品(塞背包)
|
||||
# ============================================================================
|
||||
|
||||
def giveItem(self, playerId, itemName, count=1, auxValue=0):
|
||||
@@ -404,7 +276,7 @@ class AnimalService(BlueFoxServerSystem):
|
||||
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:以掉落物形式生成物品(取蛋/取下物品等,符合"获得"体验)
|
||||
# 九、工具方法:以掉落物形式生成物品(取蛋/取下物品等,符合"获得"体验)
|
||||
# ============================================================================
|
||||
|
||||
def dropItems(self, dim, pos, dropList):
|
||||
@@ -421,13 +293,13 @@ class AnimalService(BlueFoxServerSystem):
|
||||
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:冷却检查
|
||||
# 十、工具方法:冷却检查
|
||||
# ============================================================================
|
||||
|
||||
def checkCooldown(self, key, cd=0.5):
|
||||
"""
|
||||
右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品。
|
||||
放蛋等需要快速连点的规则可在数据里用 switchCd 调小冷却。
|
||||
需要快速连点的规则可在数据里用 switchCd 调小冷却。
|
||||
"""
|
||||
now = time.time()
|
||||
last = self.cooldown.get(key, 0)
|
||||
|
||||
Reference in new issue
Block a user