乡野食材 功能实现 1

This commit is contained in:
wenhe committed 2026-09-08 15:43:09 +08:00
1 parent b947847def
commit 098290c6b6
21 files changed
+697 -1583

No files matched your search

@@ -16,6 +16,7 @@ from xiangyemod.chuju.service.ChujuService import ChujuService
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
if not serverApi.IsInServer():
pass
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
"""
================================================================================
乡野食材(xy_ingredients)·切配与裹粉 规则数据
================================================================================
统一管理"食材切配"(菜刀切分 / 蒜末) 与 "裹粉半成品" 的规则常量。
只被 ShicaiService 读取,FeatureDispatcher 通过方块名 O(1) 路由到服务。
【一、菜刀切分】(CHOP_CHAINS)
手持 菜刀(rtdd:cleaver) 右键"摆放地上"的切配方块, 逐级切分;
切到最后一级再切 -> 掉落对应"碎"(数量见 drop) 并把方块变空气。
【二、蒜末】(GARLIC)
手持 菜刀 右键 大蒜(rtdd:cutgarlic) 三次 -> 掉落 16 个蒜末并消失。
【三、裹粉】(COATING_RULES)
手持生食材右键 面粉块(rtdd:farineblock_3) -> 消耗若干生食材 + 面粉块(变空气),
掉落对应的裹粉半成品物品(供油锅炸物)。
================================================================================
"""
# 菜刀
CLEAVER_ITEM = "rtdd:cleaver"
# 切分链: blocks 为逐级方块序列, 切到最后一级继续切 -> 掉 drop 并变空气
CHOP_CHAINS = [
{
"blocks": [
"rtdd:cutcarrot_0", "rtdd:cutcarrot_1", "rtdd:cutcarrot_2",
"rtdd:cutcarrot_3", "rtdd:cutcarrot_4",
],
"drop": {"name": "rtdd:crushed_carrots", "count": 3},
"tip": "§a切出了胡萝卜碎",
},
{
"blocks": [
"rtdd:cutpotato_0", "rtdd:cutpotato_1", "rtdd:cutpotato_2",
"rtdd:cutpotato_3", "rtdd:cutpotato_4",
],
"drop": {"name": "rtdd:potato_wedges", "count": 3},
"tip": "§a切出了土豆块",
},
{
"blocks": [
"rtdd:cutvegetable_0", "rtdd:cutvegetable_1",
"rtdd:cutvegetable_2", "rtdd:cutvegetable_3",
],
"drop": {"name": "rtdd:vegetable", "count": 3},
"tip": "§a切出了青菜碎",
},
{
"blocks": [
"rtdd:cutscallions_1", "rtdd:cutscallions_2",
"rtdd:cutscallions_3", "rtdd:cutscallions_4",
],
"drop": {"name": "rtdd:crushed_scallions", "count": 3},
"tip": "§a切出了葱花碎",
},
]
# ---- 蒜末: 大蒜 + 菜刀右键 GARLIC_CHOP_COUNT 次 -> 掉落 GARLIC_DROP ----
GARLIC_BLOCK = "rtdd:cutgarlic"
GARLIC_CHOP_COUNT = 3
GARLIC_DROP = {"name": "rtdd:crushedgarlic", "count": 16}
# ---- 裹粉: 面粉块 + 生食材 -> 掉落裹粉半成品 ----
FLOUR_BLOCK = "rtdd:farineblock_3"
COATING_RULES = [
{"item": "rtdd:sashimi", "count": 6,
"drop": {"name": "rtdd:oilfish_0", "count": 1}},
{"item": "rtdd:lobster", "count": 2,
"drop": {"name": "rtdd:oiltempura", "count": 1}},
{"item": "minecraft:porkchop", "count": 1,
"drop": {"name": "rtdd:oilpork", "count": 1}},
{"item": "minecraft:chicken", "count": 1,
"drop": {"name": "rtdd:oil_chickennuggets", "count": 6}},
{"item": "minecraft:potato", "count": 1,
"drop": {"name": "rtdd:oilfrenchfries", "count": 10}},
]
# ---- 左键破坏掉落: 摆在地上的切配方块 -> 原版食材(相对"切配态"的原版不切物品) ----
DESTROY_DROP = {
"rtdd:cutcarrot_0": "minecraft:carrot",
"rtdd:cutcarrot_1": "minecraft:carrot",
"rtdd:cutcarrot_2": "minecraft:carrot",
"rtdd:cutcarrot_3": "minecraft:carrot",
"rtdd:cutcarrot_4": "minecraft:carrot",
"rtdd:cutpotato_0": "minecraft:potato",
"rtdd:cutpotato_1": "minecraft:potato",
"rtdd:cutpotato_2": "minecraft:potato",
"rtdd:cutpotato_3": "minecraft:potato",
"rtdd:cutpotato_4": "minecraft:potato",
"rtdd:cutvegetable_0": "rtdd:qingcai",
"rtdd:cutvegetable_1": "rtdd:qingcai",
"rtdd:cutvegetable_2": "rtdd:qingcai",
"rtdd:cutvegetable_3": "rtdd:qingcai",
"rtdd:cutscallions_1": "rtdd:scallions",
"rtdd:cutscallions_2": "rtdd:scallions",
"rtdd:cutscallions_3": "rtdd:scallions",
"rtdd:cutscallions_4": "rtdd:scallions",
}
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
@@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
"""
================================================================================
乡野食材·切配与裹粉 右键转换引擎(服务端)
================================================================================
处理"乡野食材"(xy_ingredients) 分类下的右键转换:
菜刀切分配(胡萝卜/土豆/青菜/葱花 逐级切 + 切碎掉落)、
蒜末(大蒜+菜刀右键三次)、裹粉半成品(生食材+面粉块)。
【事件监听在哪?】
本类【不】监听任何事件!
所有事件由 FeatureDispatcher 统一监听,按 blockOwner 路由到本类 handleBlockUse。
【本类怎么做】
handleBlockUse ← 入口:分发器转发右键方块事件
├─ _handleChop ← 菜刀切分(切配方块逐级推进 / 最后一刀掉碎)
├─ _handleGarlic ← 蒜末(菜刀右键大蒜计数)
└─ _handleCoating ← 裹粉(生食材右键面粉块)
(通用工具) getCarriedItem / consumeItem / dropItems / replaceBlock /
clearBlock / 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 ..ShicaiData import (
CLEAVER_ITEM, CHOP_CHAINS, GARLIC_BLOCK, GARLIC_CHOP_COUNT,
GARLIC_DROP, FLOUR_BLOCK, COATING_RULES, DESTROY_DROP,
)
# ---- 统一分发器 ----
from ...service.FeatureDispatcher import FeatureDispatcher
class ShicaiService(BlueFoxServerSystem):
"""
乡野食材·切配与裹粉 右键转换引擎
"""
def __init__(self, namespace, systemName):
super(ShicaiService, self).__init__(namespace, systemName)
print "===== ShicaiService init ====="
# ---- 切分链映射: 方块名 -> (链, 索引) O(1) ----
self.chopMap = {}
self.featureBlocks = set()
for chain in CHOP_CHAINS:
for i, b in enumerate(chain["blocks"]):
self.chopMap[b] = (chain, i)
self.featureBlocks.add(b)
# ---- 蒜末方块 + 面粉块 ----
self.featureBlocks.add(GARLIC_BLOCK)
self.featureBlocks.add(FLOUR_BLOCK)
# ---- 裹粉规则映射: 手持物品名 -> 规则 ----
self.coatingMap = {}
for r in COATING_RULES:
self.coatingMap[r["item"]] = r
# ---- 蒜末切菜计数 {(dim,x,y,z): n} ----
self.garlicChop = {}
# ---- 右键冷却表 ----
self.cooldown = {}
# ---- 注册到统一分发器 ----
FeatureDispatcher.register(self)
# ============================================================================
# 一、给 FeatureDispatcher 的接口
# ============================================================================
def getFeatureBlocks(self):
"""返回本功能管辖的所有右键方块名集合"""
return self.featureBlocks
def getItemUseOnRules(self):
"""手持物品右键方块表面不需要单独路由, 返回空"""
return {}
def handleDestroy(self, args):
"""由 FeatureDispatcher 转发的"玩家左键破坏方块"入口。
摆在地上的切配方块(切胡萝卜/切土豆/切青菜/葱花) 被左键打碎 ->
掉落对应的原版食材(胡萝卜/土豆/青菜/葱), 并清空方块。
返回 True=处理了; False=不属本功能管。"""
blockName = args.get("fullName") or args.get("blockName")
itemName = DESTROY_DROP.get(blockName)
if not itemName:
return False
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
# 取消默认破坏+默认掉落(不让切配方块本身掉出来)
args['cancel'] = True
args['spawnResources'] = False
# 清空方块为空气(aux 强制 0, 保证定向变体正常清除)
try:
comp.CreateBlockInfo(LevelId).SetBlockNew(
(x, y, z), blockDict={"name": "minecraft:air", "aux": 0},
oldBlockHandling=0, dimensionId=dim)
except Exception:
pass
# 掉落原版食材
try:
self.CreateEngineItemEntity(
{"itemName": itemName, "count": 1, "auxValue": 0},
dim, (x, y + 0.5, z))
except Exception:
pass
return 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.chopMap:
return self._handleChop(blockName, playerId, pos, dim, itemDict, itemName, key)
# ---- 蒜末 ----
if blockName == GARLIC_BLOCK:
return self._handleGarlic(playerId, pos, dim, itemDict, itemName, key)
# ---- 裹粉 ----
if blockName == FLOUR_BLOCK:
return self._handleCoating(playerId, pos, dim, itemDict, itemName, key)
return False
# ============================================================================
# 二、菜刀切分配
# ============================================================================
def _handleChop(self, blockName, playerId, pos, dim, itemDict, itemName, key):
if itemName != CLEAVER_ITEM:
return False
chain, idx = self.chopMap[blockName]
blocks = chain["blocks"]
if not self.checkCooldown(key, 0.3):
return True
if idx < len(blocks) - 1:
# 逐级推进到下一刀
self.replaceBlock(pos, dim, blocks[idx + 1], playerId)
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a继续切配…")
else:
# 最后一级再切 -> 掉碎 + 变空气
self.clearBlock(pos, dim)
self.dropItems(dim, pos, [chain["drop"]])
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, chain.get("tip", "§a切好了"))
return True
# ============================================================================
# 三、蒜末(大蒜 + 菜刀右键三次 -> 掉 16 蒜末)
# ============================================================================
def _handleGarlic(self, playerId, pos, dim, itemDict, itemName, key):
if itemName != CLEAVER_ITEM:
return False
if not self.checkCooldown(key, 0.3):
return True
gkey = (dim, pos[0], pos[1], pos[2])
n = self.garlicChop.get(gkey, 0) + 1
if n >= GARLIC_CHOP_COUNT:
self.clearBlock(pos, dim)
self.dropItems(dim, pos, [GARLIC_DROP])
self.garlicChop.pop(gkey, None)
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a切好了蒜末")
else:
self.garlicChop[gkey] = n
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "§a切大蒜中…(%d/%d)" % (n, GARLIC_CHOP_COUNT))
return True
# ============================================================================
# 四、裹粉(生食材右键面粉块 -> 掉裹粉物品)
# ============================================================================
def _handleCoating(self, playerId, pos, dim, itemDict, itemName, key):
rule = self.coatingMap.get(itemName)
if not rule:
return False
need = rule.get("count", 1)
if itemDict is not None and (itemDict.get("count") or 0) < need:
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "§c数量不足,需要 %d 个" % need)
return True
if not self.checkCooldown(key, 0.5):
return True
self.consumeItem(playerId, itemDict, need)
self.clearBlock(pos, dim)
self.dropItems(dim, pos, [rule["drop"]])
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a裹粉完成")
return True
# ============================================================================
# 五、工具方法:获取玩家主手物品
# ============================================================================
def getCarriedItem(self, playerId):
itemComp = comp.CreateItem(playerId)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0, True)
# ============================================================================
# 六、工具方法:消耗物品
# ============================================================================
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 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 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):
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 -*-
@@ -53,6 +53,31 @@ ITEM_USE_ON_RULES = [
"consume": 1,
"tip": "§a已放置死鳕鱼",
},
# ---- 蔬菜·切配: 原版食材右键放置 -> 摆放地上的切配方块 ----
{
"item": "minecraft:carrot", # 原版胡萝卜
"target": "rtdd:cutcarrot_0", # 切胡萝卜(摆放地上)
"consume": 1,
"tip": "§a已放置胡萝卜,可手持菜刀切配",
},
{
"item": "minecraft:potato", # 原版土豆
"target": "rtdd:cutpotato_0", # 切土豆(摆放地上)
"consume": 1,
"tip": "§a已放置土豆,可手持菜刀切配",
},
{
"item": "rtdd:qingcai", # 青菜(整颗)
"target": "rtdd:cutvegetable_0", # 切青菜(摆放地上)
"consume": 1,
"tip": "§a已放置青菜,可手持菜刀切配",
},
{
"item": "rtdd:scallions", # 葱
"target": "rtdd:cutscallions_1", # 葱花_1
"consume": 1,
"tip": "§a已放置葱,可手持菜刀切分",
},
]
# ---- 二、盘子转换: 手持原版食物右键 空盘子(plate_0), 盘子变为对应成品盘 ----