寿司 功能实现 1

This commit is contained in:
wenhe committed 2026-09-09 16:06:45 +08:00
1 parent 51804de264
commit b82391c5c9
12 files changed
+692 -56

No files matched your search

@@ -0,0 +1,341 @@
# -*- coding: utf-8 -*-
"""
================================================================================
寿司(xy_sushi)右键转换引擎(服务端)
================================================================================
处理"寿司"(xy_sushi) 分类下的右键转换:
基础组(干海带铺饭团)、加鱼、卷制、切块、手握(加鱼/堆叠)、
寿司堆(堆叠/递减取走)、饭团放置手握普通寿司(ItemUseOn)。
【事件监听在哪?】
本类【不】监听任何事件!
所有事件由 FeatureDispatcher 统一监听, 按 blockOwner/itemUseOnOwner 路由到本类。
【本类怎么做】
handleBlockUse ← 入口:分发器转发右键方块事件
├─ _handleCut ← 手持菜刀右键卷寿司两次 -> 寿司块(三个)
└─ handleSwitch ← 通用转换(基础组/加鱼/卷制/手握/寿司堆/递减)
handleItemUseOn ← 入口:手持饭团右键方块表面 -> 放置 手握普通寿司
(通用工具) getCarriedItem / consumeItem / giveItem / replaceBlock /
checkCooldown
================================================================================
"""
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 ..ShousiData import (
RICE_BALL, CLEAVER_ITEM,
NORI_0, NORI_1, NIGIRI_BASE,
SUSHI_GROUPS, NORI_RICE_CONSUME, CUT_COUNT, STACK_CONSUME,
)
# ---- 统一分发器 ----
from ...service.FeatureDispatcher import FeatureDispatcher
class ShousiService(BlueFoxServerSystem):
"""
寿司·右键转换引擎
"""
def __init__(self, namespace, systemName):
super(ShousiService, self).__init__(namespace, systemName)
print "===== ShousiService init ====="
# ---- (方块, 手持物品) -> 规则: O(1) 查找 ----
self.recipes = {}
self.featureBlocks = set()
# 1) 基础组: 干海带 + 手持饭团右键 -> 干海带(加饭团), 消耗4个饭团
self.recipes[(NORI_0, RICE_BALL)] = {
"target": NORI_1, "consume": NORI_RICE_CONSUME,
"tip": "§a海苔上铺好了饭团",
}
self.featureBlocks.add(NORI_0)
# 2) 切块映射: 卷寿司(链尾) -> 组(菜刀两次 -> 块三)
self.cutMap = {}
# 3) 逐组构建规则
for g in SUSHI_GROUPS:
roll = g.get("roll")
fish = g.get("fish")
# 卷寿司组(鳕鱼/鲑鱼/热带鱼): 加鱼 -> 卷制 -> 切块 -> 寿司堆
# (鱿鱼组 roll=None, 只有手握组, 无卷寿司/寿司堆)
if roll:
# 加鱼: 干海带(加饭团) + 手持鱼右键 -> 寿司(加鱼), 消耗1个
self.recipes[(NORI_1, fish)] = {
"target": roll[0], "consume": 1, "tip": g.get("addTip"),
}
# 卷制链: 空手右键逐级
for i in range(len(roll) - 1):
self.recipes[(roll[i], None)] = {
"target": roll[i + 1], "tip": g.get("rollTip"),
}
self.featureBlocks.add(roll[i])
# 切块: 卷寿司(链尾) 手持菜刀右键两次 -> 块(三个)
self.cutMap[roll[-1]] = g
self.featureBlocks.add(roll[-1])
# 寿司堆: 手持卷寿司右键 -> 堆(两个) -> 堆(三个), 每级消耗1个
if g.get("pile2"):
self.recipes[(roll[-1], roll[-1])] = {
"target": g["pile2"], "consume": STACK_CONSUME,
"tip": g.get("pileTip"),
}
self.recipes[(g["pile2"], roll[-1])] = {
"target": g["pile3"], "consume": STACK_CONSUME,
"tip": g.get("pileTip"),
}
# 递减: 空手右键, 每级取走一个, 退回卷寿司
self.recipes[(g["pile3"], None)] = {
"target": g["pile2"], "give": roll[-1],
"tip": g.get("takeTip"),
}
self.recipes[(g["pile2"], None)] = {
"target": roll[-1], "give": roll[-1],
"tip": g.get("takeTip"),
}
self.featureBlocks.add(g["pile2"])
self.featureBlocks.add(g["pile3"])
# 手握加鱼: 手握普通寿司 + 手持鱼右键 -> 手握X寿司(一个), 消耗1个
self.recipes[(NIGIRI_BASE, fish)] = {
"target": g["nigiri1"], "consume": 1, "tip": g.get("nigiriTip"),
}
# 手握堆叠: 手持 手握X寿司(一个) 右键 -> (两个) -> (三个), 每级消耗1个
self.recipes[(g["nigiri1"], g["nigiri1"])] = {
"target": g["nigiri2"], "consume": STACK_CONSUME,
"tip": "§a堆叠成两个",
}
self.recipes[(g["nigiri2"], g["nigiri1"])] = {
"target": g["nigiri3"], "consume": STACK_CONSUME,
"tip": "§a堆叠成三个",
}
self.featureBlocks.add(g["nigiri1"])
self.featureBlocks.add(g["nigiri2"])
self.featureBlocks.add(g["nigiri3"])
self.featureBlocks.add(NORI_1)
self.featureBlocks.add(NIGIRI_BASE)
# ---- 切块计数 {(dim,x,y,z): n} ----
self.cutCounter = {}
# ---- 右键冷却表 ----
self.cooldown = {}
# ---- 注册到统一分发器 ----
FeatureDispatcher.register(self)
# ============================================================================
# 一、给 FeatureDispatcher 的接口
# ============================================================================
def getFeatureBlocks(self):
"""返回本功能管辖的所有右键方块名集合"""
return self.featureBlocks
def getItemUseOnRules(self):
"""手持饭团右键方块表面路由(放置手握普通寿司)"""
return {RICE_BALL: True}
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)
itemName = itemDict.get("newItemName") if itemDict else None
# ---- 切块: 手持菜刀右键卷寿司 ----
if blockName in self.cutMap:
if itemName == CLEAVER_ITEM:
return self._handleCut(blockName, playerId, pos, dim, key)
# ---- 常规转换(基础组/加鱼/卷制/手握/寿司堆/递减) ----
recipe = self.findRecipe(blockName, itemDict)
if not recipe:
return False
if not self.checkCooldown(key, 0.5):
return True
self.handleSwitch(recipe, playerId, pos, dim, itemDict)
return True
def handleItemUseOn(self, args):
"""由 FeatureDispatcher 转发的"手持物品右键方块表面"入口(饭团放置):
手持饭团右键任意非寿司方块表面 -> 在表面上方放置 手握普通寿司。
返回 True=处理了; False=不属本功能管(交还其他服务/默认行为)。"""
itemName = args.get("itemName")
if itemName != RICE_BALL:
return False
playerId = args.get("playerId") or args.get("entityId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
# 被点方块若归寿司管辖(干海带/寿司等), 交给方块右键逻辑, 不在此放置
try:
cur = comp.CreateBlockInfo(LevelId).GetBlockNew((x, y, z), dim)
if not cur or cur.get("name") in self.featureBlocks:
return False
except Exception:
return False
# 目标格 = 被点方块上方一格, 必须为空
targetPos = (x, y + 1, z)
try:
cur = comp.CreateBlockInfo(LevelId).GetBlockNew(targetPos, dim)
if cur and cur.get("name") != "minecraft:air":
return False
except Exception:
return False
# 连点冷却
key = (dim, targetPos[0], targetPos[1], targetPos[2])
if not self.checkCooldown(key, 0.5):
return True
# 消耗饭团 + 放置手握普通寿司
itemDict = self.getCarriedItem(playerId)
if not itemDict:
return False
self.consumeItem(playerId, itemDict, 1)
self.replaceBlock(targetPos, dim, NIGIRI_BASE, playerId)
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a摆成手握普通寿司")
return True
# ============================================================================
# 二、规则匹配(精确 + 空手 fallback)
# ============================================================================
def findRecipe(self, blockName, itemDict):
itemName = itemDict.get("newItemName") if itemDict else None
recipe = self.recipes.get((blockName, itemName))
if recipe:
return recipe
if not itemName or itemName == "minecraft:air":
return self.recipes.get((blockName, None))
return None
# ============================================================================
# 三、通用转换执行
# ============================================================================
def handleSwitch(self, recipe, playerId, pos, dim, itemDict):
# 消耗物品
if recipe.get("consume", 0) > 0:
self.consumeItem(playerId, itemDict, recipe["consume"])
# 替换方块
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# 给背包物品(递减取走)
if recipe.get("give"):
self.giveItem(playerId, recipe["give"])
# 提示
if recipe.get("tip"):
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
# ============================================================================
# 四、切块(手持菜刀右键卷寿司两次 -> 寿司块(三个))
# ============================================================================
def _handleCut(self, blockName, playerId, pos, dim, key):
if not self.checkCooldown(key, 0.3):
return True
g = self.cutMap[blockName]
n = self.cutCounter.get(key, 0) + 1
if n >= CUT_COUNT:
self.cutCounter.pop(key, None)
self.replaceBlock(pos, dim, g["cubes"], playerId)
msg = g.get("cutTip") or "§a切好了"
else:
self.cutCounter[key] = n
msg = "§a切寿司中…(%d/%d)" % (n, CUT_COUNT)
comp.CreateMsg(playerId).NotifyOneMessage(playerId, msg)
return True
# ============================================================================
# 五、工具方法:获取玩家主手物品
# ============================================================================
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", {})})
# ============================================================================
# 八、工具方法:替换方块(保留 aux, 兼容定向变体)
# ============================================================================
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 checkCooldown(self, key, cd=0.5):
now = time.time()
last = self.cooldown.get(key, 0)
if now - last < cd:
return False
self.cooldown[key] = now
return True
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-