寿司 功能实现 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

@@ -17,6 +17,7 @@ from xiangyemod.chuju.service.ChujuTimerService import ChujuTimerService
from xiangyemod.ziran.service.NaturalService import NaturalService
from xiangyemod.ziran.service.NaturalTimerService import NaturalTimerService
from xiangyemod.shicai.service.ShicaiService import ShicaiService
from xiangyemod.shousi.service.ShousiService import ShousiService
if not serverApi.IsInServer():
pass
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
"""
================================================================================
寿司(xy_sushi)转化规则数据
================================================================================
统一管理"寿司"分类下的转化规则常量。只被 ShousiService 读取,
FeatureDispatcher 通过方块名/物品名集合路由到对应服务。
【实现范围(对应 寿司转化关系.txt)】
基础组 : 干海带 + 手持饭团右键 -> 干海带(加饭团), 消耗4个饭团
加鱼 : 干海带(加饭团) + 手持鱼右键 -> 对应寿司(加鱼), 消耗1个
卷制 : 空手右键逐级卷制(四级), 鲑鱼链无"卷_3"标注(成品卷=链尾)
切块 : 手持菜刀右键卷寿司两次 -> 寿司块(三个)
手握组 : 手握普通寿司 + 手持鱼右键 -> 手握X寿司(一个);
手持 手握X寿司(一个) 右键 逐级堆叠到(三个)
寿司堆组: 手持卷寿司右键 -> 寿司堆(两个) -> 寿司堆(三个);
空手右键递减取走, 退回卷寿司
饭团放置: 手持饭团右键方块表面 -> 在表面上方放置 手握普通寿司
(txt 第18条 todo, 手握组起点)
【不实现(txt 明确"后续实现")】
- 吃: 寿司块递减(5/9/13)、手握递减(14~17) 的空手右键吃, 后续实现
- 饭团来源: 汤锅熟米饭捏饭团(属 汤锅 分类, 本次不做)
================================================================================
"""
# ---- 物品 ----
RICE_BALL = "rtdd:rice_balls" # 饭团
CLEAVER_ITEM = "rtdd:cleaver" # 菜刀(刀)
# ---- 鱼物品(手持原料) ----
FISH_COD = "minecraft:cod" # 鳕鱼
FISH_SALMON = "minecraft:salmon" # 鲑鱼
FISH_TROPICAL = "minecraft:tropical_fish" # 热带鱼
FISH_SQUID = "rtdd:rawsquidwhiskersr" # 鱿鱼须(游戏中代表鱿鱼)
# ---- 干海带 ----
NORI_0 = "rtdd:sushi_0" # 干海带
NORI_1 = "rtdd:sushi_1" # 干海带(加饭团), 游戏内名"干海带(加米)"
# ---- 手握普通寿司(手握组起点) ----
NIGIRI_BASE = "rtdd:nigirisushi"
# ============================================================================
# 四组鱼寿司
# roll : 卷链(加鱼 -> 卷_1 -> 卷_2 -> 卷_3 -> 卷_4/成品卷), 空手右键逐级
# cubes : 切块得到的 寿司块(三个)
# nigiri1/2/3 : 手握X寿司(一个/两个/三个)
# pile2/3: 寿司堆(X_两个/X_三个)
# ============================================================================
SUSHI_GROUPS = [
{
"key": "cod",
"fish": FISH_COD,
"roll": ["rtdd:codsushi_1", "rtdd:codsushi_2",
"rtdd:codsushi_3", "rtdd:codsushi_4", "rtdd:codsushi_5"],
"cubes": "rtdd:codsushi_6",
"nigiri1": "rtdd:nigirisushifishcod",
"nigiri2": "rtdd:nigirisushicod_2",
"nigiri3": "rtdd:nigirisushicod_3",
"pile2": "rtdd:sushipile_2",
"pile3": "rtdd:sushipile_3",
"addTip": "§a加上鳕鱼",
"rollTip": "§a鳕鱼寿司卷制中…",
"cutTip": "§a切出了鳕鱼寿司块(三个)",
"nigiriTip": "§a捏成手握鳕鱼寿司",
"pileTip": "§a堆成寿司堆",
"takeTip": "§a取走一个",
},
{
"key": "salmon",
"fish": FISH_SALMON,
"roll": ["rtdd:salmonsushi_1", "rtdd:salmonsushi_2",
"rtdd:salmonsushi_3", "rtdd:salmonsushi_4", "rtdd:salmonsushi_5"],
"cubes": "rtdd:salmonsushi_6",
"nigiri1": "rtdd:nigirisushifishsalmon_1",
"nigiri2": "rtdd:nigirisushisalnom_2",
"nigiri3": "rtdd:nigirisushisalnom_3",
"pile2": "rtdd:sushipile_4",
"pile3": "rtdd:sushipile_5",
"addTip": "§a加上鲑鱼",
"rollTip": "§a鲑鱼寿司卷制中…",
"cutTip": "§a切出了鲑鱼寿司块(三个)",
"nigiriTip": "§a捏成手握鲑鱼寿司",
"pileTip": "§a堆成寿司堆",
"takeTip": "§a取走一个",
},
{
"key": "fish",
"fish": FISH_TROPICAL,
"roll": ["rtdd:fishsushi_1", "rtdd:fishsushi_2",
"rtdd:fishsushi_3", "rtdd:fishsushi_4", "rtdd:fishsushi_5"],
"cubes": "rtdd:fishmonsushi_6",
"nigiri1": "rtdd:nigirisushifish_1",
"nigiri2": "rtdd:nigirisushifish_2",
"nigiri3": "rtdd:nigirisushifish_3",
"pile2": "rtdd:sushipile_0",
"pile3": "rtdd:sushipile_1",
"addTip": "§a加上热带鱼",
"rollTip": "§a热带鱼寿司卷制中…",
"cutTip": "§a切出了热带鱼寿司块(三个)",
"nigiriTip": "§a捏成手握热带鱼寿司",
"pileTip": "§a堆成寿司堆",
"takeTip": "§a取走一个",
},
{
"key": "squid",
"fish": FISH_SQUID,
"roll": None, "cubes": None, "pile2": None, "pile3": None,
"nigiri1": "rtdd:squid_sushi",
"nigiri2": "rtdd:squidsushi_2",
"nigiri3": "rtdd:squidsushi_3",
"addTip": None, "rollTip": None, "cutTip": None,
"nigiriTip": "§a捏成手握鱿鱼寿司",
"pileTip": None, "takeTip": None,
},
]
# 干海带铺饭团消耗数(基础组)
NORI_RICE_CONSUME = 4
# 切块需要菜刀右击次数
CUT_COUNT = 2
# 手握堆叠/寿司堆 每次消耗手持数
STACK_CONSUME = 1
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
@@ -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 -*-