This commit is contained in:
wenhe committed 2026-09-23 14:27:13 +08:00
1 parent 9eb9edbb8a
commit b768ff1555
10 files changed
+618 -15

No files matched your search

@@ -108,6 +108,12 @@ class ChaoguoService(BlueFoxServerSystem):
"""手持物品右键方块表面路由: 盖饭成品菜 + 米饭三形态(盖饭/蛋炒饭)"""
return {r["item"]: True for r in ITEM_USE_ON_RULES}
def ownsItemUseOn(self, itemName, clickedName):
"""声明该(手持物品,被点方块)组合由本服务 itemUseOn 规则处理(盖饭/蛋炒饭)。
被点方块(米饭/成品菜)虽也被"总食用"登记为右键方块(吃), 但盖饭转换应仍走
itemUseOn, 故 dispatcher 据此对盖饭组合放行(不因被点方块归属别的服务而跳过)。"""
return (itemName, clickedName) in self.itemUseOnRecipes
def handleBlockUse(self, args):
"""由 FeatureDispatcher 转发的右键方块入口"""
blockName = args.get("blockName")
@@ -204,6 +210,33 @@ class ChaoguoService(BlueFoxServerSystem):
# 二、通用转换执行
# ============================================================================
def handleTakeoutRequest(self, playerId, blockName, itemName, dim, pos):
"""客户端显式触发的出锅请求(消闪方案, 同汤锅).
客户端已把 (盘子, 炒锅成品) 的放置预览拦掉(见 ClientFeatureDispatcher),
本方法在服务端补齐出锅逻辑: 校验落点仍是该成品 + 精确定位 take_out 规则
-> 复用 handleSwitch(含 needBelow/needFlag 门控), 交给它执行出锅"""
# 守卫1: 防空手
if not itemName or itemName in ("minecraft:air",):
return
try:
b = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
cur = b.get("name") if b else None
except Exception:
cur = None
# 守卫2: 落点已不是该成品 -> 跳过, 防双份
if cur != blockName:
print "[TakeoutRequest-SKIP-ALREADY]", "want:", blockName, "cur:", cur, "pos:", pos
return
recipe = self.recipes.get((blockName, itemName))
if not recipe or recipe.get("action") != "take_out":
return
# 守卫3: 冷却, 防连点双发
if not self.checkCooldown((dim, pos[0], pos[1], pos[2]), 0.4):
return
itemDict = self.getCarriedItem(playerId)
self.handleSwitch(recipe, playerId, pos, dim, itemDict)
print "[TakeoutRequest-DONE]", "player:", playerId, "item:", itemName, "block:", blockName
def handleSwitch(self, recipe, playerId, pos, dim, itemDict):
# 需要下方方块(翻炒: 炒锅下须是点燃的炉灶): 不满足则提示并拦截
needBelow = recipe.get("needBelow")
@@ -19,6 +19,7 @@
共享数据 InterceptData.py (客户端/服务端同一份, 见其注释)
================================================================================
"""
import time
import mod.client.extraClientApi as clientApi
ClientSystem = clientApi.GetClientSystemCls()
@@ -26,7 +27,23 @@ clientComp = clientApi.GetEngineCompFactory()
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxClientSystem
from .InterceptData import (ITEM_USE_ON_CANCEL, ITEM_USE_ON_CANCEL_BY_ITEM,
SUSHI_BLOCKS, COATING_ITEMS)
SUSHI_BLOCKS, SUSHI_CANCEL, COATING_ITEMS,
TAKEOUT_CANCEL, GAI_FAN_CANCEL)
# 自定义事件: 客户端拦(碗/盘,汤锅/炒锅/油锅成品)放置预览后, 显式通知服务端执行盛出
# 服务端在 FeatureDispatcher 用同一事件名监听(见 service/FeatureDispatcher.py)
TAKEOUT_EVENT = "TakeoutRequest"
# 自定义事件: 客户端拦(成品菜/米饭, 米饭/成品菜/蛋炒饭) 盖饭转换的放置预览后,
# 显式通知服务端执行盖饭转换(同盛出机制)。若不显式通知, 客户端 ret=True 会吞掉
# 服务端 ServerItemUseOnEvent, 导致盖饭"点无反应"。
GAI_FAN_EVENT = "GaifanRequest"
# 自定义事件: 客户端拦【其余全部 itemUseOn 转换】的放置预览后, 显式通知服务端执行(同盖饭)。
# 覆盖: 面食(生面饼烙馍/面粉加鸡蛋变面糊/生食蒸笼)、汤锅(锅态汤->汤面)、原版装盘(南瓜派
# 等->盘中餐)、相邻放置(放死鱼/生鸡块/切菜)、厨具蘸油(锅铲/带油锅铲)、寿司、裹粉、
# 种水稻. 若不显式通知, 客户端 ret=True 会吞掉 ServerItemUseOnEvent, 这些转换"点无反应"。
ITEM_USE_ON_REQUEST = "ItemUseOnRequest"
class ClientFeatureDispatcher(BlueFoxClientSystem):
@@ -35,6 +52,10 @@ class ClientFeatureDispatcher(BlueFoxClientSystem):
def __init__(self, namespace, systemName):
BlueFoxClientSystem.__init__(self, namespace, systemName)
print "===== ClientFeatureDispatcher init ====="
# 盛出/盖饭/出锅 后的客户端放置冷却: key=playerId@(x,y,z), value=时间戳.
# 转换完成后一段时间内, 该位置再被当作"放置"会直接 ret 吞掉, 消除"转换后
# 由于引擎重置右键导致多放置一次"的闪一下。时长 1.5 秒(用户要求明显可见)。
self._afterCd = {}
self.ListenForEvent(clientApi.GetEngineNamespace(),
clientApi.GetEngineSystemName(),
"ClientItemUseOnEvent", self, self.OnItemUseOn)
@@ -64,9 +85,28 @@ class ClientFeatureDispatcher(BlueFoxClientSystem):
if not itemName:
return
# 纯手持物品取消 (相邻放置/手持吃物品右键表面): 只看 item
# 转换后冷却: 同一玩家同一坐标, 若刚做过盛出/盖饭/出锅, 短时间内再被引擎
# 当作"放置" -> 直接吞掉(不触发服务端, 也不渲染放置预览). 消除"转换后
# 由于引擎重置右键导致的多放置闪烁"。
pId = args.get("entityId") or args.get("playerId")
cdKey = "%s@%s_%s_%s" % (pId, args.get("x"), args.get("y"), args.get("z"))
_t = self._afterCd.get(cdKey)
if _t is not None and (time.time() - _t) < 1.5:
args["ret"] = True
return
# 纯手持物品取消 (相邻放置/手持吃物品右键表面): 只看 item, 服务端走 handleItemUseOn
# (Yuanban 相邻放置/装盘、Natural 种水稻、Shiyongfood 直接吃)。这些组合一旦客户端
# ret=True 会吞掉 ServerItemUseOnEvent, 故必须显式通知服务端补齐(盖饭同款机制)。
# 【注意】此分支为"相邻格放置/装盘", 被点【表面坐标】不变, 不能设 _afterCd,
# 否则会误吞同一表面 1.5s 内的连续放置. 去重交给服务端 handleItemUseOn 自带冷却。
if itemName in ITEM_USE_ON_CANCEL_BY_ITEM:
args["ret"] = True
self.NotifyToServer(ITEM_USE_ON_REQUEST, {
"playerId": pId,
"x": args.get("x"), "y": args.get("y"), "z": args.get("z"),
"blockName": blockName, "itemName": itemName,
})
return
if not blockName:
@@ -74,18 +114,66 @@ class ClientFeatureDispatcher(BlueFoxClientSystem):
# 精确组合 (手持item, 被点block)
pair = (itemName, blockName)
# 盖饭/蛋炒饭(itemUseOn): 必须在此处理(比 ITEM_USE_ON_CANCEL 先), 否则会被
# ITEM_USE_ON_CANCEL 纯 ret 吞掉 -> 服务端 ServerItemUseOnEvent 不触发 -> 盖饭无反应。
if pair in GAI_FAN_CANCEL:
args["ret"] = True
self._afterCd[cdKey] = time.time()
self.NotifyToServer(GAI_FAN_EVENT, {
"playerId": pId,
"x": args.get("x"), "y": args.get("y"), "z": args.get("z"),
"blockName": blockName, "itemName": itemName,
})
return
# 其余 itemUseOn 转换(面食/汤锅/装盘/厨具蘸油/寿司/裹粉): 纯 ret 会吞掉服务端
# ServerItemUseOnEvent -> 服务端 handleItemUseOn 不触发 -> "点无反应"(盖饭同款)。
# 统一改为 ret + 显式 ItemUseOnRequest, 由服务端按 itemName 路由补齐 handleItemUseOn。
if pair in ITEM_USE_ON_CANCEL:
args["ret"] = True
self._afterCd[cdKey] = time.time()
self.NotifyToServer(ITEM_USE_ON_REQUEST, {
"playerId": pId,
"x": args.get("x"), "y": args.get("y"), "z": args.get("z"),
"blockName": blockName, "itemName": itemName,
})
return
# 寿司: 手持寿司方块物品 右键 自家寿司方块 -> 取消默认放置
# 汤锅盛出: 手持 碗/盘 右键 成品汤方块 -> 客户端取消放置预览(消闪),
# 并显式通知服务端执行盛出(take_out)。此组合客户端拦掉后,
# 服务端 ServerBlockUseEvent 不再触发, 故必须发 TakeoutRequest 补齐盛出逻辑。
if pair in TAKEOUT_CANCEL:
args["ret"] = True
self._afterCd[cdKey] = time.time()
self.NotifyToServer(TAKEOUT_EVENT, {
"playerId": pId,
"x": args.get("x"), "y": args.get("y"), "z": args.get("z"),
"blockName": blockName, "itemName": itemName,
})
return
# 寿司: 手持寿司方块物品 右键 自家寿司方块 -> 服务端走 ShousiService.handleItemUseOn
# (取消默认放置) + handleBlockUse(堆叠). 客户端 ret 吞事件后显式通知补齐。
if itemName in SUSHI_BLOCKS and blockName in SUSHI_BLOCKS:
args["ret"] = True
self._afterCd[cdKey] = time.time()
self.NotifyToServer(ITEM_USE_ON_REQUEST, {
"playerId": pId,
"x": args.get("x"), "y": args.get("y"), "z": args.get("z"),
"blockName": blockName, "itemName": itemName,
})
return
# 裹粉: 手持生食材 右键 面粉块 -> 取消默认放置
# 裹粉: 手持生食材 右键 面粉块 -> 服务端走 ShicaiService.handleItemUseOn(裹粉).
if itemName in COATING_ITEMS and blockName == "rtdd:farineblock_3":
args["ret"] = True
self._afterCd[cdKey] = time.time()
self.NotifyToServer(ITEM_USE_ON_REQUEST, {
"playerId": pId,
"x": args.get("x"), "y": args.get("y"), "z": args.get("z"),
"blockName": blockName, "itemName": itemName,
})
# ============================================================================
# 入口3: 手持直接吃物品(长按/空中使用) -> cancel 默认吃
@@ -41,6 +41,21 @@ comp = serverApi.GetEngineCompFactory() # 组件工厂
# ---- 继承 BlueFox 框架的系统基类 ----
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
from ...BlueFoxTools.ToolsConfig import LevelId
from ...BlueFoxTools.ModConfig import MOD_NAME
# ---- 汤锅盛出自定义事件名(与客户端 ClientFeatureDispatcher TAKEOUT_EVENT 一致) ----
TAKEOUT_EVENT = "TakeoutRequest"
# ---- 盖饭/蛋炒饭 自定义事件名(与客户端 GAI_FAN_EVENT 一致) ----
GAI_FAN_EVENT = "GaifanRequest"
# ---- 通用"手持物品右键方块表面"转换请求(与客户端 ITEM_USE_ON_REQUEST 一致).
# 用于修复"客户端提前 ret=True 吞掉 ServerItemUseOnEvent 导致 itemUseOn 转换
# 点无反应"的问题(盖饭同款). 客户端对所有走 handleItemUseOn 的白名单组合,
# ret 掉放置预览后显式 Notify 本事件, 服务端按 itemName 路由到相应服务.
# 与 GaifanRequest 的区别: 本事件覆盖全部 itemUseOn 转换(面食/汤锅/装盘/
# 相邻放置/厨具蘸油/寿司/裹粉/种水稻), 而非仅盖饭. ----
ITEM_USE_ON_REQUEST = "ItemUseOnRequest"
# ---- 方块实体数据组件(计时持久化:记录开始时间戳,重启后继续计时) ----
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
@@ -196,6 +211,20 @@ class FeatureDispatcher(BlueFoxServerSystem):
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ItemUseOnAfterServerEvent", self, self.OnItemPlaceAfter)
# 7. TakeoutRequest —— 客户端拦截(碗/盘,汤锅/炒锅/油锅成品)放置预览后, 显式请求服务端盛出。
# 客户端系统 ClientFeatureDispatcher 用 NotifyToServer(TAKEOUT_EVENT) 发送,
# 服务端以 MOD_NAME + 客户端系统名 监听该自定义事件。
self.ListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
TAKEOUT_EVENT, self, self.OnTakeoutRequest)
# 8. GaifanRequest —— 客户端拦截(成品菜/米饭,米饭/成品菜)盖饭放置预览后, 显式请求转换。
# 同盛出: 必须显式触发, 否则客户端 ret=True 吞掉 ServerItemUseOnEvent, 盖饭无反应。
self.ListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
GAI_FAN_EVENT, self, self.OnGaifanRequest)
# 9. ItemUseOnRequest —— 客户端拦截其余全部 itemUseOn 转换(面食/汤锅/装盘/相邻放置/
# 厨具蘸油/寿司/裹粉/种水稻)的放置预览后, 显式请求服务端执行对应 handleItemUseOn。
# (与 GaifanRequest 同机制, 统一覆盖全部 itemUseOn 转换)
self.ListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
ITEM_USE_ON_REQUEST, self, self.OnItemUseOnRequest)
def UnListenEvent(self):
self.UnListenForEvent(serverApi.GetEngineNamespace(),
@@ -216,6 +245,12 @@ class FeatureDispatcher(BlueFoxServerSystem):
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ItemUseOnAfterServerEvent", self, self.OnItemPlaceAfter)
self.UnListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
TAKEOUT_EVENT, self, self.OnTakeoutRequest)
self.UnListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
GAI_FAN_EVENT, self, self.OnGaifanRequest)
self.UnListenForEvent(MOD_NAME, "ClientFeatureDispatcher",
ITEM_USE_ON_REQUEST, self, self.OnItemUseOnRequest)
# ============================================================================
# 主入口1:玩家右键方块 → 按 blockOwner 路由到右键服务
@@ -232,8 +267,15 @@ class FeatureDispatcher(BlueFoxServerSystem):
服务返回 False = 当前组合无匹配规则 → 交还默认行为
"""
blockName = args.get("blockName")
playerId = args.get("playerId")
# 手持物品名(用于区分空手/木碗/盘子)
try:
_itemDict = self.getCarriedItem(playerId)
_handItem = _itemDict.get("newItemName") if _itemDict else "NONE"
except Exception:
_handItem = "NONE"
services = self.blockOwnerAll.get(blockName)
print "[FD-BlockUse]", blockName, "svcs:", ([s.__class__.__name__ for s in services] if services else "NONE")
print "[FD-BlockUse-ENTER]", "player:", playerId, "hand:", _handItem, "block:", blockName, "svcs:", ([s.__class__.__name__ for s in services] if services else "NONE")
if not services:
# 这个方块不属于任何已注册的功能 → 交给原版/其他模组处理
return
@@ -242,9 +284,10 @@ class FeatureDispatcher(BlueFoxServerSystem):
handled = service.handleBlockUse(args)
if handled:
args['cancel'] = True
playerId = args.get("playerId")
self._cancelItemUse[playerId] = True
print "[FD-BlockUse-DONE]", "player:", playerId, "hand:", _handItem, "block:", blockName, "svc:", service.__class__.__name__, "-> cancel=True, _cancelItemUse=True"
return
print "[FD-BlockUse-NOMATCH]", "player:", playerId, "hand:", _handItem, "block:", blockName, "-> 无匹配规则, 放行"
# ============================================================================
# 主入口2:手持物品右键方块表面 → 按 itemUseOnOwner 路由到右键服务
@@ -269,6 +312,7 @@ class FeatureDispatcher(BlueFoxServerSystem):
# (与 OnItemTryUse 消费标记取消"吃/喝"是同一模式。)
if self._cancelItemUse.pop(playerId, False):
args['ret'] = True
print "[FD-ItemUseOn-CONSUME-CANCEL]", "player:", playerId, "item:", args.get("itemName"), "block:", args.get("blockName"), "-> ret=True 取消放置"
return
itemName = args.get("itemName")
@@ -301,8 +345,18 @@ class FeatureDispatcher(BlueFoxServerSystem):
# 例外: 当前服务本身就在被点方块的归属列表里(如 食材裹粉: 面粉块归
# ShicaiService+面食服务 管辖, 手持生食材右键它时放行给 handleItemUseOn
# 处理) 则继续尝试。
# 【额外例外】服务声明了 ownsItemUseOn(itemName, clickedName) 且返回 True,
# 说明该(手持物品,被点方块)组合明确由本服务的 itemUseOn 规则处理(如炒锅盖饭:
# 成品菜/米饭尽管也被"总食用"登记为右键吃方块, 但盖饭转换应仍走 itemUseOn)。
# 此时不因被点方块归属别的服务而跳过, 否则盖饭永不被触发。
if clickedName and owners and service not in owners:
continue
_owns = None
try:
_owns = service.ownsItemUseOn(itemName, clickedName)
except Exception:
_owns = None
if not _owns:
continue
handled = service.handleItemUseOn(args)
if handled:
@@ -348,11 +402,117 @@ class FeatureDispatcher(BlueFoxServerSystem):
return
if not blockName:
return
print "[FD-Placed-AFTER]", "player:", playerId, "placedBlock:", blockName, "targetPos:", targetPos
# 落点是计时源方块 → 带放置者启动(判断营火 + 谁放提示发给谁)
service = self.timerOwner.get(blockName)
if service and hasattr(service, "startTimer"):
service.startTimer(dim, targetPos, blockName, playerId)
# ============================================================================
# 主入口2.6:客户端显式触发的"盛出"请求(汤锅 take_out 专用,消闪方案)
# ============================================================================
def OnTakeoutRequest(self, args):
"""
客户端 ClientFeatureDispatcher 在拦截(碗/盘, 汤锅成品)放置预览后,
显式 NotifyToServer 发来的盛出请求。因为客户端已 ret=True 把该组合的
ServerBlockUseEvent 吞掉(消闪), 所以盛出逻辑必须在这里手动补齐。
与服务器普通右键一致, 需要校验: 别再重复执行, 关键落在 handleTakeoutRequest
里"落点仍是成品方块才执行"的守卫上(防客户端事件与服务端事件双份)。
"""
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
blockName = args.get("blockName")
itemName = args.get("itemName")
if not (playerId and x is not None and y is not None and z is not None and blockName):
return
try:
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
except Exception:
return
services = self.blockOwnerAll.get(blockName)
if not services:
return
for service in services:
if hasattr(service, "handleTakeoutRequest"):
# handleTakeoutRequest 内部已校验"落点仍是成品方块" + 冷却, 防双份。
# 盛出后引擎"把手持碗/盘放到已变空锅"的多放置闪烁由客户端 CD(1.5秒)
# 在客户端吞掉, 服务端不再设 _cancelItemUse(否则标记残留会吞掉玩家
# 下一次真实的 ServerItemUseOnEvent, 影响其它操作)。
service.handleTakeoutRequest(playerId, blockName, itemName, dim, (x, y, z))
return
print "[FD-TakeoutRequest-NO-HANDLER]", blockName
def OnGaifanRequest(self, args):
"""
客户端 ClientFeatureDispatcher 在拦截(成品菜/米饭,米饭/成品菜)放置预览后,
显式 NotifyToServer 发来的盖饭/蛋炒饭转换请求。客户端已 ret=True 把该组合的
ServerItemUseOnEvent 吞掉(消闪), 这里必须显式补齐转换逻辑(否则盖饭"点无反应")。
构造 itemUseOn 所需的 args, 按 itemName 路由到 itemUseOn 服务(ChaoguoService)。
"""
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
itemName = args.get("itemName")
if not (playerId and x is not None and y is not None and z is not None and itemName):
return
services = self.itemUseOnAll.get(itemName)
if not services:
print "[FD-GaifanRequest-NO-SVC]", itemName
return
# 复用服务端的 itemUseOn 参数结构(handleItemUseOn 读取 itemName/playerId/x/y/z)
useOnArgs = {
"itemName": itemName,
"playerId": playerId,
"entityId": playerId,
"x": x, "y": y, "z": z,
"blockName": args.get("blockName"),
}
for service in services:
if hasattr(service, "handleItemUseOn"):
# 盖饭是本方块原位转换成盖饭(消耗手持成品菜/米饭), 不是"放置手持方块",
# 因此不设 _cancelItemUse —— 否则标记残留会吞掉玩家下一次真实的 itemUseOn.
handled = service.handleItemUseOn(useOnArgs)
if handled:
return
print "[FD-GaifanRequest-NO-HANDLER]", itemName
def OnItemUseOnRequest(self, args):
"""
客户端 ClientFeatureDispatcher 在拦截【所有走 handleItemUseOn 的 itemUseOn 转换】
(面食/汤锅/装盘/相邻放置/厨具蘸油/寿司/裹粉/种水稻) 放置预览后, 显式 NotifyToServer
发来的通用转换请求. 因为客户端已 ret=True 吞掉该组合的 ServerItemUseOnEvent(消闪),
服务端原本会被吞掉的 itemUseOn 转换在这里补齐路由(与 OnGaifanRequest 同机制).
按 itemName 路由到 itemUseOnAll 里的服务(可能有多个, 如 裹粉Shicai + 原版放置Yuanban
都处理 土豆/鸡肉/猪肉, 逐个 fallback, 第一个返回 True 的处理成功).
注意: 各服务 handleItemUseOn 内部自带 checkCooldown(同坐标/同物品), 即使与真实的
ServerItemUseOnEvent 重复触发, 冷却也会拦截第二次, 不会双重消耗/双重转换.
"""
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
itemName = args.get("itemName")
if not (playerId and x is not None and y is not None and z is not None and itemName):
return
services = self.itemUseOnAll.get(itemName)
if not services:
print "[FD-ItemUseOnRequest-NO-SVC]", itemName
return
# 复用服务端的 itemUseOn 参数结构(handleItemUseOn 读取 itemName/playerId/x/y/z)
useOnArgs = {
"itemName": itemName,
"playerId": playerId,
"entityId": playerId,
"x": x, "y": y, "z": z,
"blockName": args.get("blockName"),
}
for service in services:
if hasattr(service, "handleItemUseOn"):
handled = service.handleItemUseOn(useOnArgs)
if handled:
return
print "[FD-ItemUseOnRequest-NO-HANDLER]", itemName
# ============================================================================
# 主入口3:拦截物品使用(防止喝药水/吃食物等默认行为)
# ============================================================================
@@ -73,17 +73,35 @@ except Exception:
# ---- 炒锅: 盖饭双向 + 蛋炒饭 (itemUseOn, surface=米饭/成品菜) ----
try:
from ..chaoguo.ChaoguoData import ITEM_USE_ON_RULES as CHAOGUO_USON
from ..chaoguo.ChaoguoData import (
ITEM_USE_ON_RULES as CHAOGUO_USON,
SWITCH_RULES as CHAOGUO_SWITCH,
PLATE as CHAOGUO_PLATE,
)
_ALL_RULE_TABLES.append(CHAOGUO_USON)
except Exception:
pass
CHAOGUO_SWITCH, CHAOGUO_PLATE = None, None
# ---- 汤锅: 手持锅态汤右键 熟面条 (itemUseOn) ----
try:
from ..tangguo.TangguoData import ITEM_USE_ON_RULES as TANGGUO_USON
from ..tangguo.TangguoData import (
ITEM_USE_ON_RULES as TANGGUO_USON,
SWITCH_RULES as TANGGUO_SWITCH,
BOWL as TANGGUO_BOWL,
PLATE as TANGGUO_PLATE,
)
_ALL_RULE_TABLES.append(TANGGUO_USON)
except Exception:
pass
TANGGUO_SWITCH, TANGGUO_BOWL, TANGGUO_PLATE = None, None, None
# ---- 油锅: 手持盘子 出锅 (take_out) ----
try:
from ..youguo.YouguoData import (
SWITCH_RULES as YOUGUO_SWITCH,
PLATE as YOUGUO_PLATE,
)
except Exception:
YOUGUO_SWITCH, YOUGUO_PLATE = None, None
# ---- 原版放置/盘子转换 (itemUseOn, surface=相邻放置 / source=盘子) ----
try:
@@ -117,6 +135,57 @@ def _as_list(value):
# ---- 由各 Data 规则表自动派生 ----
AUTO_CANCEL = _collect_all()
# ============================================================================
# 三之A、"盛出/出锅"专用: 手持容器(木碗/盘子) 右键 成品方块 -> 客户端拦截
# 放置预览(消闪) + 显式通知服务端执行盛出(见 ClientFeatureDispatcher /
# FeatureDispatcher TakeoutRequest). 从 汤锅/炒锅/油锅 的 SWITCH_RULES 里
# action=="take_out" 且 item 是容器(木碗/盘子) 的规则自动派生.
# 空手(item=None)取饭团/水煮蛋/煎饼、菜刀切豆腐 不在此列, 走旧路径.
# ============================================================================
TAKEOUT_CANCEL = set()
def _derive_takeout(switch_rules, *container_items):
"""从一段 SWITCH_RULES 派生 take_out 且 item 是容器的 (item, block) 组合"""
if not switch_rules:
return set()
out = set()
for _r in switch_rules:
if not isinstance(_r, dict):
continue
if _r.get("action") != "take_out":
continue
_it = _r.get("item")
_bk = _r.get("block")
if _it in container_items and isinstance(_bk, basestring) and _bk:
out.add((_it, _bk))
return out
# 汤锅: 木碗/盘子 盛出碗装成品
TAKEOUT_CANCEL |= _derive_takeout(TANGGUO_SWITCH, TANGGUO_BOWL, TANGGUO_PLATE)
# 炒锅: 手持盘子 出锅盘装成品(空手出锅的煎饼/馍不在此列)
TAKEOUT_CANCEL |= _derive_takeout(CHAOGUO_SWITCH, CHAOGUO_PLATE)
# 油锅: 手持盘子 出锅炸物
TAKEOUT_CANCEL |= _derive_takeout(YOUGUO_SWITCH, YOUGUO_PLATE)
# ============================================================================
# 三之B、"盖饭/蛋炒饭"(itemUseOn) 专用: 手持 成品菜/米饭 右键 米饭/成品菜/炒锅蛋炒饭
# -> 客户端拦截放置预览(消闪) + 显式通知服务端执行转换(同 TakeoutRequest 机制).
# 从炒锅 ITEM_USE_ON_RULES(surface 字段) 自动派生: 盖饭双向 + 蛋炒饭.
# 若不单独抽出, 这些组合会被 ITEM_USE_ON_CANCEL 以纯 ret=True 吞掉,
# 导致服务端 ServerItemUseOnEvent 不触发 -> 盖饭"点无反应"(服务端 owners 修复无效).
# ============================================================================
GAI_FAN_CANCEL = set()
if CHAOGUO_USON:
for _r in CHAOGUO_USON:
if not isinstance(_r, dict):
continue
_it = _r.get("item")
_bk = _r.get("surface")
if isinstance(_it, basestring) and _it and isinstance(_bk, basestring) and _bk:
GAI_FAN_CANCEL.add((_it, _bk))
# ---- Chuju 锅铲/油罐 inline 转换(在 ChujuService.handleItemUseOn 内联, 不在数据表) ----
# 手持 锅铲/带油锅铲 右键 油罐(oilcan)/炒锅(wok) 蘸油/还油/上油 -> 取消默认
CHUJU_INLINE_CANCEL = {
@@ -206,4 +275,7 @@ ITEM_USE_ON_CANCEL_BY_ITEM = PLACE_ITEMS | EAT_ITEMS
if __name__ == "__main__":
print "=== 自动派生转换对 数量:", len(AUTO_CANCEL), "==="
for _it, _bk in sorted(AUTO_CANCEL):
print " ", _it, "->", _bk
print "=== 汤锅盛出(TAKEOUT_CANCEL) 数量:", len(TAKEOUT_CANCEL), "==="
for _it, _bk in sorted(TAKEOUT_CANCEL):
print " ", _it, "->", _bk
@@ -377,9 +377,83 @@ SWITCH_RULES = [
"tip": "§a倒入豆浆, 开始熬制; 等计时结束变成 煮豆腐(熟), 拿菜刀右键切成 豆腐x16"},
# ---------------- 六、出锅(熟汤羹+木碗 -> 碗装成品, 锅变回汤锅(空)) ----------------
# 【已迁移 2026-09-23】手持 木碗/盘子 右键 熟汤方块 的 take_out 全部搬到了
# ITEM_USE_ON_RULES(source=surface), 走 itemUseOn 路径: 转换+取消放置同一事件,
# 进客户端白名单提前拦, 消除"盛出闪一下"。此处不再定义(避免 blockUse 先拦截)。
{"block": CARROT_BEEF_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_CARROT_BEEF, "consume": 1,
"tip": "§a盛出 胡萝卜牛肉汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": LAMB_SOUP_WITH_TURNIPS_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_LAMB_TURNIP, "consume": 1,
"tip": "§a盛出 胡萝卜羊肉汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": CARROT_RIBS_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_CARROT_RIBS, "consume": 1,
"tip": "§a盛出 胡萝卜排骨汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": VEGETABLE_BEEF_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_VEGETABLE_BEEF, "consume": 1,
"tip": "§a盛出 清汤羊肉, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": VEGETABLE_LAMB_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_VEGETABLE_LAMB, "consume": 1,
"tip": "§a盛出 蔬菜牛肉汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": VEGETABLE_LEAN_BROTH_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_VEGETABLE_LEAN, "consume": 1,
"tip": "§a盛出 蔬菜瘦肉汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": VEGETABLE_SSTEW_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_VEGETABLE_SSTEW, "consume": 1,
"tip": "§a盛出 蔬菜乱炖, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": BEEF_BONE_BROTH_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_BEEF_BONE, "consume": 1,
"tip": "§a盛出 胡萝卜土豆牛骨汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": LAMB_STEW_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_LAMB_STEW, "consume": 1,
"tip": "§a盛出 土豆胡萝卜炖羊肉, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": LAMB_CHOP_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_LAMB_CHOP, "consume": 1,
"tip": "§a盛出 胡萝卜土豆羊排汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": CARROT_COD_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_CARROT_COD, "consume": 1,
"tip": "§a盛出 胡萝卜鳕鱼汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": CARROT_FISH_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_CARROT_FISH, "consume": 1,
"tip": "§a盛出 胡萝卜鲑鱼汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": EGG_COD_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_EGG_COD, "consume": 1,
"tip": "§a盛出 蛋花鳕鱼汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": NORI_EGG_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_NORI_EGG, "consume": 1,
"tip": "§a盛出 瘦肉蛋花汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": TOFU_FISH_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_TOFU_FISH, "consume": 1,
"tip": "§a盛出 豆腐鲑鱼汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": CHICKEN_STEW_MUSHROOMS_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_MUSHROOM_CHICKEN, "consume": 1,
"tip": "§a盛出 蘑菇炖鸡, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": RAW_PIG_FOOD_SOUP_5, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_PIG_FOOT, "consume": 1,
"tip": "§a盛出 猪蹄汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": ROUJIAMO_SOUP_1, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_ROUJIAMO, "consume": 1,
"tip": "§a盛出 羊肉泡馍汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": COOKED_NOODLE_SOUP, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_NOODLE, "consume": 1,
"tip": "§a盛出 熟面条, 锅变回 汤锅(空); 熟面条可做汤面"},
{"block": DUMPLINGS_SOUP_2, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_DUMPLINGS, "consume": 1,
"tip": "§a盛出 饺子_碗装, 锅变回 汤锅(空); 成品【吃】右键食用"},
# 菜谱32 todo: 饺子汤_熟 + 盘子 -> 美味饺子(全份)盘装
{"block": DUMPLINGS_SOUP_2, "item": PLATE, "action": "take_out",
"target": POT_EMPTY, "dropItem": PLATE_30A, "consume": 1,
"tip": "§a捞出 美味饺子(全份)盘装, 锅变回 汤锅(空); 成品【吃】右键食用"},
{"block": VEGETABLE_FISH_SOUP_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_VEGETABLE_FISH, "consume": 1,
"tip": "§a盛出 蔬菜鱼片汤, 锅变回 汤锅(空); 成品【吃】右键食用"},
# 菜谱38 碗装米饭(木碗盛出)
{"block": RICE_POT_1, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_RICE_1, "consume": 1,
"tip": "§a盛出 米饭_1(熟米饭_形态1), 锅变回 汤锅(空); 碗装米饭供炒锅蛋炒饭"},
{"block": RICE_POT_2, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_RICE_2, "consume": 1,
"tip": "§a盛出 米饭_2(熟米饭_形态2), 锅变回 汤锅(空); 碗装米饭供炒锅蛋炒饭"},
{"block": RICE_POT_3, "item": BOWL, "action": "take_out",
"target": POT_EMPTY, "dropItem": BOWL_RICE_3, "consume": 1,
"tip": "§a盛出 米饭_3(熟米饭_形态3), 锅变回 汤锅(空); 碗装米饭供炒锅蛋炒饭"},
# ---------------- 七、取出(空手/菜刀, 锅变回汤锅(空)) ----------------
# 菜谱37 饭团
@@ -257,6 +257,35 @@ class TangguoService(BlueFoxServerSystem):
# 二、通用转换执行
# ============================================================================
def handleTakeoutRequest(self, playerId, blockName, itemName, dim, pos):
"""客户端显式触发的盛出请求(消闪方案)。
客户端已把 (碗/盘, 成品汤方块) 的放置预览拦掉(见 ClientFeatureDispatcher),
本方法在服务端补齐盛出逻辑, 与右键 take_out 等效:
- 校验落点仍是该成品方块(防客户端事件与服务端 ServerBlockUseEvent 双份)
- 用 (block, item) 精确定位盛出规则并执行 switch
"""
# 守卫1: 防空手/非碗盘(客户端已过滤, 这里再兜底)
if not itemName or itemName in ("minecraft:air",):
return
try:
b = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
cur = b.get("name") if b else None
except Exception:
cur = None
# 守卫2: 落点方块已不是该成品(可能已被旧路径/上一次盛出处理掉) -> 跳过, 防重复
if cur != blockName:
print "[TakeoutRequest-SKIP-ALREADY]", "want:", blockName, "cur:", cur, "pos:", pos
return
recipe = self.recipes.get((blockName, itemName))
if not recipe or recipe.get("action") != "take_out":
return
# 守卫3: 冷却, 防连点双发
if not self.checkCooldown((dim, pos[0], pos[1], pos[2]), 0.4):
return
itemDict = self.getCarriedItem(playerId)
self.handleSwitch(recipe, playerId, pos, dim, itemDict)
print "[TakeoutRequest-DONE]", "player:", playerId, "item:", itemName, "block:", blockName
def handleSwitch(self, recipe, playerId, pos, dim, itemDict):
# 消耗物品(数量不足则拦截, 不转换)
consume = recipe.get("consume", 0)
@@ -219,6 +219,33 @@ class YouguoService(BlueFoxServerSystem):
# 二、通用转换执行
# ============================================================================
def handleTakeoutRequest(self, playerId, blockName, itemName, dim, pos):
"""客户端显式触发的出锅请求(消闪方案, 同汤锅).
客户端已把 (盘子, 熟炸物) 的放置预览拦掉(见 ClientFeatureDispatcher),
本方法在服务端补齐出锅逻辑: 校验落点仍是该成品 + 精确定位 take_out 规则
-> 复用 handleSwitch(含 needBelow 门控), 交给它执行出锅"""
# 守卫1: 防空手
if not itemName or itemName in ("minecraft:air",):
return
try:
b = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
cur = b.get("name") if b else None
except Exception:
cur = None
# 守卫2: 落点已不是该成品 -> 跳过, 防双份
if cur != blockName:
print "[TakeoutRequest-SKIP-ALREADY]", "want:", blockName, "cur:", cur, "pos:", pos
return
recipe = self.recipes.get((blockName, itemName))
if not recipe or recipe.get("action") != "take_out":
return
# 守卫3: 冷却, 防连点双发
if not self.checkCooldown((dim, pos[0], pos[1], pos[2]), 0.4):
return
itemDict = self.getCarriedItem(playerId)
self.handleSwitch(recipe, playerId, pos, dim, itemDict)
print "[TakeoutRequest-DONE]", "player:", playerId, "item:", itemName, "block:", blockName
def handleSwitch(self, recipe, playerId, pos, dim, itemDict):
# 需要下方方块(放料: 油锅下须是点燃的炉灶才计时炸制): 不满足则提示并拦截
needBelow = recipe.get("needBelow")