油锅 2
This commit is contained in:
1 parent
69503e18a7
commit
c58f5fea40
18 files changed
+697
-13
No files matched your search
+229
@@ -0,0 +1,229 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
油锅·右键转换引擎(服务端)
|
||||
================================================================================
|
||||
处理"油锅"(xy_youguo) 分类下的右键转换:
|
||||
倒油(油罐->玻璃罐) / 取油(玻璃罐->油罐) / 放料(满油锅+裹粉->炸物生) /
|
||||
出锅(熟炸物+盘子->掉落盘装成品)
|
||||
|
||||
【事件监听在哪?】
|
||||
本类【不】监听任何事件!
|
||||
所有事件由 FeatureDispatcher 统一监听, 按 blockOwner 路由到本类 handleBlockUse。
|
||||
|
||||
【本类怎么做】
|
||||
handleBlockUse <- 入口: 分发器转发右键方块事件
|
||||
└─ handleSwitch <- 通用转换(倒油/取油/放料/出锅)
|
||||
|
||||
【计时在?】
|
||||
放料后的炸制计时在 YouguoTimerService, 本类只负责"开始计时"(startTimer 字段调
|
||||
FeatureDispatcher.startTimerFor 转发, 记录玩家以便完成/暂停提示送达)。
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
# ---- 网易API基础对象 ----
|
||||
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
|
||||
comp = serverApi.GetEngineCompFactory() # 组件工厂
|
||||
|
||||
# ---- 继承 BlueFox 框架的系统基类(提供 CreateEngineItemEntity 等方法) ----
|
||||
from ....BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||
from ....BlueFoxTools.ToolsConfig import LevelId
|
||||
|
||||
# ---- 只导入油锅自己的数据库 ----
|
||||
from ..YouguoData import SWITCH_RULES
|
||||
|
||||
# ---- 统一分发器 ----
|
||||
from ...service.FeatureDispatcher import FeatureDispatcher
|
||||
|
||||
|
||||
class YouguoService(BlueFoxServerSystem):
|
||||
"""
|
||||
油锅·右键转换引擎
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
super(YouguoService, self).__init__(namespace, systemName)
|
||||
print "===== YouguoService init ====="
|
||||
|
||||
# ---- (方块, 手持物品) -> 规则: O(1) 查找 ----
|
||||
self.recipes = {}
|
||||
self.featureBlocks = set()
|
||||
for r in SWITCH_RULES:
|
||||
self.recipes[(r["block"], r.get("item"))] = r
|
||||
self.featureBlocks.add(r["block"])
|
||||
|
||||
# ---- 右键冷却表 ----
|
||||
self.cooldown = {}
|
||||
|
||||
# ---- 注册到统一分发器 ----
|
||||
FeatureDispatcher.register(self)
|
||||
|
||||
# ============================================================================
|
||||
# 一、给 FeatureDispatcher 的接口
|
||||
# ============================================================================
|
||||
|
||||
def getFeatureBlocks(self):
|
||||
"""返回本功能管辖的所有右键方块名集合"""
|
||||
return self.featureBlocks
|
||||
|
||||
def getItemUseOnRules(self):
|
||||
"""手持物品右键方块表面不需要单独路由(放料/出锅都是右键方块本身), 返回空"""
|
||||
return {}
|
||||
|
||||
def handleBlockUse(self, args):
|
||||
"""由 FeatureDispatcher 转发的右键方块入口"""
|
||||
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.recipes.get((blockName, itemDict.get("newItemName") if itemDict else None))
|
||||
if not recipe:
|
||||
return False
|
||||
if not self.checkCooldown(key, 0.4):
|
||||
return True
|
||||
self.handleSwitch(recipe, playerId, pos, dim, itemDict)
|
||||
return True
|
||||
|
||||
# ============================================================================
|
||||
# 二、通用转换执行
|
||||
# ============================================================================
|
||||
|
||||
def handleSwitch(self, recipe, playerId, pos, dim, itemDict):
|
||||
# 需要下方方块(放料: 油锅下须是点燃的炉灶才计时炸制): 不满足则提示并拦截
|
||||
needBelow = recipe.get("needBelow")
|
||||
if needBelow and not self._belowIs(dim, pos, needBelow):
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(
|
||||
playerId, recipe.get("belowTip", "§c下方方块不符合"))
|
||||
return
|
||||
|
||||
# 消耗物品(数量不足则拦截, 不转换)
|
||||
consume = recipe.get("consume", 0)
|
||||
if consume > 1 and itemDict is not None and (itemDict.get("count") or 0) < consume:
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(
|
||||
playerId, "§c数量不足, 需要 %d 个" % consume)
|
||||
return
|
||||
if consume > 0:
|
||||
self.consumeItem(playerId, itemDict, consume)
|
||||
|
||||
# 出锅: 熟炸物消失 + 掉落盘装成品(掉落物形式)
|
||||
action = recipe.get("action")
|
||||
if action == "take_out":
|
||||
self.clearBlock(pos, dim)
|
||||
self.dropItem(dim, pos, recipe["dropItem"])
|
||||
# 替换方块(倒油/取油/放料)
|
||||
if recipe.get("target"):
|
||||
self.replaceBlock(pos, dim, recipe["target"], playerId)
|
||||
|
||||
# 放料后启动计时(记录发起玩家, 炸好/暂停提示能送达)
|
||||
if recipe.get("startTimer"):
|
||||
FeatureDispatcher.startTimerFor(recipe["startTimer"], dim, pos, playerId)
|
||||
|
||||
# 给背包物品(倒油给玻璃罐 / 取油给油罐)
|
||||
if recipe.get("give"):
|
||||
self.giveItem(playerId, recipe["give"])
|
||||
|
||||
# 提示
|
||||
if recipe.get("tip"):
|
||||
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
|
||||
|
||||
# ============================================================================
|
||||
# 三、工具方法:判断正下方是否指定方块(点燃炉灶)
|
||||
# ============================================================================
|
||||
|
||||
def _belowIs(self, dim, pos, blockName):
|
||||
try:
|
||||
b = comp.CreateBlockInfo(LevelId).GetBlockNew((pos[0], pos[1] - 1, pos[2]), dim)
|
||||
return bool(b) and b.get("name") == blockName
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# ============================================================================
|
||||
# 四、工具方法:获取玩家主手物品
|
||||
# ============================================================================
|
||||
|
||||
def getCarriedItem(self, playerId):
|
||||
itemComp = comp.CreateItem(playerId)
|
||||
return itemComp.GetPlayerItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0, True)
|
||||
|
||||
# ============================================================================
|
||||
# 五、工具方法:给玩家物品
|
||||
# ============================================================================
|
||||
|
||||
def giveItem(self, playerId, itemName, count=1, aux=0):
|
||||
try:
|
||||
itemDict = {"itemName": itemName, "count": count, "auxValue": aux}
|
||||
comp.CreateItem(playerId).SpawnItemToPlayerInv(itemDict, playerId)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 六、工具方法:消耗物品
|
||||
# ============================================================================
|
||||
|
||||
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),
|
||||
"userData": itemDict.get("userData", {})})
|
||||
|
||||
# ============================================================================
|
||||
# 七、工具方法:以掉落物形式生成物品
|
||||
# ============================================================================
|
||||
|
||||
def dropItem(self, dim, pos, itemName, count=1, aux=0):
|
||||
try:
|
||||
itemDict = {"itemName": itemName, "count": count, "auxValue": aux}
|
||||
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 八、工具方法:替换方块 / 清空方块(清空时 aux 强制 0, 兼容定向变体)
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId=None):
|
||||
creator = playerId if playerId is not None else LevelId
|
||||
aux = 0
|
||||
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 clearBlock(self, pos, dim):
|
||||
comp.CreateBlockInfo(LevelId).SetBlockNew(
|
||||
pos, blockDict={"name": "minecraft:air", "aux": 0},
|
||||
oldBlockHandling=0, dimensionId=dim)
|
||||
|
||||
# ============================================================================
|
||||
# 九、工具方法:冷却检查
|
||||
# ============================================================================
|
||||
|
||||
def checkCooldown(self, key, cd=0.4):
|
||||
now = time.time()
|
||||
last = self.cooldown.get(key, 0)
|
||||
if now - last < cd:
|
||||
return False
|
||||
self.cooldown[key] = now
|
||||
return True
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
油锅·计时炸制引擎(服务端)
|
||||
================================================================================
|
||||
只处理"油锅"(xy_youguo) 分类下的【计时自动转换】:
|
||||
炸物(生) -> 60秒 -> 炸物(半熟) -> 30秒 -> 炸物(熟)
|
||||
天妇罗/炸饺子: 生 -> 60秒 -> 熟
|
||||
|
||||
【计时判定(用户指定, 与蒸笼一致)】
|
||||
- 炸物方块【正下方】必须是 点燃的炉灶(stove_2) 才开始计时
|
||||
- 计时数据仍在【炸物方块本身】上(炸物方块 tick 时判定下方炉灶)
|
||||
- 炉灶被移走/熄灭 -> 暂停计时(数据保留, 炉灶回来继续) + 提示发起玩家
|
||||
|
||||
【事件监听在哪?】
|
||||
本类【不】监听任何事件!
|
||||
ServerBlockEntityTickEvent 由 FeatureDispatcher 统一监听, 再按 timerOwner
|
||||
路由到本类的 handleBlockEntityTick。计时用绝对时间戳写方块实体数据,
|
||||
退出重进继续计时。
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
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)
|
||||
|
||||
# ---- 只导入油锅自己的数据库 ----
|
||||
from ..YouguoData import TIMER_RULES
|
||||
|
||||
# ---- 统一分发器 ----
|
||||
from ...service.FeatureDispatcher import FeatureDispatcher
|
||||
|
||||
|
||||
class YouguoTimerService(BlueFoxServerSystem):
|
||||
"""
|
||||
油锅·计时炸制引擎
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
super(YouguoTimerService, self).__init__(namespace, systemName)
|
||||
print "===== YouguoTimerService init ====="
|
||||
|
||||
# ---- 计时规则: source -> [规则...] ----
|
||||
self.timerMap = {}
|
||||
for t in TIMER_RULES:
|
||||
self.timerMap.setdefault(t["source"], []).append(t)
|
||||
|
||||
# ---- 注册到分发器的计时路由表 ----
|
||||
FeatureDispatcher.registerTimer(self)
|
||||
|
||||
# ============================================================================
|
||||
# 一、给 FeatureDispatcher 的接口
|
||||
# ============================================================================
|
||||
|
||||
def getTimerBlocks(self):
|
||||
"""返回本功能的所有"计时源方块"集合(tick 路由用)"""
|
||||
return set(self.timerMap.keys())
|
||||
|
||||
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)
|
||||
|
||||
# ============================================================================
|
||||
# 二、启动计时(右键放料后由 YouguoService 通过 startTimerFor 转发)
|
||||
# ============================================================================
|
||||
|
||||
def startTimer(self, dim, pos, blockName, playerId=None):
|
||||
rules = self.timerMap.get(blockName)
|
||||
if not rules:
|
||||
return False
|
||||
rule = self._pickRule(rules, dim, pos)
|
||||
if not rule:
|
||||
return False
|
||||
try:
|
||||
data = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if data and data["rtdd_tid"] == blockName:
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
self._writeTimerData(dim, pos, rule, playerId)
|
||||
return True
|
||||
|
||||
# ============================================================================
|
||||
# 三、tick 检查与到点转换
|
||||
# ============================================================================
|
||||
|
||||
def _pickRule(self, rules, dim, pos):
|
||||
"""按"下方方块"匹配规则(点燃炉灶), 返回命中的规则; 无命中返回 None"""
|
||||
belowPos = (pos[0], pos[1] - 1, pos[2])
|
||||
try:
|
||||
b = comp.CreateBlockInfo(LevelId).GetBlockNew(belowPos, dim)
|
||||
belowName = b.get("name") if b else ""
|
||||
except Exception:
|
||||
belowName = ""
|
||||
for rule in rules:
|
||||
if belowName in rule.get("below", [belowName]):
|
||||
return rule
|
||||
return None
|
||||
|
||||
def _tickProcess(self, dim, pos, blockName):
|
||||
try:
|
||||
rules = self.timerMap.get(blockName)
|
||||
if not rules:
|
||||
return
|
||||
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
if blockData is None:
|
||||
return
|
||||
|
||||
try:
|
||||
tid = blockData["rtdd_tid"]
|
||||
except Exception:
|
||||
tid = ""
|
||||
if tid != blockName:
|
||||
# autoStart: 计时源方块无计时数据且下方点燃炉灶时自动启动
|
||||
if any(r.get("autoStart", True) for r in rules):
|
||||
rule = self._pickRule(rules, dim, pos)
|
||||
if rule:
|
||||
self._writeTimerData(dim, pos, rule, None)
|
||||
return
|
||||
|
||||
# 计时中: 下方点燃炉灶被移走/熄灭 -> 暂停 + 提示
|
||||
rule = self._pickRule(rules, dim, pos)
|
||||
if rule is None:
|
||||
self._handleBelowBroken(dim, pos, blockName, blockData, rules)
|
||||
return
|
||||
|
||||
tstart = blockData["rtdd_tstart"]
|
||||
tdur = blockData["rtdd_tdur"]
|
||||
|
||||
if time.time() - tstart >= tdur:
|
||||
self._finishTimer(dim, pos, rule, blockData)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _handleBelowBroken(self, dim, pos, blockName, data, rules):
|
||||
"""下方点燃炉灶被移走/熄灭: 暂停计时(数据保留, 炉灶回来继续) + 提示发起玩家"""
|
||||
self._notifyIfOnline(data.get("rtdd_tp"), "§c油锅下的炉灶移走了, 暂停炸制")
|
||||
|
||||
def _finishTimer(self, dim, pos, rule, data):
|
||||
try:
|
||||
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
|
||||
if cur and cur.get("name") != rule["source"]:
|
||||
self._clearTimerData(dim, pos)
|
||||
return
|
||||
# 到点转换
|
||||
self.replaceBlock(pos, dim, rule["target"], None)
|
||||
self._clearTimerData(dim, pos)
|
||||
tip = rule.get("doneTip", "")
|
||||
if tip:
|
||||
self._notifyIfOnline(data.get("rtdd_tp"), tip)
|
||||
if not data.get("rtdd_tp"):
|
||||
self._notifyNearby(dim, pos, tip)
|
||||
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 _notifyNearby(self, dim, pos, tip, radius=6):
|
||||
"""完成提示兜底: 发起玩家不在/未知时, 提示该方块附近(三维距离)的在线玩家"""
|
||||
if not tip:
|
||||
return
|
||||
try:
|
||||
r2 = radius * radius
|
||||
for pid in comp.CreateGame(LevelId).GetAllPlayerIds():
|
||||
try:
|
||||
if comp.CreateDimension(pid).GetEntityDimensionId() != dim:
|
||||
continue
|
||||
fp = comp.CreatePos(pid).GetFootPos()
|
||||
if fp is None:
|
||||
continue
|
||||
dx = fp[0] - pos[0]
|
||||
dy = fp[1] - pos[1]
|
||||
dz = fp[2] - pos[2]
|
||||
if dx * dx + dy * dy + dz * dz <= r2:
|
||||
comp.CreateMsg(pid).NotifyOneMessage(pid, tip)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 六、工具方法:替换方块
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId=None):
|
||||
creator = playerId if playerId is not None else LevelId
|
||||
aux = 0
|
||||
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)
|
||||
+1
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
Reference in new issue
Block a user