烧烤 动物养殖 分类 功能实现1
This commit is contained in:
1 parent
98335837dd
commit
eac9409571
16 files changed
+1493
-5
No files matched your search
+341
@@ -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
|
||||
Whitespace-only changes.
Reference in new issue
Block a user