烧烤 动物养殖 分类 转换功能 计时转换 退出重新判断tick 解耦
This commit is contained in:
1 parent
f359008ac3
commit
991239bc26
4 files changed
+264
-432
No files matched your search
@@ -3,7 +3,9 @@ from xiangyemod.service.CookingService import CookingService
|
|||||||
from xiangyemod.service.BlockSwitchService import BlockSwitchService
|
from xiangyemod.service.BlockSwitchService import BlockSwitchService
|
||||||
from xiangyemod.service.FeatureDispatcher import FeatureDispatcher
|
from xiangyemod.service.FeatureDispatcher import FeatureDispatcher
|
||||||
from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService
|
from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService
|
||||||
|
from xiangyemod.dongwuyangzhi.service.AnimalTimerService import AnimalTimerService
|
||||||
from xiangyemod.shaokao.service.BarbecueService import BarbecueService
|
from xiangyemod.shaokao.service.BarbecueService import BarbecueService
|
||||||
|
from xiangyemod.shaokao.service.BarbecueTimerService import BarbecueTimerService
|
||||||
|
|
||||||
if not serverApi.IsInServer():
|
if not serverApi.IsInServer():
|
||||||
pass
|
pass
|
||||||
|
|||||||
+68
-196
@@ -1,27 +1,37 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
================================================================================
|
================================================================================
|
||||||
动物养殖引擎(服务端)
|
动物养殖·右键转换引擎(服务端)
|
||||||
================================================================================
|
================================================================================
|
||||||
只处理"动物养殖"(xy_animal) 分类下的方块转换:鸡窝、饲料盆、饲料槽、粪便。
|
只处理"动物养殖"(xy_animal) 分类下的【右键切换型】转换:
|
||||||
|
放蛋/取蛋、填充饲料、加水、清理粪便。
|
||||||
|
|
||||||
【与 BlockSwitchService / CookingService 的区别】
|
【与计时转换解耦】
|
||||||
那些系统各自监听 ServerBlockUseEvent,右键时会互相跑一遍拦截。
|
同分类的"计时自动转换"(饲料盆/饲料槽随时间变空)已拆到独立类:
|
||||||
本系统(方案A)改为"统一分发器"架构:
|
AnimalTimerService —— 处理 ServerBlockEntityTickEvent 转发的计时逻辑
|
||||||
- FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统
|
本类只负责右键事件,两者通过 FeatureDispatcher 协作:
|
||||||
- 本类通过 FeatureDispatcher.register(self) 把管辖的方块名注册进去
|
- 本类右键碰到"计时源方块"(如装满的饲料盆)→ 调 FeatureDispatcher.startTimerFor
|
||||||
- 玩家右键方块时,FeatureDispatcher 用 O(1) 的 dict 直接找到本实例
|
转发给 AnimalTimerService 启动计时
|
||||||
- 本类只查自己的 AnimalData(ANIMAL_SWITCHES),不碰其他功能的数据库
|
- 计时到点转换完全由 AnimalTimerService 负责,本类不碰任何计时数据
|
||||||
→ 每个右键事件只查一次数据库,功能损耗最小
|
|
||||||
|
|
||||||
【本类不做的事】
|
【事件监听在哪?】
|
||||||
- 不自己监听 ServerBlockUseEvent(由 FeatureDispatcher 统一转发 handleBlockUse)
|
本类【不】监听任何事件!
|
||||||
- 不处理其他分类的方块(数据只来自 AnimalData)
|
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
|
||||||
|
- ServerBlockUseEvent → OnBlockUse → 按 blockOwner 路由到本类的 handleBlockUse
|
||||||
|
- ServerBlockEntityTickEvent → OnBlockEntityTick → 按 timerOwner 路由到 AnimalTimerService
|
||||||
|
本类只实现被分发器调用的接口方法,不关心事件来源。
|
||||||
|
|
||||||
【转换类型】
|
【本类怎么做(自上而下的方法系列)】
|
||||||
- type="switch" : 右键直接转换(放蛋/取蛋/填充饲料/清理粪便/加水)
|
handleBlockUse ← 入口:分发器转发右键事件
|
||||||
- type="timer" : 计时自动转换(饲料盆/饲料槽随时间变空),
|
├─ findRecipe ← 匹配 (方块名, 物品名) 规则
|
||||||
由本类用 AddTimer 实现,不依赖方块实体 tick
|
├─ checkCooldown ← 连点冷却(防刷提示/刷物品)
|
||||||
|
├─ forwardStartTimer ← 计时源方块 → 转发给计时服务启动计时
|
||||||
|
└─ handleSwitch ← 切换型规则具体执行
|
||||||
|
├─ consumeItem ← 消耗物品
|
||||||
|
├─ replaceBlock ← 替换方块
|
||||||
|
├─ giveItem ← 直接给物品(塞背包)
|
||||||
|
├─ dropItems ← 掉落物形式给物品(取蛋/清理)
|
||||||
|
└─ startTimerFor← 新方块是计时源 → 转发给计时服务自动开始变空
|
||||||
================================================================================
|
================================================================================
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
@@ -35,9 +45,6 @@ comp = serverApi.GetEngineCompFactory() # 组件工厂
|
|||||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||||
|
|
||||||
# ---- 方块实体数据组件(计时持久化:把开始时间戳+时长写进方块,重启后继续计时) ----
|
|
||||||
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
|
|
||||||
|
|
||||||
# ---- 只导入动物养殖自己的数据库 ----
|
# ---- 只导入动物养殖自己的数据库 ----
|
||||||
from ..AnimalData import ANIMAL_SWITCHES
|
from ..AnimalData import ANIMAL_SWITCHES
|
||||||
|
|
||||||
@@ -47,34 +54,29 @@ from ...service.FeatureDispatcher import FeatureDispatcher
|
|||||||
|
|
||||||
class AnimalService(BlueFoxServerSystem):
|
class AnimalService(BlueFoxServerSystem):
|
||||||
"""
|
"""
|
||||||
动物养殖引擎
|
动物养殖·右键转换引擎
|
||||||
|
|
||||||
【核心数据结构】
|
【核心数据结构】
|
||||||
animalRecipes = {
|
animalRecipes = {(方块名, 物品名): 规则dict} # O(1) 查找,含 switch+timer 规则
|
||||||
(方块名, 物品名): 规则dict, # O(1) 查找
|
animalBlockSet = {方块名, ...} # O(1) 拦截/注册用(所有源方块)
|
||||||
}
|
cooldown = {key: 时间戳} # 连点冷却表
|
||||||
animalBlockSet = {方块名, ...} # O(1) 拦截/注册用
|
|
||||||
animalTimers = {方块名: 规则dict} # 计时型规则的起始方块 → 规则
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, namespace, systemName):
|
def __init__(self, namespace, systemName):
|
||||||
"""
|
"""
|
||||||
初始化:构建自己的转换规则字典,并注册到统一分发器
|
初始化:构建规则字典,并注册到统一分发器(让分发器把右键事件转发过来)。
|
||||||
"""
|
"""
|
||||||
super(AnimalService, self).__init__(namespace, systemName)
|
super(AnimalService, self).__init__(namespace, systemName)
|
||||||
print "===== AnimalService init ====="
|
print "===== AnimalService init ====="
|
||||||
|
|
||||||
# ---- 构建动物养殖规则字典(O(1) 查找) ----
|
# ---- 构建规则字典 ----
|
||||||
|
# switch 和 timer 规则都收进来:右键"计时源方块"时才能识别并转发给计时服务
|
||||||
self.animalRecipes = {}
|
self.animalRecipes = {}
|
||||||
self.animalBlockSet = set()
|
self.animalBlockSet = set()
|
||||||
self.animalTimers = {}
|
|
||||||
for sw in ANIMAL_SWITCHES:
|
for sw in ANIMAL_SWITCHES:
|
||||||
key = (sw["source"], sw.get("item"))
|
key = (sw["source"], sw.get("item"))
|
||||||
self.animalRecipes[key] = sw
|
self.animalRecipes[key] = sw
|
||||||
self.animalBlockSet.add(sw["source"])
|
self.animalBlockSet.add(sw["source"])
|
||||||
# 收集计时型规则的"起始方块",方便转换后自动启动计时
|
|
||||||
if sw.get("type") == "timer":
|
|
||||||
self.animalTimers[sw["source"]] = sw
|
|
||||||
|
|
||||||
# ---- 右键冷却表:防止连点瞬间触发多次 ----
|
# ---- 右键冷却表:防止连点瞬间触发多次 ----
|
||||||
self.cooldown = {}
|
self.cooldown = {}
|
||||||
@@ -83,7 +85,7 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
FeatureDispatcher.register(self)
|
FeatureDispatcher.register(self)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 给 FeatureDispatcher 的接口
|
# 一、给 FeatureDispatcher 的接口
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def getFeatureBlocks(self):
|
def getFeatureBlocks(self):
|
||||||
@@ -115,29 +117,28 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
|
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
|
||||||
key = (dim, x, y, z)
|
key = (dim, x, y, z)
|
||||||
|
|
||||||
# ---- 拿玩家主手物品 ----
|
# 拿玩家主手物品
|
||||||
itemDict = self.getCarriedItem(playerId)
|
itemDict = self.getCarriedItem(playerId)
|
||||||
|
|
||||||
# ---- 查动物养殖规则(O(1)) ----
|
# 查规则(O(1))
|
||||||
recipe = self.findRecipe(blockName, itemDict)
|
recipe = self.findRecipe(blockName, itemDict)
|
||||||
if not recipe:
|
if not recipe:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# ---- 冷却检查(防止连点,规则可自定义冷却时间 switchCd) ----
|
# 冷却检查(防止连点,规则可自定义冷却时间 switchCd)
|
||||||
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
|
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
|
||||||
return True # 冷却中,虽然不执行但也算"处理了",避免默认行为
|
return True # 冷却中,虽然不执行但也算"处理了",避免默认行为
|
||||||
|
|
||||||
# ---- 计时型规则:启动/续计时,不立即转换 ----
|
# 计时型规则:右键碰到"计时源方块" → 转发给计时服务启动计时
|
||||||
if recipe.get("type") == "timer":
|
if recipe.get("type") == "timer":
|
||||||
self.startTimer(key, recipe, dim, playerId)
|
return self.forwardStartTimer(blockName, dim, pos, playerId)
|
||||||
return True
|
|
||||||
|
|
||||||
# ---- 切换型规则:执行具体转换 ----
|
# 切换型规则:就地执行具体转换
|
||||||
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName)
|
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 规则匹配(O(1) 字典查找)
|
# 二、规则匹配(O(1) 字典查找)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def findRecipe(self, blockName, itemDict):
|
def findRecipe(self, blockName, itemDict):
|
||||||
@@ -150,16 +151,24 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
recipe = self.animalRecipes.get((blockName, itemName))
|
recipe = self.animalRecipes.get((blockName, itemName))
|
||||||
if recipe:
|
if recipe:
|
||||||
return recipe
|
return recipe
|
||||||
recipe = self.animalRecipes.get((blockName, None))
|
return self.animalRecipes.get((blockName, None))
|
||||||
if recipe:
|
|
||||||
return recipe
|
|
||||||
return 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"):
|
if recipe.get("giveItem"):
|
||||||
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
|
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
|
||||||
|
|
||||||
# ★ 如果新方块是某个计时链的起始方块 → 自动启动计时(如刚装满的饲料盆)
|
# ★ 新方块若是"计时源"(如刚装满的饲料盆)→ 转发给计时服务自动开始变空
|
||||||
if recipe.get("target") in self.animalTimers:
|
target = recipe.get("target")
|
||||||
nextTimer = self.animalTimers[recipe["target"]]
|
if target and target != "minecraft:air":
|
||||||
self.startTimer(key, nextTimer, dim, playerId)
|
FeatureDispatcher.startTimerFor(target, dim, pos, playerId)
|
||||||
|
|
||||||
# 转换成功提示
|
# 转换成功提示
|
||||||
if recipe.get("tip"):
|
if recipe.get("tip"):
|
||||||
@@ -207,144 +216,7 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
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):
|
def getCarriedItem(self, playerId):
|
||||||
@@ -353,7 +225,7 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 工具方法:消耗物品
|
# 六、工具方法:消耗物品
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def consumeItem(self, playerId, itemDict, amount=1):
|
def consumeItem(self, playerId, itemDict, amount=1):
|
||||||
@@ -373,7 +245,7 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
"auxValue": itemDict.get("newAuxValue", 0)})
|
"auxValue": itemDict.get("newAuxValue", 0)})
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 工具方法:替换方块
|
# 七、工具方法:替换方块
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
|
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
|
||||||
@@ -396,7 +268,7 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
dimensionId=dim)
|
dimensionId=dim)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 工具方法:给玩家物品(塞背包)
|
# 八、工具方法:给玩家物品(塞背包)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def giveItem(self, playerId, itemName, count=1, auxValue=0):
|
def giveItem(self, playerId, itemName, count=1, auxValue=0):
|
||||||
@@ -404,7 +276,7 @@ class AnimalService(BlueFoxServerSystem):
|
|||||||
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
|
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 工具方法:以掉落物形式生成物品(取蛋/取下物品等,符合"获得"体验)
|
# 九、工具方法:以掉落物形式生成物品(取蛋/取下物品等,符合"获得"体验)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def dropItems(self, dim, pos, dropList):
|
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]))
|
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 工具方法:冷却检查
|
# 十、工具方法:冷却检查
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def checkCooldown(self, key, cd=0.5):
|
def checkCooldown(self, key, cd=0.5):
|
||||||
"""
|
"""
|
||||||
右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品。
|
右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品。
|
||||||
放蛋等需要快速连点的规则可在数据里用 switchCd 调小冷却。
|
需要快速连点的规则可在数据里用 switchCd 调小冷却。
|
||||||
"""
|
"""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
last = self.cooldown.get(key, 0)
|
last = self.cooldown.get(key, 0)
|
||||||
|
|||||||
+70
-42
@@ -3,33 +3,33 @@
|
|||||||
================================================================================
|
================================================================================
|
||||||
统一事件分发器(服务端)—— 方案A核心
|
统一事件分发器(服务端)—— 方案A核心
|
||||||
================================================================================
|
================================================================================
|
||||||
【为什么需要它】
|
【事件监听在哪?—— 全模组只有这一个类监听游戏事件】
|
||||||
以前每个功能(做菜/工具切换)都各自监听 ServerBlockUseEvent,
|
所有右键/计时相关事件都由本类(FeatureDispatcher)统一监听:
|
||||||
玩家每次右键,所有系统都会被触发一遍、各自跑一次数据库拦截判断,
|
- ServerBlockUseEvent 玩家右键方块 → 按 blockOwner 路由
|
||||||
功能越多损耗越大。
|
- ServerItemUseOnEvent 手持物品右键方块表面 → 按 itemUseOnOwner 路由
|
||||||
|
- ServerItemTryUseEvent 拦截喝药水/吃食物等默认行为(配合右键转换)
|
||||||
|
- ServerBlockEntityTickEvent 计时方块每tick → 按 timerOwner 路由
|
||||||
|
|
||||||
【它做什么】
|
其他所有功能服务(AnimalService/BarbecueService 及各自的 TimerService)
|
||||||
FeatureDispatcher 是"唯一"监听右键相关事件的服务端系统:
|
【都不监听事件】。它们只是"逻辑处理类",被本类调用:
|
||||||
- ServerBlockUseEvent 玩家右键方块
|
- 右键服务实现 handleBlockUse / handleItemUseOn,由本类转发
|
||||||
- ServerItemUseOnEvent 手持物品右键方块表面(如放置生鸡肉)
|
- 计时服务实现 handleBlockEntityTick / startTimer,由本类转发
|
||||||
- ServerItemTryUseEvent 拦截喝药水/吃食物等默认行为
|
|
||||||
|
|
||||||
各功能服务在初始化时通过 FeatureDispatcher.register(self) 上报:
|
【为什么要它】
|
||||||
- 自己管辖的方块名集合 → 构建 方块名→服务 的 O(1) 路由表
|
以前每个功能都各自监听 ServerBlockUseEvent,玩家每次右键所有系统都被触发
|
||||||
- 自己处理的手持物品 → 构建 物品名→服务 的 O(1) 路由表
|
一遍、各自跑一次数据库,功能越多损耗越大。
|
||||||
|
现在分发器用"方块名→服务"的 O(1) dict 路由,每个事件只命中一个服务。
|
||||||
事件进来后,分发器先用 blockName / itemName 在路由表里 O(1) 定位到
|
|
||||||
唯一的那个功能服务,再调用它的 handleBlockUse / handleItemUseOn。
|
|
||||||
→ 每个右键事件只命中一个数据库,其余功能的数据库根本不会被查询。
|
|
||||||
|
|
||||||
【如何新增一个功能】
|
【如何新增一个功能】
|
||||||
1. 新建 xxx/xxxData.py 写转换规则
|
1. 新建 xxx/xxxData.py 写转换规则
|
||||||
2. 新建 xxx/service/XxxService.py(继承 BlueFoxServerSystem):
|
2. 右键转换 → 新建 xxx/service/XxxService.py(继承 BlueFoxServerSystem):
|
||||||
- __init__ 里构建自己的规则字典
|
- __init__ 里构建规则字典 + 调 FeatureDispatcher.register(self)
|
||||||
- 实现 getFeatureBlocks() / getItemUseOnRules()
|
- 实现 getFeatureBlocks() / getItemUseOnRules() / handleBlockUse(...)
|
||||||
- 实现 handleBlockUse(args) / handleItemUseOn(args)
|
3. 计时转换 → 新建 xxx/service/XxxTimerService.py(继承 BlueFoxServerSystem):
|
||||||
- 调用 FeatureDispatcher.register(self)
|
- __init__ 里构建计时规则字典 + 调 FeatureDispatcher.registerTimer(self)
|
||||||
3. 在 modMain.py import 这个服务类即可
|
- 实现 getTimerBlocks() / handleBlockEntityTick(...) / startTimer(...)
|
||||||
|
- 右键服务要"启动计时"时,调 FeatureDispatcher.startTimerFor(...) 转发过来
|
||||||
|
4. 在 modMain.py import 这些服务类即可(框架会自动实例化)
|
||||||
================================================================================
|
================================================================================
|
||||||
"""
|
"""
|
||||||
import mod.server.extraServerApi as serverApi
|
import mod.server.extraServerApi as serverApi
|
||||||
@@ -48,13 +48,12 @@ blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
|
|||||||
|
|
||||||
class FeatureDispatcher(BlueFoxServerSystem):
|
class FeatureDispatcher(BlueFoxServerSystem):
|
||||||
"""
|
"""
|
||||||
统一事件分发器
|
统一事件分发器(全模组唯一监听事件的地方)
|
||||||
|
|
||||||
【类级注册表(所有功能服务共享同一份路由表)】
|
【类级注册表(所有功能服务共享同一份路由表,O(1) 定位)】
|
||||||
blockOwner = {方块名: 功能服务实例}
|
blockOwner = {方块名: 右键服务实例} # ServerBlockUseEvent 用
|
||||||
blockSet = {方块名} # 全部功能方块集合
|
itemUseOnOwner = {物品名: 右键服务实例} # ServerItemUseOnEvent 用
|
||||||
itemUseOnOwner = {物品名: 功能服务实例}
|
timerOwner = {方块名: 计时服务实例} # ServerBlockEntityTickEvent 用
|
||||||
itemUseOnSet = {物品名} # 全部"手持物品右键方块表面"集合
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ---- 类级共享路由表(用类变量,任何实例都能读写) ----
|
# ---- 类级共享路由表(用类变量,任何实例都能读写) ----
|
||||||
@@ -62,11 +61,17 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
blockSet = set()
|
blockSet = set()
|
||||||
itemUseOnOwner = {}
|
itemUseOnOwner = {}
|
||||||
itemUseOnSet = set()
|
itemUseOnSet = set()
|
||||||
|
timerOwner = {}
|
||||||
|
timerSet = set()
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 注册接口(功能服务在 __init__ 里调用)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register(cls, service):
|
def register(cls, service):
|
||||||
"""
|
"""
|
||||||
功能服务初始化时调用,把管辖的方块和物品注册进路由表。
|
右键服务初始化时调用:把管辖的"右键方块"和"手持物品右键方块表面"注册进路由表。
|
||||||
同一方块/物品只会归属最后一个注册的服务(一般不会冲突)。
|
同一方块/物品只会归属最后一个注册的服务(一般不会冲突)。
|
||||||
"""
|
"""
|
||||||
# 右键方块路由:方块名 → 服务
|
# 右键方块路由:方块名 → 服务
|
||||||
@@ -79,9 +84,33 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
cls.itemUseOnOwner[itemName] = service
|
cls.itemUseOnOwner[itemName] = service
|
||||||
cls.itemUseOnSet.add(itemName)
|
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):
|
def __init__(self, namespace, systemName):
|
||||||
"""
|
"""
|
||||||
初始化:注册三个事件监听。
|
初始化:注册事件监听(全模组唯一监听点)。
|
||||||
注意:本类自身不注册任何功能方块,只负责把事件分发给已注册的服务。
|
注意:本类自身不注册任何功能方块,只负责把事件分发给已注册的服务。
|
||||||
"""
|
"""
|
||||||
super(FeatureDispatcher, self).__init__(namespace, systemName)
|
super(FeatureDispatcher, self).__init__(namespace, systemName)
|
||||||
@@ -94,7 +123,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
self.UnListenEvent()
|
self.UnListenEvent()
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 事件注册与注销
|
# 事件注册与注销(这是全模组唯一监听事件的地方)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def ListenEvent(self):
|
def ListenEvent(self):
|
||||||
@@ -133,7 +162,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
"ServerBlockEntityTickEvent", self, self.OnBlockEntityTick)
|
"ServerBlockEntityTickEvent", self, self.OnBlockEntityTick)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 主入口1:玩家右键方块
|
# 主入口1:玩家右键方块 → 按 blockOwner 路由到右键服务
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def OnBlockUse(self, args):
|
def OnBlockUse(self, args):
|
||||||
@@ -141,7 +170,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
玩家右键方块时触发。
|
玩家右键方块时触发。
|
||||||
用被右键的方块名 O(1) 定位到唯一的功能服务,转发给它的 handleBlockUse。
|
用被右键的方块名 O(1) 定位到唯一的功能服务,转发给它的 handleBlockUse。
|
||||||
|
|
||||||
服务返回 True = 处理成功 → 取消默认行为(如防止喝药水),并标记拦截下一次物品使用
|
服务返回 True = 处理成功 → 取消默认行为,并标记拦截下一次物品使用
|
||||||
服务返回 False = 当前组合无匹配规则 → 交还默认行为
|
服务返回 False = 当前组合无匹配规则 → 交还默认行为
|
||||||
"""
|
"""
|
||||||
blockName = args.get("blockName")
|
blockName = args.get("blockName")
|
||||||
@@ -157,7 +186,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
self._cancelItemUse[playerId] = True
|
self._cancelItemUse[playerId] = True
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 主入口2:手持物品右键方块表面
|
# 主入口2:手持物品右键方块表面 → 按 itemUseOnOwner 路由到右键服务
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def OnItemUseOn(self, args):
|
def OnItemUseOn(self, args):
|
||||||
@@ -165,10 +194,8 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
手持物品右键某个方块表面时触发(如手持原版生鸡肉右键地面)。
|
手持物品右键某个方块表面时触发(如手持原版生鸡肉右键地面)。
|
||||||
用手持物品名 O(1) 定位到唯一的功能服务,转发给它的 handleItemUseOn。
|
用手持物品名 O(1) 定位到唯一的功能服务,转发给它的 handleItemUseOn。
|
||||||
|
|
||||||
注意:取消"使用物品于方块"的字段是 ret(不是 cancel),
|
取消"使用物品于方块"的字段是 ret(不是 cancel),官方要求双端设置才能
|
||||||
官方要求双端设置才能真正取消默认行为。本分发器已在服务端设 ret=True,
|
真正取消默认行为。本分发器已在服务端设 ret=True。
|
||||||
服务端不会执行默认放置/使用逻辑;客户端如需完美取消动画,
|
|
||||||
可再增加一个监听 ClientItemUseOnEvent 的客户端配对系统。
|
|
||||||
"""
|
"""
|
||||||
itemName = args.get("itemName")
|
itemName = args.get("itemName")
|
||||||
service = self.itemUseOnOwner.get(itemName)
|
service = self.itemUseOnOwner.get(itemName)
|
||||||
@@ -196,7 +223,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
args['cancel'] = True
|
args['cancel'] = True
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 主入口4:计时方块 tick(持久化计时)
|
# 主入口4:计时方块 tick → 按 timerOwner 路由到计时服务(持久化计时)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def OnBlockEntityTick(self, args):
|
def OnBlockEntityTick(self, args):
|
||||||
@@ -207,8 +234,9 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
- 服务器重启/玩家重进后,tick 恢复,读回时间戳继续计时,
|
- 服务器重启/玩家重进后,tick 恢复,读回时间戳继续计时,
|
||||||
到点即转换(例如烤鸡烤了40秒退出,回来只剩20秒)
|
到点即转换(例如烤鸡烤了40秒退出,回来只剩20秒)
|
||||||
|
|
||||||
注意:该事件参数是 blockName/dimension/posX/posY/posZ(不是 posDim!)
|
路由:用事件自带的 blockName 在 timerOwner 里 O(1) 找到计时服务,
|
||||||
直接用事件自带的 blockName 路由,省掉每帧读方块,性能更好。
|
转发给它的 handleBlockEntityTick(省掉每帧读方块,性能更好)。
|
||||||
|
注意:事件参数是 blockName/dimension/posX/posY/posZ(不是 posDim!)
|
||||||
"""
|
"""
|
||||||
posX = args.get("posX")
|
posX = args.get("posX")
|
||||||
posY = args.get("posY")
|
posY = args.get("posY")
|
||||||
@@ -217,7 +245,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
|
|||||||
if posX is None or posY is None or posZ is None or dimension is None:
|
if posX is None or posY is None or posZ is None or dimension is None:
|
||||||
return
|
return
|
||||||
blockName = args.get("blockName", "")
|
blockName = args.get("blockName", "")
|
||||||
service = self.blockOwner.get(blockName) # 计时源方块一定注册在路由表里
|
service = self.timerOwner.get(blockName) # 计时源方块一定注册在 timerOwner
|
||||||
if not service:
|
if not service:
|
||||||
return
|
return
|
||||||
service.handleBlockEntityTick(args)
|
service.handleBlockEntityTick(args)
|
||||||
+124
-194
@@ -1,25 +1,39 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
================================================================================
|
================================================================================
|
||||||
烧烤引擎(服务端)
|
烧烤·右键转换引擎(服务端)
|
||||||
================================================================================
|
================================================================================
|
||||||
只处理"烧烤"(xy_barbecue) 分类下的方块转换:烧烤架、烤串、烤鸡、烤乳猪、烤鱼。
|
只处理"烧烤"(xy_barbecue) 分类下的【右键切换型】转换:
|
||||||
|
烧烤架点火/灭火、烤串翻烤2次、上杆(手持生食材右键支杆)、取下成品。
|
||||||
|
|
||||||
【与 BlockSwitchService / CookingService 的区别】
|
【与计时转换解耦】
|
||||||
本系统(方案A)采用"统一分发器"架构:
|
同分类的"计时自动转换"(烤鸡/烤乳猪/烤鱼 上杆后下方篝火烤熟)已拆到独立类:
|
||||||
- FeatureDispatcher 是唯一监听 ServerBlockUseEvent 的系统
|
BarbecueTimerService —— 处理 ServerBlockEntityTickEvent 转发的计时逻辑
|
||||||
- 本类通过 FeatureDispatcher.register(self) 把管辖方块注册进去
|
本类只负责右键事件,两者通过 FeatureDispatcher 协作:
|
||||||
- 玩家右键方块时,FeatureDispatcher 用 O(1) dict 直接找到本实例
|
- 本类右键碰到"计时源方块"(如烤鸡(生))→ 调 FeatureDispatcher.startTimerFor
|
||||||
- 本类只查自己的 BarbecueData(BARBECUE_SWITCHES),不碰其他功能数据库
|
转发给 BarbecueTimerService 启动烤制(篝火检查也在计时服务里做)
|
||||||
|
- 计时到点转换完全由 BarbecueTimerService 负责,本类不碰任何计时数据
|
||||||
|
|
||||||
【本类额外处理的规则】
|
【事件监听在哪?】
|
||||||
- 手持"原版生鸡肉"右键方块表面(ServerItemUseOnEvent) → 生成"生鸡肉(放置后)"方块
|
本类【不】监听任何事件!
|
||||||
该规则通过 FeatureDispatcher 的 itemUseOn 路由转发到本类 handleItemUseOn
|
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
|
||||||
|
- ServerBlockUseEvent → OnBlockUse → 按 blockOwner 路由到本类 handleBlockUse
|
||||||
|
- ServerItemUseOnEvent → OnItemUseOn → 按 itemUseOnOwner 路由到本类 handleItemUseOn
|
||||||
|
- ServerBlockEntityTickEvent → OnBlockEntityTick → 按 timerOwner 路由到 BarbecueTimerService
|
||||||
|
本类只实现被分发器调用的接口方法,不关心事件来源。
|
||||||
|
|
||||||
【转换类型】
|
【本类怎么做(自上而下的方法系列)】
|
||||||
- type="switch" : 右键直接转换(点火/灭火/烤串2次/上杆/取下)
|
handleBlockUse ← 入口1:分发器转发右键方块事件
|
||||||
- type="timer" : 计时自动转换(上杆后下方篝火烤熟),
|
handleItemUseOn ← 入口2:分发器转发"手持物品右键方块表面"事件(放置生鸡肉)
|
||||||
由本类用 AddTimer 实现,启动前检查下方是否为篝火
|
├─ findRecipe ← 匹配 (方块名, 物品名) 规则
|
||||||
|
├─ checkCooldown ← 连点冷却(防刷提示)
|
||||||
|
├─ forwardStartTimer ← 计时源方块 → 转发给计时服务启动烤制
|
||||||
|
├─ checkBelow ← 切换型规则的下方方块检查(烤串需点燃的烧烤炉)
|
||||||
|
└─ handleSwitch ← 切换型规则具体执行(点火/烤串/上杆/取下)
|
||||||
|
├─ consumeItem ← 消耗物品
|
||||||
|
├─ replaceBlock ← 替换方块
|
||||||
|
├─ giveItem ← 直接给物品(塞背包)
|
||||||
|
└─ dropItems ← 掉落物形式给物品(取下整只成品)
|
||||||
================================================================================
|
================================================================================
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
@@ -33,9 +47,6 @@ comp = serverApi.GetEngineCompFactory() # 组件工厂
|
|||||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||||
|
|
||||||
# ---- 方块实体数据组件(计时持久化:把开始时间戳+时长写进方块,重启后继续计时) ----
|
|
||||||
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
|
|
||||||
|
|
||||||
# ---- 只导入烧烤自己的数据库 ----
|
# ---- 只导入烧烤自己的数据库 ----
|
||||||
from ..BarbecueData import BARBECUE_SWITCHES, ITEM_USE_ON_RULES
|
from ..BarbecueData import BARBECUE_SWITCHES, ITEM_USE_ON_RULES
|
||||||
|
|
||||||
@@ -55,31 +66,30 @@ FACE_OFFSET = {
|
|||||||
|
|
||||||
class BarbecueService(BlueFoxServerSystem):
|
class BarbecueService(BlueFoxServerSystem):
|
||||||
"""
|
"""
|
||||||
烧烤引擎
|
烧烤·右键转换引擎
|
||||||
|
|
||||||
【核心数据结构】
|
【核心数据结构】
|
||||||
barbecueRecipes = {
|
barbecueRecipes = {(方块名, 物品名): 规则dict} # O(1) 查找,含 switch+timer 规则
|
||||||
(方块名, 物品名): 规则dict, # O(1) 查找
|
barbecueBlockSet = {方块名, ...} # O(1) 拦截/注册用(所有源方块)
|
||||||
}
|
itemUseOnRecipes = {物品名: 规则dict} # 手持物品右键方块表面 → 生成规则
|
||||||
barbecueBlockSet = {方块名, ...} # O(1) 拦截/注册用
|
cooldown = {key: 时间戳} # 连点冷却表
|
||||||
barbecueTimers = {方块名: 规则dict} # 计时型起始方块 → 规则
|
|
||||||
itemUseOnRecipes = {物品名: 规则dict} # 手持物品右键方块表面 → 生成规则
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, namespace, systemName):
|
def __init__(self, namespace, systemName):
|
||||||
|
"""
|
||||||
|
初始化:构建规则字典,并注册到统一分发器(让分发器把右键事件转发过来)。
|
||||||
|
"""
|
||||||
super(BarbecueService, self).__init__(namespace, systemName)
|
super(BarbecueService, self).__init__(namespace, systemName)
|
||||||
print "===== BarbecueService init ====="
|
print "===== BarbecueService init ====="
|
||||||
|
|
||||||
# ---- 构建烧烤规则字典(O(1) 查找) ----
|
# ---- 构建烧烤规则字典(O(1) 查找) ----
|
||||||
|
# switch 和 timer 规则都收进来:右键"计时源方块"时才能识别并转发给计时服务
|
||||||
self.barbecueRecipes = {}
|
self.barbecueRecipes = {}
|
||||||
self.barbecueBlockSet = set()
|
self.barbecueBlockSet = set()
|
||||||
self.barbecueTimers = {}
|
|
||||||
for sw in BARBECUE_SWITCHES:
|
for sw in BARBECUE_SWITCHES:
|
||||||
key = (sw["source"], sw.get("item"))
|
key = (sw["source"], sw.get("item"))
|
||||||
self.barbecueRecipes[key] = sw
|
self.barbecueRecipes[key] = sw
|
||||||
self.barbecueBlockSet.add(sw["source"])
|
self.barbecueBlockSet.add(sw["source"])
|
||||||
if sw.get("type") == "timer":
|
|
||||||
self.barbecueTimers[sw["source"]] = sw
|
|
||||||
|
|
||||||
# ---- 手持物品右键方块表面规则:物品名 → 规则 ----
|
# ---- 手持物品右键方块表面规则:物品名 → 规则 ----
|
||||||
self.itemUseOnRecipes = {}
|
self.itemUseOnRecipes = {}
|
||||||
@@ -93,7 +103,7 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
FeatureDispatcher.register(self)
|
FeatureDispatcher.register(self)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 给 FeatureDispatcher 的接口
|
# 一、给 FeatureDispatcher 的接口
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def getFeatureBlocks(self):
|
def getFeatureBlocks(self):
|
||||||
@@ -107,8 +117,10 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
def handleBlockUse(self, args):
|
def handleBlockUse(self, args):
|
||||||
"""
|
"""
|
||||||
由 FeatureDispatcher 转发的右键方块入口。
|
由 FeatureDispatcher 转发的右键方块入口。
|
||||||
只有 blockName 在本功能方块集合里才会被调用。
|
只有 blockName 在本功能的方块集合里才会被调用。
|
||||||
返回 True=已处理,False=当前组合无匹配规则。
|
|
||||||
|
返回 True = 本功能处理了这个右键(分发器会取消默认行为)
|
||||||
|
返回 False = 当前组合没有匹配规则(分发器不拦截,交还默认行为)
|
||||||
"""
|
"""
|
||||||
blockName = args.get("blockName")
|
blockName = args.get("blockName")
|
||||||
playerId = args.get("playerId")
|
playerId = args.get("playerId")
|
||||||
@@ -127,23 +139,18 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
|
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# ---- 下方方块检查(timer 需篝火,烤串需点燃的烧烤炉) ----
|
# 计时型规则:右键"计时源方块"(如烤鸡(生))→ 转发给计时服务启动烤制。
|
||||||
if not self.checkBelow(recipe, dim, pos):
|
# 下方篝火检查在 BarbecueTimerService.startTimer 里做(失败会提示玩家)
|
||||||
# 下方不是要求的方块 → 失败提示,不执行(但也算处理,取消默认行为)
|
|
||||||
if recipe.get("type") == "timer":
|
|
||||||
tip = "§c支杆下方需要放一个篝火(营火)才能烤制!"
|
|
||||||
else:
|
|
||||||
tip = "§c下方不是点燃的烧烤炉,无法翻烤!"
|
|
||||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, tip)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 计时型:启动计时
|
|
||||||
if recipe.get("type") == "timer":
|
if recipe.get("type") == "timer":
|
||||||
self.startTimer(key, recipe, dim, playerId)
|
return self.forwardStartTimer(blockName, dim, pos, playerId)
|
||||||
return True
|
|
||||||
|
|
||||||
# 切换型
|
# 切换型规则:先检查下方方块(烤串要求下方是点燃的烧烤炉)
|
||||||
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName)
|
if not self.checkBelow(recipe, dim, pos):
|
||||||
|
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§c下方不是点燃的烧烤炉,无法翻烤!")
|
||||||
|
return True # 已拦截并提示,取消默认行为
|
||||||
|
|
||||||
|
# 切换型规则:就地执行
|
||||||
|
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def handleItemUseOn(self, args):
|
def handleItemUseOn(self, args):
|
||||||
@@ -188,53 +195,75 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 规则匹配(O(1))
|
# 二、规则匹配(O(1) 字典查找)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def findRecipe(self, blockName, itemDict):
|
def findRecipe(self, blockName, itemDict):
|
||||||
|
"""
|
||||||
|
在烧烤字典中查找匹配规则:
|
||||||
|
1. 先精确匹配 (方块名, 物品名)
|
||||||
|
2. 没找到再匹配 (方块名, None) —— 空手也能触发的规则(烤串/取下等)
|
||||||
|
"""
|
||||||
itemName = itemDict.get("newItemName") if itemDict else None
|
itemName = itemDict.get("newItemName") if itemDict else None
|
||||||
recipe = self.barbecueRecipes.get((blockName, itemName))
|
recipe = self.barbecueRecipes.get((blockName, itemName))
|
||||||
if recipe:
|
if recipe:
|
||||||
return recipe
|
return recipe
|
||||||
recipe = self.barbecueRecipes.get((blockName, None))
|
return self.barbecueRecipes.get((blockName, None))
|
||||||
if recipe:
|
|
||||||
return recipe
|
|
||||||
return None
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 切换型规则处理
|
# 三、计时转发(把"计时源方块"的烤制启动任务交给 BarbecueTimerService)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName):
|
def forwardStartTimer(self, blockName, dim, pos, playerId):
|
||||||
"""
|
"""
|
||||||
正向(item 非 None):点火/上杆 → 消耗 → 替换 → 给奖励 → 续计时
|
右键碰到"计时源方块"(如烤鸡(生))时,把启动烤制转交给计时服务。
|
||||||
逆向(item 为 None):烤串2次/取下 → 掉落 → 替换
|
通过 FeatureDispatcher.startTimerFor 按方块名 O(1) 路由到 BarbecueTimerService。
|
||||||
"""
|
"""
|
||||||
# ---- 正向 ----
|
return FeatureDispatcher.startTimerFor(blockName, dim, pos, playerId)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 四、切换型规则处理
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key):
|
||||||
|
"""
|
||||||
|
处理"右键直接转换"类规则。
|
||||||
|
|
||||||
|
正向(item 不为 None):点火/上杆 → 消耗 → 替换 → 给奖励
|
||||||
|
逆向(item 为 None) :烤串2次/取下 → 掉落 → 替换
|
||||||
|
|
||||||
|
注意:上杆/点火后【不自动启动烤制】。
|
||||||
|
烧烤计时规则 autoStart=False,必须玩家右键"计时源方块"才激活烤制
|
||||||
|
(见 handleBlockUse → forwardStartTimer)。
|
||||||
|
"""
|
||||||
|
# ---- 正向操作 ----
|
||||||
if recipe.get("item") is not None:
|
if recipe.get("item") is not None:
|
||||||
|
# 消耗物品
|
||||||
if recipe.get("consume", 1) > 0:
|
if recipe.get("consume", 1) > 0:
|
||||||
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
|
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
|
||||||
|
|
||||||
|
# 替换方块
|
||||||
if recipe.get("target"):
|
if recipe.get("target"):
|
||||||
self.replaceBlock(pos, dim, recipe["target"], playerId)
|
self.replaceBlock(pos, dim, recipe["target"], playerId)
|
||||||
|
|
||||||
|
# 给玩家物品
|
||||||
if recipe.get("giveItem"):
|
if recipe.get("giveItem"):
|
||||||
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
|
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
|
||||||
|
|
||||||
# 注意:上杆/点火后【不自动启动烤制】。
|
|
||||||
# 烤制规则 autoStart=False,必须玩家右键烤制方块才激活计时(见 handleBlockUse)
|
|
||||||
# 转换成功提示(点火/上杆等)
|
# 转换成功提示(点火/上杆等)
|
||||||
if recipe.get("tip"):
|
if recipe.get("tip"):
|
||||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# ---- 逆向 ----
|
# ---- 逆向操作 ----
|
||||||
|
# 掉落物形式生成(取下整只成品等),优先于 giveItem
|
||||||
if recipe.get("dropItem"):
|
if recipe.get("dropItem"):
|
||||||
self.dropItems(dim, pos, recipe["dropItem"])
|
self.dropItems(dim, pos, recipe["dropItem"])
|
||||||
elif recipe.get("giveItem"):
|
elif recipe.get("giveItem"):
|
||||||
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
|
self.giveItem(playerId, recipe["giveItem"], recipe.get("giveCount", 1))
|
||||||
|
|
||||||
|
# 替换方块(如烤熟成品取下后还原为支杆)
|
||||||
if recipe.get("target"):
|
if recipe.get("target"):
|
||||||
self.replaceBlock(pos, dim, recipe["target"], playerId)
|
self.replaceBlock(pos, dim, recipe["target"], playerId)
|
||||||
|
|
||||||
@@ -243,13 +272,14 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# 计时型规则处理(方块实体 tick 持久化计时,启动前检查下方篝火)
|
# 五、下方方块检查(切换型规则专用,如烤串要求下方是点燃的烧烤炉)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def checkBelow(self, recipe, dim, pos):
|
def checkBelow(self, recipe, dim, pos):
|
||||||
"""
|
"""
|
||||||
检查方块下方是否为 recipe["below"] 中指定的方块。
|
检查方块下方是否为 recipe["below"] 中指定的方块。
|
||||||
没写 below 字段 → 视为满足。
|
没写 below 字段 → 视为满足。
|
||||||
|
(计时型烤制规则的篝火检查在 BarbecueTimerService 里做)
|
||||||
"""
|
"""
|
||||||
below = recipe.get("below")
|
below = recipe.get("below")
|
||||||
if not below:
|
if not below:
|
||||||
@@ -261,139 +291,8 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def startTimer(self, key, recipe, dim, playerId=None):
|
|
||||||
"""
|
|
||||||
启动烤制计时:把 计时标识+开始时间戳+时长 写入方块实体数据。
|
|
||||||
- 已处于本规则计时中的方块不会重复重置时间
|
|
||||||
- 到点判断由 FeatureDispatcher 转发的 ServerBlockEntityTickEvent 完成
|
|
||||||
- 时间戳存在方块上 → 服务器重启/玩家重进后继续计时
|
|
||||||
(例如烤了40秒退出,回来只剩20秒)
|
|
||||||
"""
|
|
||||||
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.barbecueTimers.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
|
|
||||||
if not self.checkBelow(recipe, dim, pos):
|
|
||||||
return # 篝火被拿走了,暂停烤制
|
|
||||||
self.replaceBlock(pos, dim, recipe["target"], None)
|
|
||||||
nextRecipe = self.barbecueTimers.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):
|
def getCarriedItem(self, playerId):
|
||||||
@@ -401,6 +300,10 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
return itemComp.GetPlayerItem(
|
return itemComp.GetPlayerItem(
|
||||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 七、工具方法:消耗物品
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
def consumeItem(self, playerId, itemDict, amount=1):
|
def consumeItem(self, playerId, itemDict, amount=1):
|
||||||
if itemDict is None:
|
if itemDict is None:
|
||||||
return
|
return
|
||||||
@@ -417,7 +320,16 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
"count": itemDict["count"],
|
"count": itemDict["count"],
|
||||||
"auxValue": itemDict.get("newAuxValue", 0)})
|
"auxValue": itemDict.get("newAuxValue", 0)})
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 八、工具方法:替换方块
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
|
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
|
||||||
|
"""
|
||||||
|
替换指定位置的方块。
|
||||||
|
newBlockName="minecraft:air" 表示清除该方块。
|
||||||
|
playerId 可为 None(计时回调时没有玩家),用 LevelId 创建组件。
|
||||||
|
"""
|
||||||
creator = playerId if playerId is not None else LevelId
|
creator = playerId if playerId is not None else LevelId
|
||||||
if aux is None:
|
if aux is None:
|
||||||
try:
|
try:
|
||||||
@@ -431,12 +343,23 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
oldBlockHandling=0,
|
oldBlockHandling=0,
|
||||||
dimensionId=dim)
|
dimensionId=dim)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 九、工具方法:给玩家物品(塞背包)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
def giveItem(self, playerId, itemName, count=1, auxValue=0):
|
def giveItem(self, playerId, itemName, count=1, auxValue=0):
|
||||||
comp.CreateItem(playerId).SpawnItemToPlayerInv(
|
comp.CreateItem(playerId).SpawnItemToPlayerInv(
|
||||||
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
|
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 十、工具方法:以掉落物形式生成物品(取下烤鸡/烤乳猪等)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
def dropItems(self, dim, pos, dropList):
|
def dropItems(self, dim, pos, dropList):
|
||||||
"""以掉落物形式生成物品(取下烤鸡/烤乳猪等)"""
|
"""
|
||||||
|
dropList 格式:[{"name": "xxx", "count": N}, ...]
|
||||||
|
在方块上方生成掉落物实体,让它自然掉到地上。
|
||||||
|
"""
|
||||||
for d in dropList:
|
for d in dropList:
|
||||||
itemDict = {
|
itemDict = {
|
||||||
"itemName": d["name"],
|
"itemName": d["name"],
|
||||||
@@ -445,8 +368,15 @@ class BarbecueService(BlueFoxServerSystem):
|
|||||||
}
|
}
|
||||||
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# 十一、工具方法:冷却检查
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
def checkCooldown(self, key, cd=0.5):
|
def checkCooldown(self, key, cd=0.5):
|
||||||
"""右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品"""
|
"""
|
||||||
|
右键冷却:同一方块 cd 秒内只响应一次,防止连点刷物品/刷提示。
|
||||||
|
烤串"右键2次"用 switchCd 调小冷却以便快速连点。
|
||||||
|
"""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
last = self.cooldown.get(key, 0)
|
last = self.cooldown.get(key, 0)
|
||||||
if now - last < cd:
|
if now - last < cd:
|
||||||
|
|||||||
Reference in new issue
Block a user