家具实现

This commit is contained in:
wenhe committed 2026-09-03 15:23:33 +08:00
1 parent cae9bcb19e
commit 1413a3abc6
19 files changed
+1076 -9480

No files matched your search

@@ -1,6 +1,4 @@
from BlueFoxTools.ModLoader import * from BlueFoxTools.ModLoader import *
from xiangyemod.service.CookingService import CookingService
from xiangyemod.service.BlockSwitchService import BlockSwitchService
from xiangyemod.service.FeatureDispatcher import FeatureDispatcher from xiangyemod.service.FeatureDispatcher import FeatureDispatcher
from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService
from xiangyemod.dongwuyangzhi.service.AnimalTimerService import AnimalTimerService from xiangyemod.dongwuyangzhi.service.AnimalTimerService import AnimalTimerService
@@ -11,6 +9,7 @@ from xiangyemod.siwangshengwu.service.DeadmobTimerService import DeadmobTimerSer
from xiangyemod.shouban.service.ShoubanService import ShoubanService from xiangyemod.shouban.service.ShoubanService import ShoubanService
from xiangyemod.shichangmaoyi.service.MarketService import MarketService from xiangyemod.shichangmaoyi.service.MarketService import MarketService
from xiangyemod.yuanbanfangzhi.service.YuanbanService import YuanbanService from xiangyemod.yuanbanfangzhi.service.YuanbanService import YuanbanService
from xiangyemod.jiaju.service.FurnitureService import FurnitureService
if not serverApi.IsInServer(): if not serverApi.IsInServer():
pass pass
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""
烹饪客户端(预留)
=================
目前客户端不需要做额外的UI工作。
如果以后需要进度条、翻炒提示等UI,可以在这里扩展。
"""
import mod.client.extraClientApi as clientApi
ClientSystem = clientApi.GetClientSystemCls()
class CookingClient(ClientSystem):
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
print "===== CookingClient init ====="
def Destroy(self):
print "===== CookingClient Destroy ====="
@@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
"""
================================================================================
家具家私分类转换规则数据
================================================================================
统一管理"家具家私"(xy_furniture) 分类下的全部【右键切换型】转换规则:
箭桶、水槽、盘子架、书柜、厨具架、古式唱片机、垃圾桶。
全部为即时右键转换, 无计时, 不需要 TimerService。
由 xiangyemod/jiaju/service/FurnitureService 处理, 注册到 FeatureDispatcher。
【规则字段说明】
source: 被右键的源方块
item: 手持物品名; None=空手; "*music_disc"=任意原版唱片;
"*arrow"=任意箭; "*any"=任意非空物品
target: 转换成的目标方块
consume: 消耗数量(默认0, 即不消耗)
clearHand: True=清空主手全部物品(垃圾桶扔垃圾)
giveItem: 取回/取水时给玩家背包的物品名(直接进背包)
dropItem: 以掉落物形式给的物品 [{"name": "xx", "count": N}]
record: "count"=记录一种物品+数量(箭桶) / "stack"=后放先取堆栈(书柜),
记录写入方块实体数据(rtdd_swt/rtdd_swc/rtdd_swi/rtdd_swa/rtdd_sws),
退出重进不丢
maxCount: record 类型最大容量(箭桶32 / 书柜13)
arrowMode:"store"=箭桶存箭(批量) / "retrieve"=箭桶取箭(一次性全取)
discPut: True=唱片机放入时把唱片写入方块实体(取回时原样返还)
discGet: True=唱片机取回时从方块实体读出唱片并掉落
tip: 转换成功提示
================================================================================
"""
# ============================================================
# 一、箭桶 (arrowbarrel_0 无箭 / arrowbarrel_1 有箭)
# 存箭: 手持箭(普通箭/药水箭)右键, 批量存入手中所有箭, 至多32只,
# 只存一种箭(类型+药水效果aux不符不能混存), count记录
# 取箭: 空手右键, 一次性取回方块记录的全部箭
# ============================================================
ARROWBARREL_RULES = [
{"source": "rtdd:arrowbarrel_0", "item": "*arrow",
"target": "rtdd:arrowbarrel_1", "arrowMode": "store",
"record": "count", "maxCount": 32,
"tip": "§a已存入箭"},
{"source": "rtdd:arrowbarrel_1", "item": None,
"target": "rtdd:arrowbarrel_0", "arrowMode": "retrieve",
"record": "count",
"tip": "§a取回了箭"},
]
# ============================================================
# 二、水槽 (flume_0~5)
# 加水: 空手右键空槽 -> 满水
# 取水: 手持空水桶右键满水 -> 空槽 + 给装满水的水桶
# 放脏盘子(脏盘子 rtdd:plate_0a): 满水起, 逐个放入最多4个
# 取脏盘子: 空手右键逐个取回
# ============================================================
FLUME_RULES = [
# 加水/取水
{"source": "rtdd:flume_0", "item": None,
"target": "rtdd:flume_1", "tip": "§a水槽加满了水"},
{"source": "rtdd:flume_1", "item": "minecraft:bucket",
"target": "rtdd:flume_0", "consume": 1,
"giveItem": "minecraft:water_bucket", "tip": "§a取走了水, 水桶装满水"},
# 放脏盘子(需满水)
{"source": "rtdd:flume_1", "item": "rtdd:plate_0a",
"target": "rtdd:flume_2", "consume": 1, "tip": "§a放入1个脏盘子"},
{"source": "rtdd:flume_2", "item": "rtdd:plate_0a",
"target": "rtdd:flume_3", "consume": 1, "tip": "§a放入1个脏盘子"},
{"source": "rtdd:flume_3", "item": "rtdd:plate_0a",
"target": "rtdd:flume_4", "consume": 1, "tip": "§a放入1个脏盘子"},
{"source": "rtdd:flume_4", "item": "rtdd:plate_0a",
"target": "rtdd:flume_5", "consume": 1, "tip": "§a放入1个脏盘子"},
# 取脏盘子
{"source": "rtdd:flume_5", "item": None,
"target": "rtdd:flume_4", "giveItem": "rtdd:plate_0a", "tip": "§a取回1个脏盘子"},
{"source": "rtdd:flume_4", "item": None,
"target": "rtdd:flume_3", "giveItem": "rtdd:plate_0a", "tip": "§a取回1个脏盘子"},
{"source": "rtdd:flume_3", "item": None,
"target": "rtdd:flume_2", "giveItem": "rtdd:plate_0a", "tip": "§a取回1个脏盘子"},
{"source": "rtdd:flume_2", "item": None,
"target": "rtdd:flume_1", "giveItem": "rtdd:plate_0a", "tip": "§a取回1个脏盘子"},
]
# ============================================================
# 三、盘子架 (trayracks_1 空 ~ trayracks_6 五个)
# 放盘子(盘子 rtdd:plate_0): 逐个放入最多5个
# 取盘子: 空手右键逐个取回
# ============================================================
TRAYRACKS_RULES = [
{"source": "rtdd:trayracks_1", "item": "rtdd:plate_0",
"target": "rtdd:trayracks_2", "consume": 1, "tip": "§a放上1个盘子"},
{"source": "rtdd:trayracks_2", "item": "rtdd:plate_0",
"target": "rtdd:trayracks_3", "consume": 1, "tip": "§a放上1个盘子"},
{"source": "rtdd:trayracks_3", "item": "rtdd:plate_0",
"target": "rtdd:trayracks_4", "consume": 1, "tip": "§a放上1个盘子"},
{"source": "rtdd:trayracks_4", "item": "rtdd:plate_0",
"target": "rtdd:trayracks_5", "consume": 1, "tip": "§a放上1个盘子"},
{"source": "rtdd:trayracks_5", "item": "rtdd:plate_0",
"target": "rtdd:trayracks_6", "consume": 1, "tip": "§a放上1个盘子"},
{"source": "rtdd:trayracks_6", "item": None,
"target": "rtdd:trayracks_5", "giveItem": "rtdd:plate_0", "tip": "§a取回1个盘子"},
{"source": "rtdd:trayracks_5", "item": None,
"target": "rtdd:trayracks_4", "giveItem": "rtdd:plate_0", "tip": "§a取回1个盘子"},
{"source": "rtdd:trayracks_4", "item": None,
"target": "rtdd:trayracks_3", "giveItem": "rtdd:plate_0", "tip": "§a取回1个盘子"},
{"source": "rtdd:trayracks_3", "item": None,
"target": "rtdd:trayracks_2", "giveItem": "rtdd:plate_0", "tip": "§a取回1个盘子"},
{"source": "rtdd:trayracks_2", "item": None,
"target": "rtdd:trayracks_1", "giveItem": "rtdd:plate_0", "tip": "§a取回1个盘子"},
]
# ============================================================
# 四、书柜 (bookcase 系列, 后放先取, 至多13本)
# 放书(书 minecraft:book / 附魔书 minecraft:enchanted_book): 逐个放入,
# stack记录完整物品信息(附魔书附魔不丢)
# 取书: 空手右键, 后放先取(LIFO)
# 方块链: 书柜_1(空)=bookcase, 书柜_2(1本)=bookcase_12, 书柜_3(2本)=bookcase_11,
# 书柜_4(3本)=bookcase_10, 书柜_5(4本)=bookcase_9, 书柜_6(5本)=bookcase_8,
# 书柜_7(6本)=bookcase_7, 书柜_8(7本)=bookcase_6, 书柜_9(8本)=bookcase_5,
# 书柜_10(9本)=bookcase_4, 书柜_11(10本)=b_196, 书柜_12(11本)=bookcase_3,
# 书柜_13(12本)=bookcase_2, 书柜_14(13本)=bookcase_1
# ============================================================
_BOOKCASE_CHAIN = [
"rtdd:bookcase", # 书柜_1(空)
"rtdd:bookcase_12", # 书柜_2(1本)
"rtdd:bookcase_11", # 书柜_3(2本)
"rtdd:bookcase_10", # 书柜_4(3本)
"rtdd:bookcase_9", # 书柜_5(4本)
"rtdd:bookcase_8", # 书柜_6(5本)
"rtdd:bookcase_7", # 书柜_7(6本)
"rtdd:bookcase_6", # 书柜_8(7本)
"rtdd:bookcase_5", # 书柜_9(8本)
"rtdd:bookcase_4", # 书柜_10(9本)
"rtdd:b_196", # 书柜_11(10本)
"rtdd:bookcase_3", # 书柜_12(11本)
"rtdd:bookcase_2", # 书柜_13(12本)
"rtdd:bookcase_1", # 书柜_14(13本)
]
BOOKCASE_RULES = []
# 放入: 第 i 级 -> 第 i+1 级, 书/附魔书, 消耗1, stack记录
for _i in range(len(_BOOKCASE_CHAIN) - 1):
_src, _dst = _BOOKCASE_CHAIN[_i], _BOOKCASE_CHAIN[_i + 1]
BOOKCASE_RULES.append({
"source": _src, "item": "minecraft:book", "target": _dst,
"consume": 1, "record": "stack", "maxCount": 13,
"tip": "§a放入1本书",
})
BOOKCASE_RULES.append({
"source": _src, "item": "minecraft:enchanted_book", "target": _dst,
"consume": 1, "record": "stack", "maxCount": 13,
"tip": "§a放入1本附魔书",
})
# 取回: 第 i 级 -> 第 i-1 级, 空手, 后放先取
for _i in range(len(_BOOKCASE_CHAIN) - 1, 0, -1):
_src, _dst = _BOOKCASE_CHAIN[_i], _BOOKCASE_CHAIN[_i - 1]
BOOKCASE_RULES.append({
"source": _src, "item": None, "target": _dst,
"record": "stack", "tip": "§a取回了书",
})
# ============================================================
# 五、厨具架 (kitchenwarerack, 至多挂2件, 方块状态即组合, 纯切换)
# 可放: 锅铲(spatula) / 锅勺(potandspoon) / 菜刀(cleaver) / 炒锅(wok)
# ============================================================
KITCHENRACK_RULES = [
# ---- 放入 (空架 -> 单件) ----
{"source": "rtdd:kitchenwarerack", "item": "rtdd:spatula",
"target": "rtdd:kitchenwareracka_1", "consume": 1, "tip": "§a放上了锅铲"},
{"source": "rtdd:kitchenwarerack", "item": "rtdd:potandspoon",
"target": "rtdd:kitchenwarerackb_1", "consume": 1, "tip": "§a放上了锅勺"},
{"source": "rtdd:kitchenwarerack", "item": "rtdd:cleaver",
"target": "rtdd:kitchenwarerackd_1", "consume": 1, "tip": "§a放上了菜刀"},
{"source": "rtdd:kitchenwarerack", "item": "rtdd:wok",
"target": "rtdd:kitchenwarerackc_1", "consume": 1, "tip": "§a放上了炒锅"},
# ---- 放入 (单件 -> 双件/组合) ----
{"source": "rtdd:kitchenwareracka_1", "item": "rtdd:spatula",
"target": "rtdd:kitchenwareracka_2", "consume": 1, "tip": "§a再放上一把锅铲"},
{"source": "rtdd:kitchenwareracka_1", "item": "rtdd:potandspoon",
"target": "rtdd:kitchenwareracka_3", "consume": 1, "tip": "§a放上了锅勺"},
{"source": "rtdd:kitchenwarerackb_1", "item": "rtdd:potandspoon",
"target": "rtdd:kitchenwarerackb_2", "consume": 1, "tip": "§a再放上一把锅勺"},
{"source": "rtdd:kitchenwarerackc_1", "item": "rtdd:cleaver",
"target": "rtdd:kitchenwarerackc_2", "consume": 1, "tip": "§a放上了菜刀"},
{"source": "rtdd:kitchenwarerackd_1", "item": "rtdd:cleaver",
"target": "rtdd:kitchenwarerackd_3", "consume": 1, "tip": "§a再放上一把菜刀"},
{"source": "rtdd:kitchenwarerackd_1", "item": "rtdd:spatula",
"target": "rtdd:kitchenwarerackd_2", "consume": 1, "tip": "§a放上了锅铲"},
{"source": "rtdd:kitchenwarerackd_1", "item": "rtdd:potandspoon",
"target": "rtdd:kitchenwarerackd_4", "consume": 1, "tip": "§a放上了锅勺"},
# ---- 取回 (空手右键, 掉落对应厨具) ----
{"source": "rtdd:kitchenwareracka_1", "item": None,
"target": "rtdd:kitchenwarerack",
"dropItem": [{"name": "rtdd:spatula", "count": 1}], "tip": "§a取回了锅铲"},
{"source": "rtdd:kitchenwareracka_2", "item": None,
"target": "rtdd:kitchenwareracka_1",
"dropItem": [{"name": "rtdd:spatula", "count": 1}], "tip": "§a取回了一把锅铲"},
{"source": "rtdd:kitchenwareracka_3", "item": None,
"target": "rtdd:kitchenwareracka_1",
"dropItem": [{"name": "rtdd:potandspoon", "count": 1}], "tip": "§a取回了锅勺"},
{"source": "rtdd:kitchenwarerackb_1", "item": None,
"target": "rtdd:kitchenwarerack",
"dropItem": [{"name": "rtdd:potandspoon", "count": 1}], "tip": "§a取回了锅勺"},
{"source": "rtdd:kitchenwarerackb_2", "item": None,
"target": "rtdd:kitchenwarerackb_1",
"dropItem": [{"name": "rtdd:potandspoon", "count": 1}], "tip": "§a取回了一把锅勺"},
{"source": "rtdd:kitchenwarerackc_1", "item": None,
"target": "rtdd:kitchenwarerack",
"dropItem": [{"name": "rtdd:wok", "count": 1}], "tip": "§a取回了炒锅"},
{"source": "rtdd:kitchenwarerackc_2", "item": None,
"target": "rtdd:kitchenwarerackc_1",
"dropItem": [{"name": "rtdd:cleaver", "count": 1}], "tip": "§a取回了菜刀"},
{"source": "rtdd:kitchenwarerackd_1", "item": None,
"target": "rtdd:kitchenwarerack",
"dropItem": [{"name": "rtdd:cleaver", "count": 1}], "tip": "§a取回了菜刀"},
{"source": "rtdd:kitchenwarerackd_2", "item": None,
"target": "rtdd:kitchenwarerackd_1",
"dropItem": [{"name": "rtdd:spatula", "count": 1}], "tip": "§a取回了锅铲"},
{"source": "rtdd:kitchenwarerackd_3", "item": None,
"target": "rtdd:kitchenwarerackd_1",
"dropItem": [{"name": "rtdd:cleaver", "count": 1}], "tip": "§a取回了一把菜刀"},
{"source": "rtdd:kitchenwarerackd_4", "item": None,
"target": "rtdd:kitchenwarerackd_1",
"dropItem": [{"name": "rtdd:potandspoon", "count": 1}], "tip": "§a取回了锅勺"},
]
# ============================================================
# 六、古式唱片机 (b_108 无碟 / b_109 有碟)
# 放入: 手持任意原版唱片 -> 有碟, 消耗1, 记录唱片(rtdd_disc)
# 取回: 空手右键 -> 无碟 + 原样返还唱片
# 注: 放入后播放曲目为 todo, 后续实现
# ============================================================
RECORDPLAYER_RULES = [
{"source": "rtdd:b_108", "item": "*music_disc",
"target": "rtdd:b_109", "consume": 1, "discPut": True,
"tip": "§a放入了唱片"},
{"source": "rtdd:b_109", "item": None,
"target": "rtdd:b_108", "discGet": True,
"tip": "§a取回了唱片"},
]
# ============================================================
# 七、垃圾桶 (storage_buckets 关闭 / rubbish_bin_2 打开 / b_160 有垃圾)
# 打开: 右键垃圾桶(空手/手持均可) -> 打开桶盖(不消耗)
# 扔垃圾: 打开时手持任意物品右键 -> 有垃圾 + 清空主手
# 关闭: 右键(空手/手持均可) 打开/有垃圾 -> 关闭
# ============================================================
TRASHBIN_RULES = [
{"source": "rtdd:storage_buckets", "item": None,
"target": "rtdd:rubbish_bin_2", "tip": "§a垃圾桶已打开"},
{"source": "rtdd:storage_buckets", "item": "*any",
"target": "rtdd:rubbish_bin_2", "tip": "§a垃圾桶已打开"},
{"source": "rtdd:rubbish_bin_2", "item": None,
"target": "rtdd:storage_buckets", "tip": "§a垃圾桶已关闭"},
{"source": "rtdd:rubbish_bin_2", "item": "*any",
"target": "rtdd:b_160", "clearHand": True, "tip": "§a已丢入垃圾桶"},
{"source": "rtdd:b_160", "item": None,
"target": "rtdd:storage_buckets", "tip": "§a垃圾桶已关闭"},
{"source": "rtdd:b_160", "item": "*any",
"target": "rtdd:storage_buckets", "tip": "§a垃圾桶已关闭"},
]
# ============================================================
# 汇总: 家具家私全部右键转换规则
# ============================================================
FURNITURE_SWITCHES = (ARROWBARREL_RULES + FLUME_RULES + TRAYRACKS_RULES
+ BOOKCASE_RULES + KITCHENRACK_RULES
+ RECORDPLAYER_RULES + TRASHBIN_RULES)
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
@@ -0,0 +1,603 @@
# -*- coding: utf-8 -*-
"""
================================================================================
家具家私·右键转换引擎(服务端)
================================================================================
处理"家具家私"(xy_furniture) 分类下的全部【右键切换型】转换:
箭桶(存/取箭)、水槽(加水/取水/放洗脏盘)、盘子架(放/取盘)、
书柜(放/取书,后放先取)、厨具架(放/取厨具)、古式唱片机(放/取唱片)、
垃圾桶(开/关/扔垃圾)。
全部为即时右键转换, 无计时, 因此只有一个服务类, 不需要拆分 TimerService。
【事件监听在哪?】
本类【不】监听任何事件!
所有事件由 FeatureDispatcher(xiangyemod/service/FeatureDispatcher.py)统一监听:
- ServerBlockUseEvent → OnBlockUse → 按 blockOwner 路由到本类 handleBlockUse
本类只实现被分发器调用的接口方法, 不关心事件来源。
【规则匹配】
findRecipe 支持五种:
1. 精确匹配 (方块名, 物品名) —— 水槽/盘子架/厨具架/书柜等
2. 唱片机 (方块名, "*music_disc") —— 手持任意原版唱片
3. 箭桶 (方块名, "*arrow") —— 手持任意箭(普通箭/药水箭)
4. 任意物品(方块名, "*any") —— 垃圾桶: 手持任意非空物品右键
5. 空手 (方块名, None) —— 取回/开关类, 只有真正空手才 fallback
【方块记录(存储物品)】
记录写入方块实体数据, 退出重进不丢(rtdd_swt 类型标记):
- count记录(箭桶): 记录一种箭的类型+数量, 字段 rtdd_swt=count /
rtdd_swc=数量 / rtdd_swi=物品名 / rtdd_swa=aux
- stack记录(书柜): 后放先取堆栈, 记录完整物品信息(含userData, 附魔不丢),
字段 rtdd_swt=stack / rtdd_sws=堆栈列表
唱片机单独用 rtdd_disc 记录唱片。SetBlockNew 后 block entity 可能未初始化,
写入带 0.1s 延迟兜底。
【本类怎么做(自上而下的方法系列)】
handleBlockUse ← 入口:分发器转发右键方块事件
├─ findRecipe ← 匹配规则
├─ checkCooldown← 连点冷却
├─ _handleArrowMode ← 箭桶批量存/取箭
└─ handleSwitch ← 其他切换: 读记录→消耗/清手→替换→记记录→给/掉物品→提示
================================================================================
"""
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
# ---- 方块实体数据组件(记录箭桶数量/书柜堆栈/唱片) ----
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
# ---- 游戏组件(AddTimer 延迟写入 block entity 兜底) ----
gameComp = comp.CreateGame(LevelId)
# ---- 只导入家具家私自己的数据库 ----
from ..FurnitureData import FURNITURE_SWITCHES
# ---- 统一分发器 ----
from ...service.FeatureDispatcher import FeatureDispatcher
class FurnitureService(BlueFoxServerSystem):
"""
家具家私·右键转换引擎
【核心数据结构】
furnitureRecipes = {(方块名, 物品名): 规则dict} # O(1) 查找
furnitureBlockSet = {方块名, ...} # O(1) 注册用
cooldown = {key: 时间戳} # 连点冷却表
"""
def __init__(self, namespace, systemName):
"""
初始化:构建规则字典,并注册到统一分发器。
"""
super(FurnitureService, self).__init__(namespace, systemName)
print "===== FurnitureService init ====="
# ---- 构建规则字典(O(1) 查找) ----
self.furnitureRecipes = {}
self.furnitureBlockSet = set()
for sw in FURNITURE_SWITCHES:
key = (sw["source"], sw.get("item"))
self.furnitureRecipes[key] = sw
self.furnitureBlockSet.add(sw["source"])
# ---- 右键冷却表 ----
self.cooldown = {}
# ---- 注册到统一分发器 ----
FeatureDispatcher.register(self)
# ============================================================================
# 一、给 FeatureDispatcher 的接口
# ============================================================================
def getFeatureBlocks(self):
"""返回本功能管辖的所有方块名集合(右键路由用)"""
return self.furnitureBlockSet
def getItemUseOnRules(self):
"""返回"手持物品右键方块表面"的规则(家具家私没有, 返回空)"""
return {}
def handleBlockUse(self, args):
"""
由 FeatureDispatcher 转发的右键方块入口。
只有 blockName 在本功能的方块集合里才会被调用。
返回 True = 本功能处理了这个右键(分发器取消默认行为)
返回 False = 当前组合没有匹配规则(分发器不拦截)
"""
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.findRecipe(blockName, itemDict)
if not recipe:
return False
# 连点冷却
if not self.checkCooldown(key, recipe.get("switchCd", 0.5)):
return True
# 箭桶模式:批量存/取箭, 单独处理
if recipe.get("arrowMode"):
self._handleArrowMode(recipe, playerId, pos, dim, itemDict, key)
return True
# 其余切换型规则:就地执行
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key)
return True
# ============================================================================
# 二、规则匹配(O(1) 字典查找)
# ============================================================================
def findRecipe(self, blockName, itemDict):
"""
匹配规则, 优先级从高到低:
1. 精确匹配 (方块名, 物品名)
2. 唱片机 (方块名, "*music_disc")
3. 箭桶 (方块名, "*arrow")
4. 任意物品 (方块名, "*any")
5. 空手 (方块名, None) —— 只有真正空手才 fallback
"""
itemName = itemDict.get("newItemName") if itemDict else None
# 1. 精确匹配
recipe = self.furnitureRecipes.get((blockName, itemName))
if recipe:
return recipe
# 2. 唱片机:手持任意原版唱片
if itemName and itemName.startswith("minecraft:music_disc"):
recipe = self.furnitureRecipes.get((blockName, "*music_disc"))
if recipe:
return recipe
# 3. 箭桶:手持任意箭(普通箭/药水箭)
if itemName in ("minecraft:arrow", "minecraft:tipped_arrow"):
recipe = self.furnitureRecipes.get((blockName, "*arrow"))
if recipe:
return recipe
# 4. 任意物品匹配(垃圾桶)
if itemName and itemName != "minecraft:air":
recipe = self.furnitureRecipes.get((blockName, "*any"))
if recipe:
return recipe
# 5. 空手匹配
if not itemName or itemName == "minecraft:air":
return self.furnitureRecipes.get((blockName, None))
return None
# ============================================================================
# 三、箭桶模式(批量存箭/一次性取箭, count记录)
# ============================================================================
def _handleArrowMode(self, recipe, playerId, pos, dim, itemDict, key):
"""
箭桶存/取箭:
存箭: 批量存入手中所有箭, 至多 maxCount(32), 只存一种箭
(方块内已有箭且与手中类型/药水效果不同则不能混存)
取箭: 一次性取回方块记录的全部箭(数量+类型+药水效果aux)
"""
mode = recipe.get("arrowMode")
if mode == "store":
# ---- 存箭 ----
if not itemDict:
return
handCount = itemDict.get("count", 0)
if handCount <= 0:
return
handItemName = itemDict.get("newItemName", "")
handAux = itemDict.get("newAuxValue", 0)
record = self._loadRecord(pos, dim)
# 类型检查: 已有箭且类型不符 -> 不能混存
if record and record.get("count", 0) > 0:
if (record.get("itemName") != handItemName
or record.get("aux", 0) != handAux):
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "§c箭桶里已有其他种类的箭, 不能混存!")
return
else:
record = {"count": 0, "itemName": handItemName, "aux": handAux}
# 计算实际可存数量
maxCount = recipe.get("maxCount", 32)
if record["count"] >= maxCount:
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "§c箭桶已满!")
return
storeCount = min(handCount, maxCount - record["count"])
# 从手中扣减 storeCount 只箭
self._consumeCount(playerId, itemDict, storeCount)
# 更新记录
record["count"] += storeCount
# 替换方块
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# 保存记录到新方块(延迟兜底)
self._saveRecordDelayed(pos, dim, record)
if recipe.get("tip"):
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "§a存入了 %d 只箭 (共%d/32)" % (storeCount, record["count"]))
elif mode == "retrieve":
# ---- 取箭 ----
record = self._loadRecord(pos, dim)
if not record or record.get("count", 0) <= 0:
return
retCount = record["count"]
retItemName = record["itemName"]
retAux = record.get("aux", 0)
# 给玩家箭
self.giveItem(playerId, retItemName, retCount, retAux)
# 替换方块
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# 清空记录
self._clearRecord(pos, dim)
if recipe.get("tip"):
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "§a取回了 %d 只箭" % retCount)
# ============================================================================
# 四、切换型规则处理(水槽/盘子架/书柜/厨具架/唱片机/垃圾桶)
# ============================================================================
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key):
"""
执行"右键直接转换"类规则:
书柜取回/唱片机取回 → 先读记录
消耗物品/清空主手 → 替换方块 → 记录放入 → 给/掉物品 → 提示
"""
# ---- 取回类: 先读记录(此时方块还是满的/有碟的) ----
discToDrop = None
if recipe.get("discGet"):
discToDrop = self._readDiscData(pos, dim)
self._clearDiscData(pos, dim)
if recipe.get("record") == "stack" and recipe.get("item") is None:
self._popStackAndGive(playerId, pos, dim)
# ---- 消耗物品 ----
if recipe.get("clearHand"):
self.clearHand(playerId)
elif recipe.get("item") is not None and recipe.get("consume", 0) > 0:
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
# ---- 替换方块 ----
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# ---- 放入类: 记录到新方块(延迟兜底) ----
if recipe.get("record") == "stack" and recipe.get("item") is not None and itemDict:
self._pushStack(pos, dim, itemDict)
if recipe.get("discPut") and itemDict:
self._writeDiscData(dim, pos, itemDict)
# ---- 掉落物(厨具架取回/唱片取回) ----
if discToDrop:
self._dropItemDict(dim, pos, discToDrop)
if recipe.get("dropItem"):
self.dropItems(dim, pos, recipe["dropItem"])
# ---- 给玩家背包物品(水槽取水/取脏盘子/盘子架取回) ----
if recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"])
# ---- 转换成功提示 ----
if recipe.get("tip"):
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["tip"])
# ============================================================================
# 五、方块记录:count/stack 读写(箭桶/书柜, 字段 rtdd_sw*)
# ============================================================================
def _loadRecord(self, pos, dim):
"""从方块实体数据读出记录: count型 -> {"count","itemName","aux"},
stack型 -> {"stack":[...]}; 无记录返回 None"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
return None
swt = blockData["rtdd_swt"]
if swt == "count":
return {"count": blockData["rtdd_swc"],
"itemName": blockData["rtdd_swi"],
"aux": blockData["rtdd_swa"]}
if swt == "stack":
return {"stack": blockData["rtdd_sws"]}
return None
except Exception:
return None
def _saveRecord(self, pos, dim, record):
"""把记录写入方块实体数据"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
return
if "count" in record:
blockData["rtdd_swt"] = "count"
blockData["rtdd_swc"] = record["count"]
blockData["rtdd_swi"] = record.get("itemName", "")
blockData["rtdd_swa"] = record.get("aux", 0)
elif "stack" in record:
blockData["rtdd_swt"] = "stack"
blockData["rtdd_sws"] = record["stack"]
except Exception:
pass
def _saveRecordDelayed(self, pos, dim, record):
"""写入记录, block entity 未初始化则 0.1s 延迟兜底"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
gameComp.AddTimer(0.1, self._saveRecord, pos, dim, record)
return
self._saveRecord(pos, dim, record)
except Exception:
gameComp.AddTimer(0.1, self._saveRecord, pos, dim, record)
def _clearRecord(self, pos, dim):
"""清除方块实体数据里的记录"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is not None:
blockData["rtdd_swt"] = ""
blockData["rtdd_swc"] = 0
blockData["rtdd_swi"] = ""
blockData["rtdd_swa"] = 0
blockData["rtdd_sws"] = []
except Exception:
pass
# ---- 书柜 stack 记录: 压栈/弹栈 ----
def _pushStack(self, pos, dim, itemDict):
"""书柜放入: 把书的完整信息压入堆栈(含userData, 附魔不丢)"""
record = self._loadRecord(pos, dim)
if not record:
record = {"stack": []}
record["stack"].append(self._toItemObj(itemDict))
self._saveRecordDelayed(pos, dim, record)
def _popStackAndGive(self, playerId, pos, dim):
"""书柜取回: 弹栈(后放先取)并把书给玩家"""
record = self._loadRecord(pos, dim)
if not record or not record.get("stack"):
return
topItem = record["stack"].pop()
self.giveItemObj(playerId, topItem)
if record["stack"]:
self._saveRecordDelayed(pos, dim, record)
else:
self._clearRecord(pos, dim)
def _toItemObj(self, itemDict):
"""从手持物品 dict 提取可持久化的物品对象"""
return {
"itemName": itemDict.get("newItemName", ""),
"count": itemDict.get("count", 1),
"aux": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {}),
}
# ============================================================================
# 六、唱片机唱片记录(rtdd_disc)
# ============================================================================
def _writeDiscData(self, dim, pos, itemDict):
"""把放入的唱片(物品名+aux)写入方块实体数据 rtdd_disc, 延迟兜底"""
discInfo = {
"itemName": itemDict.get("newItemName", "minecraft:music_disc_13"),
"aux": itemDict.get("newAuxValue", 0),
}
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
gameComp.AddTimer(0.1, self._writeDiscDataDelayed,
dim, pos, discInfo)
return
blockData["rtdd_disc"] = discInfo
except Exception:
gameComp.AddTimer(0.1, self._writeDiscDataDelayed,
dim, pos, discInfo)
def _writeDiscDataDelayed(self, dim, pos, discInfo):
"""延迟写入兜底"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is not None:
blockData["rtdd_disc"] = discInfo
except Exception:
pass
def _readDiscData(self, pos, dim):
"""从方块实体数据读出唱片信息, 没有返回 None"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
return None
# 注意: BlockEntityData 需用 [] 访问, 键不存在会抛异常, 捕获后按无记录处理
return blockData["rtdd_disc"]
except Exception:
return None
def _clearDiscData(self, pos, dim):
"""清除方块实体数据里的唱片记录"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is not None:
blockData["rtdd_disc"] = None
except Exception:
pass
def _dropItemDict(self, dim, pos, itemDict):
"""以掉落物形式生成一个物品(唱片取回)"""
try:
self.CreateEngineItemEntity(
{"itemName": itemDict["itemName"],
"count": 1,
"auxValue": itemDict.get("aux", 0)},
dim, (pos[0], pos[1] + 0.5, pos[2]))
except Exception:
pass
# ============================================================================
# 七、工具方法:获取玩家主手物品
# ============================================================================
def getCarriedItem(self, playerId):
itemComp = comp.CreateItem(playerId)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
# ============================================================================
# 八、工具方法:给玩家物品
# ============================================================================
def giveItem(self, playerId, itemName, count=1, aux=0, userData=None):
"""往玩家背包塞一个物品, 背包满则掉地上"""
try:
itemDict = {"itemName": itemName, "count": count, "auxValue": aux}
if userData:
itemDict["userData"] = userData
comp.CreateItem(playerId).SpawnItemToPlayerInv(itemDict, playerId)
except Exception:
pass
def giveItemObj(self, playerId, itemObj):
"""按持久化物品对象给玩家(书柜弹栈取回, 保留附魔)"""
try:
itemDict = {"itemName": itemObj["itemName"],
"count": itemObj.get("count", 1),
"auxValue": itemObj.get("aux", 0)}
if itemObj.get("userData"):
itemDict["userData"] = itemObj["userData"]
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)})
def _consumeCount(self, playerId, itemDict, amount):
"""批量扣减指定数量(箭桶存箭)"""
if not itemDict or amount <= 0:
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)})
def clearHand(self, playerId):
"""清空主手全部物品(垃圾桶扔垃圾)"""
comp.CreateItem(playerId).SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": "minecraft:air", "count": 0, "auxValue": 0})
# ============================================================================
# 十、工具方法:替换方块
# ============================================================================
def replaceBlock(self, pos, dim, newBlockName, playerId=None):
"""
替换指定位置的方块。
playerId 可为 None, 用 LevelId 创建组件。
"""
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 dropItems(self, dim, pos, dropList):
"""
dropList 格式:[{"name": "xxx", "count": N}, ...]
在方块上方生成掉落物实体, 让它自然掉到地上。
"""
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):
"""
右键冷却:同一方块 cd 秒内只响应一次, 防止连点刷物品/刷提示。
"""
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 -*-
@@ -1,764 +0,0 @@
# -*- coding: utf-8 -*-
"""
================================================================================
工具方块切换引擎(服务端)
================================================================================
这个文件负责所有"普通工具方块"的右键转换逻辑,和做菜(CookingService)彻底分开。
【一句话概括】
玩家右键工具方块(箭桶、挂肉钩、水槽、盘子架、药水架、书柜……) →
查 BlockSwitchData 有没有匹配的规则 → 消耗物品 → 替换方块 → 保存记录
【为什么单独拆一个类】
原来做菜和工具切换都在 CookingService 里,右键时每次都要同时碰到两套数据,
内存和逻辑都搅在一起。现在拆成两个系统:
- CookingService → 只处理做菜配方(RECIPES)
- BlockSwitchService → 只处理工具方块切换(BLOCK_SWITCHES)
两个系统都监听同一个 ServerBlockUseEvent,
但各自在事件第一行用 switchBlockSet/cookBlockSet(O(1) 的 set)按方块名拦截:
不是自己的方块直接 return,互不干扰,各自只查自己那一份数据。
【事件监听】
- ServerBlockUseEvent → 玩家右键方块(工具切换主入口)
- ServerItemTryUseEvent → 玩家尝试使用物品(拦截喝药水等默认行为)
【保存/持久化】
箭桶、药水架、书柜这类有 record 记录的方块,把放进来的物品记录在
方块实体数据里(字段 rtdd_swt/rtdd_swc/rtdd_swi/rtdd_sws),
这样玩家退出游戏再重进,loadSwitchRecord 还能从方块里把物品读回来。
================================================================================
"""
import time
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
comp = serverApi.GetEngineCompFactory() # 组件工厂,用来创建各种功能组件
# ---- 继承 BlueFox 框架的系统基类 ----
# BlueFoxServerSystem 会自动处理 LoadMod / UnloadMod 等生命周期,
# 不需要手动写,继承后框架自动发现并注册(ModLoader 的 GetLastSubclass)
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
from ...BlueFoxTools.ToolsConfig import LevelId
# ---- 方块实体数据组件 ----
# GetBlockEntityData 返回的 BlockEntityData 对象支持 [] 操作,直接赋值即可持久化。
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
# ---- 游戏组件(用于 AddTimer 延迟写入 block entity) ----#
gameComp = comp.CreateGame(LevelId)
# ---- 导入工具方块切换规则 ----
# BLOCK_SWITCHES 是一个列表,每条规则是一个 dict,格式见 BlockSwitchData.py 顶部注释
from ..BlockSwitchData import BLOCK_SWITCHES
# ---- 已由 DeadmobService/DeadmobTimerService 接管的死亡生物方块 ----
# 这些方块属于"死亡生物"(xy_deadmob) 分类,由新系统处理(挂肉钩切肉/鱼晾干/死鱼切块),
# 工具切换系统必须跳过,否则右键会触发旧规则(含逆向退回),和新系统双重响应产生冲突。
_DEADMOB_OWNED = set([
"rtdd:meathook",
"rtdd:cow_0", "rtdd:cow_1", "rtdd:cow_2", "rtdd:cow_3",
"rtdd:pig_0", "rtdd:pig_1", "rtdd:pig_2", "rtdd:pig_3",
"rtdd:sheep", "rtdd:sheep_1", "rtdd:sheep_2", "rtdd:sheep_3",
"rtdd:hang_meat_hooks_1", "rtdd:hang_meat_hooks_2",
"rtdd:hang_meat_hooks_3", "rtdd:hang_meat_hooks_4",
"rtdd:meathookcod_1", "rtdd:meathookcod_2", "rtdd:meathookcoddry",
"rtdd:killfish_0", "rtdd:killfish_1", "rtdd:killfish_2", "rtdd:killfish_3",
"rtdd:killcod_0", "rtdd:killcod_1", "rtdd:killcod_2",
"rtdd:deadsquida", "rtdd:dead_squid_1",
# 旧版挂鱼方块(minecraft:fish 挂肉钩),已被新挂鱼(鲑鱼/鳕鱼)取代
"rtdd:fish_1", "rtdd:fish_2",
])
# ================================================================================
# 工具函数:读取物品 userData 里的嵌套字段
# ================================================================================
def getUserDataField(itemDict, *keys):
"""
从物品 dict 的 userData 里一层层往下读。
中间某一层不存在会自动创建空 dict,不会报错。
"""
itemDict['userData'] = itemDict.get('userData', {})
if itemDict.get('userData') is None:
itemDict['userData'] = {}
node = itemDict['userData']
for key in keys:
node[key] = node.get(key, {})
node = node[key]
return node
# ================================================================================
# 工具方块切换引擎主类
# ================================================================================
class BlockSwitchService(BlueFoxServerSystem):
"""
通用工具方块切换引擎
【核心数据结构】
switchRecord = {
(维度ID, x, y, z): { # 用方块坐标当 key,每个方块独立
"count": 整数, # recordType="count" 时记录数量(如箭桶里有多少箭)
"itemName": 字符串, # recordType="count" 时记录物品名(如"minecraft:arrow")
"stack": [物品信息列表], # recordType="stack" 时记录后进先出的物品堆
}
}
这个数据同时存在两处:
1. 内存(self.switchRecord)—— 快,关服就没了
2. 方块实体数据(blockEntityData)—— 慢一点,但关服重启后还在
每次操作都会同步更新两处,保证重启不丢记录
"""
def __init__(self, namespace, systemName):
"""
初始化:清空记录数据、注册事件监听、构建工具切换规则字典
"""
super(BlockSwitchService, self).__init__(namespace, systemName)
print "===== BlockSwitchService init ====="
self.switchRecord = {} # 工具方块的记录数据(箭桶数量、药水架顺序等)
self.switchCooldown = {} # 工具方块切换冷却时间(防止连点)
self._cancelItemUse = {} # 将要取消下一次物品使用的玩家ID集合(防止喝药水等)
self.ListenEvent() # 注册游戏事件监听
# ---- 构建工具切换规则字典(O(1) 查找) ----
# key = (方块名, 物品名),value = 规则dict
self.switchRecipes = {}
for switch in BLOCK_SWITCHES:
key = (switch["source"], switch.get("item"))
self.switchRecipes[key] = switch
# ---- 构建工具方块名集合(O(1) 快速拦截,第一道过滤) ----
# 事件进来先判断方块名在不在这个集合里,不在直接 return,
# 这样做菜方块的右键事件根本不会进到本系统的字典查找
self.switchBlockSet = set()
for switch in BLOCK_SWITCHES:
self.switchBlockSet.add(switch["source"])
def Destroy(self):
"""
系统销毁时取消所有事件监听,防止内存泄漏
"""
print "===== BlockSwitchService Destroy ====="
self.UnListenEvent()
# ============================================================================
# 事件注册与注销
# ============================================================================
def ListenEvent(self):
"""
注册两个游戏事件:
1. ServerBlockUseEvent —— 玩家右键方块(工具切换入口)
2. ServerItemTryUseEvent —— 玩家尝试使用物品(拦截喝药水等默认行为)
"""
# 事件1:玩家右键方块
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
# 事件2:玩家尝试使用物品(拦截喝药水)
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemTryUseEvent", self, self.OnItemTryUse)
def UnListenEvent(self):
"""
注销事件监听,和 ListenEvent 一一对应
"""
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemTryUseEvent", self, self.OnItemTryUse)
# ============================================================================
# 主入口:玩家右键方块
# ============================================================================
def OnBlockUse(self, args):
"""
玩家右键方块时触发,是工具方块切换的入口。
流程:
1. 先按方块名判断是不是本系统管辖的工具方块(O(1) set 拦截)
- 不是 → 直接 return(这是做菜方块的右键,交给 CookingService 处理)
2. 拿到被右键的方块名、玩家ID、方块坐标
3. 看玩家手里拿了什么物品
4. 拿着"方块名+手里物品名"去工具切换字典里 O(1) 查找
5. 命中规则 → 执行工具切换逻辑
"""
# ---- 第一步:提取事件参数 ----
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) # 完整的唯一标识:维度+坐标
# ---- 第二步:O(1) 方块名拦截 ----
# 只有工具方块的右键才归本系统处理;做菜方块直接放过,交给 CookingService
if blockName in _DEADMOB_OWNED:
# 死亡生物方块由新系统(DeadmobService)处理,本系统跳过
return
if blockName not in self.switchBlockSet:
return
# ---- 第三步:拿到玩家手里拿的物品 ----
itemDict = self.getCarriedItem(playerId)
# ---- 第四步:查工具切换规则(O(1) 字典查找) ----
recipe = self.findRecipe(blockName, itemDict)
if not recipe:
# 方块是工具方块,但当前组合没有匹配的规则(比如手里物品不对)→ 不拦截
return
# 找到规则后,取消默认行为(如喝药水、吃食物等),由本 Mod 全权接管
args['cancel'] = True
# 同时标记该玩家下一次物品使用事件也要取消,防止喝药水等默认行为
self._cancelItemUse[playerId] = True
# ---- 第五步:加载方块历史记录到内存 ----
# 这样 handleSwitch 里直接从 self.switchRecord 取就行
self._loadBlockDataToMemory(key, playerId, pos, dim)
# ---- 第六步:执行工具切换 ----
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key, blockName)
# ============================================================================
# 拦截物品使用事件(防止喝药水等默认行为)
# ============================================================================
def OnItemTryUse(self, args):
playerId = args.get("playerId")
if self._cancelItemUse.pop(playerId, False):
args['cancel'] = True
# ============================================================================
# 规则匹配(O(1) 字典查找)
# ============================================================================
def findRecipe(self, blockName, itemDict):
"""
在工具切换字典中 O(1) 查找匹配的规则。
匹配条件:
1. 先精确匹配 (方块名, 物品名)
2. 如果没找到,尝试匹配 (方块名, None) —— 空手也能触发的规则
3. 如果匹配到的是 arrowMode 规则,额外检查手中物品是否为箭
返回:匹配到的规则 dict,找不到返回 None
"""
itemName = itemDict.get("newItemName") if itemDict else None
# 精确匹配:(方块名, 物品名)
recipe = self.switchRecipes.get((blockName, itemName))
if recipe:
return recipe
# 空手匹配:(方块名, None)
recipe = self.switchRecipes.get((blockName, None))
if recipe:
# 如果规则是 arrowMode 存箭模式,检查手中物品是否为箭
if recipe.get("arrowMode") == "store":
if itemDict and self._isArrow(itemDict):
return recipe
return None
return recipe
return None
def _isArrow(self, itemDict):
"""
判断手中物品是否为箭(普通箭或药水箭)。
返回 True/False。
"""
if not itemDict:
return False
itemName = itemDict.get("newItemName", "")
return itemName == "minecraft:arrow" or itemName == "minecraft:tipped_arrow"
# ============================================================================
# 工具方块切换处理
# ============================================================================
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key, blockName):
"""
处理工具方块的切换逻辑(箭桶、挂肉钩、水槽、盘子架、药水架、书柜等)。
支持三种模式:
1. recordType="count" → 记录一种物品+数量(如箭桶存箭)
2. recordType="stack" → 后进先出堆栈(如药水架、书柜)
3. 无 record → 纯切换,不记录(如挂肉钩直接换状态)
流程:
1. 如果是计时型(type="timer"),直接替换方块,不消耗物品
2. 如果是切换型(type="switch"),根据 recordType 决定记录方式
3. 消耗物品、替换方块、更新记录(记录写入方块实体数据,退出重进不丢)
"""
# ---- 如果规则指定了 tool,检查玩家手里是不是拿着指定工具 ----
if recipe.get("tool"):
if not itemDict:
return
carriedItem = itemDict.get("newItemName", "")
# 简单判断:物品名是否包含工具名
if recipe["tool"] not in carriedItem:
return
# ---- 冷却检查:防止连点右键瞬间切换多次 ----
now = time.time()
lastOp = self.switchCooldown.get(key, 0)
cd = recipe.get("switchCd", 0.5)
if now - lastOp < cd:
return
self.switchCooldown[key] = now
# ---- 箭桶模式(arrowMode):存箭 / 取箭 ----
# 独立于普通 count/stack 逻辑,因为箭桶需要批量操作
arrowMode = recipe.get("arrowMode")
if arrowMode:
self._handleArrowMode(arrowMode, recipe, playerId, pos, dim, itemDict, key, blockName)
return
# ---- 计时型:直接替换方块,不消耗物品,不记录 ----
if recipe.get("type") == "timer":
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
return
# ---- 切换型 ----
# 内存中已有数据(_loaBlockDataToMemory 已加载),直接用
record = self.switchRecord.get(key)
# ---- 正向操作:往方块里放东西(source == 被右键的方块) ----
if recipe["source"] == blockName and recipe.get("item") is not None:
# 玩家手里有物品,往方块里放
needSave = False # 标记是否需要保存记录到方块
if recipe.get("record"):
if recipe.get("recordType") == "count":
# 数量记录型:只记录一种物品的总数
if not record:
record = {
"count": 0,
"itemName": itemDict["newItemName"],
"auxValue": itemDict.get("newAuxValue", 0),
}
# 检查是否满了
maxCount = recipe.get("maxCount", 999)
if record["count"] >= maxCount:
return
# 记录物品+1
record["count"] += 1
needSave = True
elif recipe.get("recordType") == "stack":
# 堆栈记录型:后进先出,记录每种物品的完整信息
if not record:
record = {"stack": []}
maxCount = recipe.get("maxCount", 999)
if len(record["stack"]) >= maxCount:
return
# 把物品信息压入堆栈
record["stack"].append({
"itemName": itemDict["newItemName"],
"count": itemDict.get("count", 1),
"auxValue": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {}),
})
needSave = True
# 消耗物品
if recipe.get("consume", 1) > 0:
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
# 替换方块(旧方块销毁,新方块出现)
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# ★ 关键:替换完方块后再保存记录到新方块上,否则数据随旧方块一起销毁
if needSave:
self._saveRecord(key, playerId, pos, dim, record)
# 如果规则指定了给物品(如烧烤架浇水给桶)
if recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"])
return
# ---- 逆向操作:从方块里取东西(空手右键或取回物品) ----
if recipe["source"] == blockName and recipe.get("item") is None:
needSaveRecord = False # 是否需要保存记录
needClearRecord = False # 是否需要清除记录
if recipe.get("record") and record:
if recipe.get("recordType") == "count":
# 数量记录型取回:给玩家对应的物品,数量-1
if record["count"] > 0:
self.giveItem(playerId, record["itemName"])
record["count"] -= 1
if record["count"] <= 0:
needClearRecord = True
else:
needSaveRecord = True
elif recipe.get("recordType") == "stack":
# 堆栈记录型取回:后进先出,取最后一个放的物品
if record["stack"]:
topItem = record["stack"].pop()
comp.CreateItem(playerId).SpawnItemToPlayerInv(
{"itemName": topItem["itemName"],
"count": topItem["count"],
"auxValue": topItem["auxValue"],
"userData": topItem.get("userData", {})},
playerId)
if not record["stack"]:
needClearRecord = True
else:
needSaveRecord = True
# 如果规则直接指定了给什么物品(水槽、盘子架取回、挂肉钩退回等)
if recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"])
# 替换方块(旧方块销毁,新方块出现)
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# ★ 关键:替换完方块后再保存/清除记录,否则数据随旧方块一起销毁
if needClearRecord:
self.clearSwitchRecord(key, playerId, pos, dim)
elif needSaveRecord:
self._saveRecord(key, playerId, pos, dim, record)
return
# ============================================================================
# 通用持久化帮手(避免重复的 try/except/Get/Set 写法)
# ============================================================================
def _writeBlockData(self, dim, pos, **kwargs):
"""
通用方块实体数据写入。
GetBlockEntityData 返回的 BlockEntityData 对象支持 [] 操作,
直接赋值即可持久化。官方没有 SetBlockEntityData 方法。
"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
return
for key, value in kwargs.items():
blockData[key] = value
except Exception as e:
pass
def _saveRecord(self, key, playerId, pos, dim, record):
"""
同时更新内存和方块实体数据中的工具方块记录。
替代原本重复出现的:
self.switchRecord[key] = record
self.saveSwitchRecord(playerId, pos, dim, record)
"""
self.switchRecord[key] = record
self.saveSwitchRecord(playerId, pos, dim, record)
# ============================================================================
# 统一数据加载入口(玩家右键工具方块时先把方块数据恢复到内存)
# ============================================================================
def _loadBlockDataToMemory(self, key, playerId, pos, dim):
"""
在确定玩家右键命中了一个工具切换规则后,立刻把方块里存的记录加载到内存。
这样 handleSwitch 里就不用自己加载了,直接从 self.switchRecord 取就行。
"""
if key not in self.switchRecord:
switchInfo = self.loadSwitchRecord(playerId, pos, dim)
if switchInfo:
self.switchRecord[key] = switchInfo
# ============================================================================
# 工具方块记录持久化(药水架、书柜、箭桶等)
# ============================================================================
def saveSwitchRecord(self, playerId, pos, dim, record):
"""
把工具方块的记录数据写入方块实体数据。
记录类型:count型(箭桶)→ rtdd_swt/swc/swi,stack型(书柜/药水架)→ rtdd_swt/sws
记录存在方块实体数据里,所以玩家退出游戏再重进,
loadSwitchRecord 还能从方块里把物品读回来。
"""
if "count" in record:
self._writeBlockData(dim, pos,
rtdd_swt="count",
rtdd_swc=record["count"],
rtdd_swi=record.get("itemName", ""),
rtdd_swa=record.get("auxValue", 0))
elif "stack" in record:
self._writeBlockData(dim, pos,
rtdd_swt="stack",
rtdd_sws=record["stack"])
def loadSwitchRecord(self, playerId, pos, dim):
"""
从方块实体数据里读取工具方块的记录数据。
返回:
有数据 → {"count": N, "itemName": "xxx"} 或 {"stack": [...]}
没有 → None
"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
return None
swt = blockData["rtdd_swt"]
if swt == "count":
count = blockData["rtdd_swc"]
itemName = blockData["rtdd_swi"]
try:
auxValue = blockData["rtdd_swa"]
except (KeyError, Exception):
auxValue = 0
return {
"count": count,
"itemName": itemName,
"auxValue": auxValue,
}
elif swt == "stack":
stack = blockData["rtdd_sws"]
return {
"stack": stack,
}
else:
return None
except Exception as e:
return None
def clearSwitchRecord(self, key, playerId, pos, dim):
"""
清除工具方块的记录数据(内存+方块实体数据)。
"""
self.switchRecord.pop(key, None)
self._writeBlockData(dim, pos, rtdd_swt="", rtdd_swc=0, rtdd_swi="", rtdd_swa=0, rtdd_sws=[])
# ============================================================================
# 箭桶模式(arrowMode):存箭 / 取箭
# ============================================================================
def _handleArrowMode(self, arrowMode, recipe, playerId, pos, dim, itemDict, key, blockName):
"""
处理箭桶的存箭/取箭逻辑。
【存箭流程】
1. 检查手中是否为箭(_isArrow 已在 findRecipe 验证过)
2. 检查方块是否有记录且类型匹配:
a. 已有记录 → 检查手中箭类型(itemName+auxValue)是否匹配 → 不匹配则 return
b. 无记录 → 新建记录
3. 计算实际可存数量 = min(手中数量, maxCount - 已有数量)
4. 如果可存数量 <= 0 → return
5. 从手中扣减,更新记录,替换方块
【取箭流程】
1. 检查方块是否有记录且 count > 0
2. 给玩家手中/背包添加 count 只箭(itemName + auxValue 按记录)
3. 清空记录,替换方块
"""
record = self.switchRecord.get(key)
if arrowMode == "store":
# ---- 存箭 ----
handCount = itemDict.get("count", 0)
if handCount <= 0:
return
handItemName = itemDict.get("newItemName", "")
handAuxValue = itemDict.get("newAuxValue", 0)
# 检查方块是否有记录且类型匹配
if record and record["count"] > 0:
# 已有记录 → 检查箭类型是否一致
if record.get("itemName") != handItemName or record.get("auxValue", 0) != handAuxValue:
return # 类型不匹配,不能混存
else:
# 无记录 → 新建
record = {
"count": 0,
"itemName": handItemName,
"auxValue": handAuxValue,
}
# 计算实际可存数量
maxCount = recipe.get("maxCount", 32)
if record["count"] >= maxCount:
return # 已满
storeCount = min(handCount, maxCount - record["count"])
# 从玩家手中扣减
self._consumeItemCount(playerId, itemDict, storeCount)
# 更新记录
record["count"] += storeCount
# 替换方块
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# ★ 保存记录到新方块
# 注意:这里不用延迟写入。虽然 SetBlockNew 替换方块后 block entity
# 可能尚未完全初始化,但 GetBlockEntityData 仍然能获取到对象并写入。
# 延迟写入反而可能导致退出游戏后数据不持久化。
self._saveRecord(key, playerId, pos, dim, record)
elif arrowMode == "retrieve":
# ---- 取箭 ----
if not record or record["count"] <= 0:
return
retCount = record["count"]
retItemName = record["itemName"]
retAuxValue = record.get("auxValue", 0)
# 给玩家箭
self.giveItem(playerId, retItemName, retCount, retAuxValue)
# 替换方块
if recipe.get("target"):
self.replaceBlock(pos, dim, recipe["target"], playerId)
# ★ 清空记录
self.clearSwitchRecord(key, playerId, pos, dim)
def _consumeItemCount(self, playerId, itemDict, amount):
"""
从玩家主手批量扣减指定数量的物品。
如果手中数量刚好等于 amount → 清空主手。
如果手中数量大于 amount → 减少 amount。
"""
if not itemDict or amount <= 0:
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:
# 数量够 → 减少 amount 个
itemDict["count"] = count - amount
comp.CreateItem(playerId).SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict["count"],
"auxValue": itemDict.get("newAuxValue", 0)})
# ============================================================================
# 延迟写入帮手(解决 SetBlockNew 替换方块后 block entity 未初始化的问题)
# ============================================================================
def _saveRecordDelayed(self, key, playerId, pos, dim, record):
"""
延迟一帧保存记录到方块实体数据。
SetBlockNew 替换方块后,新方块的 block entity 需要引擎内部初始化时间,
立即 GetBlockEntityData 可能返回 None 导致写入静默失败。
先更新内存,再延迟写入方块实体。
"""
self.switchRecord[key] = record
gameComp.AddTimer(0.1, self.saveSwitchRecord, playerId, pos, dim, record)
def _clearRecordDelayed(self, key, playerId, pos, dim):
"""
延迟一帧清除方块实体数据。
"""
self.switchRecord.pop(key, None)
gameComp.AddTimer(0.1, self._clearRecordWrite, playerId, pos, dim)
def _clearRecordWrite(self, playerId, pos, dim):
"""
实际执行清除写入(被 AddTimer 调用)。
"""
self._writeBlockData(dim, pos, rtdd_swt="", rtdd_swc=0, rtdd_swi="", rtdd_swa=0, rtdd_sws=[])
# ============================================================================
# 工具方法:获取玩家手中物品
# ============================================================================
def getCarriedItem(self, playerId):
"""
获取玩家主手(右手)拿的物品。
空手返回 None。
"""
itemComp = comp.CreateItem(playerId)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
# ============================================================================
# 工具方法:消耗物品
# ============================================================================
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:
# 数量够 → 减少 amount 个
itemDict["count"] = count - amount
comp.CreateItem(playerId).SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict["count"],
"auxValue": itemDict.get("newAuxValue", 0)})
# ============================================================================
# 工具方法:替换方块
# ============================================================================
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
"""
把指定位置的方块替换成新方块。
对照 BlockBlock.py 的标准写法:
- 使用命名参数
- oldBlockHandling=0 表示直接替换(不保留旧方块数据)
- aux 默认继承旧方块朝向,这样替换后不会转方向
"""
if aux is None:
try:
oldBlock = comp.CreateBlockInfo(playerId).GetBlockNew(pos, dim)
aux = oldBlock.get("aux", 0)
except:
aux = 0
comp.CreateBlockInfo(playerId).SetBlockNew(
pos,
blockDict={"name": newBlockName, "aux": aux},
oldBlockHandling=0,
dimensionId=dim)
# ============================================================================
# 工具方法:给玩家物品
# ============================================================================
def giveItem(self, playerId, itemName, count=1, auxValue=0):
"""
往玩家背包里塞一个物品。
如果背包满了,物品会掉在地上。
"""
comp.CreateItem(playerId).SpawnItemToPlayerInv(
{"itemName": itemName, "count": count, "auxValue": auxValue}, playerId)
@@ -1,791 +0,0 @@
# -*- coding: utf-8 -*-
"""
================================================================================
通用烹饪引擎(服务端)
================================================================================
这个文件是整个模组最核心的"大脑",所有做菜相关的逻辑都在这里。
它不关心具体做什么菜——具体菜谱全在 RecipeData.py 里。
它只负责"当玩家对某个方块右键时,查菜谱、耗材料、换方块、记进度"这套通用流程。
【一句话概括】
玩家右键烹饪方块 → 引擎查 RecipeData 有没有匹配的配方 →
有就消耗手上物品 → 替换方块/记录翻炒进度 → 完成做菜
【为什么和工具切换分开】
原本做菜和普通工具方块切换(箭桶、水槽、书架、药水架等)都在这个类里。
现在已拆成两个独立系统:
- CookingService → 只处理做菜配方(RECIPES),本文件
- BlockSwitchService → 只处理工具方块切换(BLOCK_SWITCHES)
两个系统都监听同一个 ServerBlockUseEvent,
各自在事件第一行用 cookBlockSet/switchBlockSet(O(1) 的 set)按方块名拦截,
不是自己的方块直接 return,互不干扰,各自只查自己那一份数据。
【设计原则】
- 加新菜不改代码:在 RecipeData.py 加一条配方就行,这里一行不用动
- 所有做菜逻辑一处维护:炒锅、煎锅、油锅、架锅、蒸笼……全走同一个引擎
- 翻炒进度存方块里:服务器重启后数据不丢,玩家能继续翻
【用法】
在 modMain.py 里注册这个系统即可:
serverApi.RegisterSystem("rtdd", "CookingService",
"Script_NeteaseModousIY5aSF.xiangyemod.service.CookingService.CookingService")
【事件监听】
- ServerBlockUseEvent → 玩家右键方块(做菜主入口)
- ServerPlayerTryDestroyBlockEvent → 玩家拆方块(掉落食材)
================================================================================
"""
import time
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
comp = serverApi.GetEngineCompFactory() # 组件工厂,用来创建各种功能组件
# ---- 继承 BlueFox 框架的系统基类 ----
# BlueFoxServerSystem 会自动处理 LoadMod / UnloadMod 等生命周期,
# 不需要手动写,继承后框架自动发现并注册
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
from ...BlueFoxTools.ToolsConfig import LevelId
# ---- 方块实体数据组件 ----
# GetBlockEntityData 返回的 BlockEntityData 对象支持 [] 操作,直接赋值即可持久化。
# 参考官方文档:方块实体数据组件只有 GetBlockEntityData,没有 SetBlockEntityData。
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
# ---- 导入所有配方 ----
# RECIPES 是一个列表,每条配方是一个 dict,格式见 RecipeData.py 顶部注释
from ..RecipeData import RECIPES, inferBaseBlock, BELOW_CAMPFIRE
# ---- 已由 FeatureDispatcher/BarbecueService 接管的烧烤方块 ----
# 这些方块属于"烧烤"(xy_barbecue) 分类,由新系统处理(支杆+营火机制),
# 做菜系统必须跳过,否则右键烤乳猪(熟)等会被当成菜谱去查灶台底座,弹出错误提示。
_BARBECUE_OWNED = set([
"rtdd:barbecuegrill_0", "rtdd:barbecuegrill_1",
"rtdd:roastpig_0", "rtdd:roastpig_1",
"rtdd:roastchicken_0", "rtdd:roastchicken_1",
"rtdd:roastchickenplace_0",
"rtdd:grilledcod_0", "rtdd:grilledcod_1",
"rtdd:grilled_fish", "rtdd:grilled_salmon",
"rtdd:roasted_vegetables_0", "rtdd:roasted_vegetables_1", "rtdd:roasted_vegetables_3",
"rtdd:roastedmushrooms_0", "rtdd:roastedmushrooms_1", "rtdd:roastedmushrooms_3",
"rtdd:beefkebabs_0", "rtdd:beefkebabs_1", "rtdd:beefkebabs_3",
"rtdd:lambkebabs_0", "rtdd:lambkebabs_1", "rtdd:lambkebabs_3",
])
# ================================================================================
# 工具函数:读取物品 userData 里的嵌套字段
# ================================================================================
def getUserDataField(itemDict, *keys):
"""
从物品 dict 的 userData 里一层层往下读。
我的世界物品可以携带隐藏数据(userData),比如一个锅铲可以标记"有油"。
这个函数就是方便地读取这些嵌套数据,不用每次都写一堆 .get()。
例子:
itemDict = {
"newItemName": "rtdd:spatula",
"userData": {"rtdd": {"oil": True}}
}
getUserDataField(itemDict, 'rtdd', 'oil') → 返回 True
如果中间某一层不存在,会自动创建空 dict,不会报错。
"""
# 确保 userData 字段存在
itemDict['userData'] = itemDict.get('userData', {})
if itemDict.get('userData') is None:
itemDict['userData'] = {}
# 一层层往下钻,路径上不存在的 key 自动创建空 dict
node = itemDict['userData']
for key in keys:
node[key] = node.get(key, {})
node = node[key]
return node
# ================================================================================
# 烹饪引擎主类
# ================================================================================
class CookingService(BlueFoxServerSystem):
"""
通用烹饪引擎
【核心数据结构】
stirData = {
(维度ID, x, y, z): { # 用方块坐标当 key,每个方块独立
"count": 整数, # 已经翻炒了多少次(比如翻了3次)
"lastTime": 浮点数, # 上次翻炒的时间戳(用于冷却判断)
"finished": 布尔值, # 是否已经熟了(熟了才能出锅)
"dropIfDone": [物品名列表], # 熟了拆掉方块时掉什么
"dropIfNot": [物品名列表], # 没熟拆掉方块时掉什么
}
}
这个数据同时存在两处:
1. 内存(self.stirData)—— 快,关服就没了
2. 方块实体数据(blockEntityData)—— 慢一点,但关服重启后还在
每次操作都会同步更新两处,保证重启不丢进度
工具方块(箭桶、书架、药水架等)的记录数据已拆分到 BlockSwitchService,
本类只负责做菜配方(RECIPES)。
"""
def __init__(self, namespace, systemName):
"""
初始化:清空翻炒数据、注册事件监听、构建统一配方字典
"""
super(CookingService, self).__init__(namespace, systemName)
print "===== CookingService init ====="
self.stirData = {} # 内存中的翻炒数据字典
self._cancelItemUse = {} # 将要取消下一次物品使用的玩家ID集合(防止喝药水等)
self.ListenEvent() # 注册游戏事件监听
# ---- 构建做菜配方字典(O(1) 查找) ----
# 只含 RECIPES(做菜配方),工具方块切换已拆到 BlockSwitchService
# key = (方块名, 物品名),value = 配方dict
# 这样查找时不用遍历整个列表,直接 dict[key] 就能拿到,速度飞快
self.allRecipes = {}
for recipe in RECIPES:
key = (recipe["source"], recipe.get("item"))
self.allRecipes[key] = recipe
# ---- 构建做菜方块名集合(O(1) 快速拦截,第一道过滤) ----
# 事件进来先判断方块名在不在这个集合里,不在直接 return,
# 这样工具方块的右键事件根本不会进到本系统的字典查找
# 注意:跳过已由 BarbecueService 接管的烧烤方块,避免做菜系统抢事件
self.cookBlockSet = set()
for recipe in RECIPES:
if recipe["source"] in _BARBECUE_OWNED:
continue
self.cookBlockSet.add(recipe["source"])
def Destroy(self):
"""
系统销毁时取消所有事件监听,防止内存泄漏
"""
print "===== CookingService Destroy ====="
self.UnListenEvent()
# ============================================================================
# 事件注册与注销
# ============================================================================
def ListenEvent(self):
"""
注册三个游戏事件:
1. ServerBlockUseEvent —— 玩家右键方块(做菜、翻炒、加油等)
2. ServerPlayerTryDestroyBlockEvent —— 玩家拆方块(掉落食材)
3. ServerItemTryUseEvent —— 玩家尝试使用物品(拦截喝药水等默认行为)
"""
# 事件1:玩家右键方块
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
# 事件2:玩家尝试拆方块
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerPlayerTryDestroyBlockEvent", self, self.OnDestroy)
# 事件3:玩家尝试使用物品(拦截喝药水)
self.ListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemTryUseEvent", self, self.OnItemTryUse)
def UnListenEvent(self):
"""
注销事件监听,和 ListenEvent 一一对应
"""
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerBlockUseEvent", self, self.OnBlockUse)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerPlayerTryDestroyBlockEvent", self, self.OnDestroy)
self.UnListenForEvent(serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
"ServerItemTryUseEvent", self, self.OnItemTryUse)
# ============================================================================
# 主入口:玩家右键方块
# ============================================================================
def OnBlockUse(self, args):
"""
玩家右键方块时触发,是整个做菜流程的入口。
流程:
1. 拿到被右键的方块名、玩家ID、方块坐标
2. 先按方块名判断是不是本系统管辖的做菜方块(O(1) set 拦截)
- 不是 → 直接 return(这是工具方块的右键,交给 BlockSwitchService 处理)
3. 看玩家手里拿了什么物品
4. 拿着"方块名+手里物品名"去做菜字典里 O(1) 查找
5. 命中配方 → 查底座 → 执行做菜逻辑(翻炒/替换)
args 是网易API传过来的事件参数,包含:
- blockName: 被右键的方块名字(如 "rtdd:wok")
- playerId: 右键的玩家ID
- x, y, z: 方块坐标
"""
# ---- 第一步:提取事件参数 ----
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) # 完整的唯一标识:维度+坐标
# ---- 第二步:O(1) 方块名拦截 ----
# 只有做菜方块的右键才归本系统处理;工具方块直接放过,交给 BlockSwitchService
if blockName not in self.cookBlockSet:
return
# ---- 第三步:拿到玩家手里拿的物品 ----
itemDict = self.getCarriedItem(playerId)
# itemDict 可能是 None(空手),也可能是 dict(手里有东西)
# dict 格式:{"newItemName": "rtdd:spatula", "count": 1, "userData": {...}}
# ---- 第四步:查做菜配方(O(1) 字典查找) ----
recipe = self.findRecipe(blockName, itemDict)
if not recipe:
# 方块是烹饪方块,但当前组合没有匹配的配方(比如手里物品不对)→ 不拦截
return
# 找到配方后,取消默认行为(如喝药水、吃食物等),由本 Mod 全权接管
args['cancel'] = True
# 同时标记该玩家下一次物品使用事件也要取消,防止喝药水等默认行为
self._cancelItemUse[playerId] = True
# ---- 第五步:加载翻炒历史数据到内存 ----
# 这样 handleStir 里直接从 self.stirData 取就行
self._loadBlockDataToMemory(key, playerId, pos, dim, recipe)
# ---- 底座检查:锅下面必须有灶台/营火 ----
if not self.checkBaseBlock(recipe, playerId, pos, dim):
return
# ---- 第六步:以下为做菜配方逻辑 ----
# 内存中已有数据(_loadBlockDataToMemory 已加载),直接用
stirInfo = self.stirData.get(key, {})
# ---- 根据配方类型判断 ----
# needFinished=True 的配方 → 需要先翻炒熟了才能操作(比如出锅)
# needFinished=False 的配方 → 只能对没熟的进行操作(比如继续加油)
if recipe.get("needFinished") is True and not stirInfo.get("finished"):
# 配方要求"已熟",但当前状态是"没熟" → 不执行
return
if recipe.get("needFinished") is False and stirInfo.get("finished"):
# 配方要求"没熟",但当前状态是"已熟" → 不执行
return
# ---- 分发到不同的处理逻辑 ----
if recipe.get("stirCount"):
# 这个配方有 stirCount 字段 → 需要翻炒,走翻炒流程
self.handleStir(key, recipe, playerId, pos, dim, itemDict)
return
# 没有 stirCount → 普通替换流程(比如加油、加水、盖盖)
self.executeRecipe(recipe, playerId, pos, dim, itemDict, key)
# ============================================================================
# 拦截物品使用事件(防止喝药水等默认行为)
# ============================================================================
def OnItemTryUse(self, args):
playerId = args.get("playerId")
if self._cancelItemUse.pop(playerId, False):
args['cancel'] = True
# ============================================================================
# 配方匹配(O(1) 字典查找)
# ============================================================================
def findRecipe(self, blockName, itemDict):
"""
在统一字典中 O(1) 查找匹配的配方。
匹配条件:
1. 先精确匹配 (方块名, 物品名)
2. 如果没找到,尝试匹配 (方块名, None) —— 空手也能触发的规则
返回:匹配到的配方 dict,找不到返回 None
"""
# 提取玩家手里物品的名字(空手时 itemName 为 None)
itemName = itemDict.get("newItemName") if itemDict else None
# 精确匹配:(方块名, 物品名)
recipe = self.allRecipes.get((blockName, itemName))
if recipe:
return recipe
# 空手匹配:(方块名, None)
recipe = self.allRecipes.get((blockName, None))
if recipe:
return recipe
return None
# ============================================================================
# 配方执行(普通替换类型)
# ============================================================================
def executeRecipe(self, recipe, playerId, pos, dim, itemDict, key):
"""
执行一个"普通替换"类型的配方。
这种配方不需要翻炒,直接消耗物品、替换方块、给奖励。
典型场景:
- 空炒锅 + 锅铲 → 倒了油的炒锅(加油)
- 倒了油的炒锅 + 食材 → 正在炒的炒锅(下菜)
- 空架锅 + 水桶 → 加了水的架锅(加水)
流程:
1. 检查前置条件(下面有没有底座、物品有没有油标签等)
2. 消耗玩家手里的物品
3. 如果需要去油,去掉物品上的油标签
4. 替换方块(比如从"空炒锅"变成"倒了油的炒锅")
5. 如果需要给玩家物品,塞进背包
"""
# ---- 第一步:检查前置条件 ----
if not self.checkConditions(recipe, playerId, pos, dim, itemDict):
# 条件不满足(比如下面没有灶台),什么都不做
return
# ---- 第二步:消耗物品 ----
if recipe.get("consume", 1) > 0:
# consume 默认是 1,表示消耗 1 个物品
# 如果 consume=0 表示不消耗(比如锅盖只是点一下,不消耗锅盖本身)
self.consumeItem(playerId, itemDict, recipe.get("consume", 1))
# ---- 第三步:如果有 needOil 标记,去掉物品上的油标签 ----
if recipe.get("needOil"):
self.removeOil(itemDict, playerId)
# ---- 第四步:替换方块 ----
if recipe.get("target"):
# target 是目标方块名,比如 "rtdd:oilpan"(倒了油的煎锅)
self.replaceBlock(pos, dim, recipe["target"], playerId)
# 方块变了,旧方块的翻炒数据也要清除
self.clearStirData(key, playerId, pos, dim)
# ---- 第五步:给玩家奖励物品 ----
if recipe.get("giveItem"):
self.giveItem(playerId, recipe["giveItem"])
# ============================================================================
# 翻炒处理
# ============================================================================
def handleStir(self, key, recipe, playerId, pos, dim, itemDict):
"""
处理需要"翻炒"的配方。
翻炒类配方的特点:
- 玩家右键一次 = 翻一次
- 有冷却时间(stirCd),防止连点器秒熟
- 翻够次数(stirCount)后才算"熟"
- 熟了之后可以继续右键"出锅"(变成成品菜)
流程:
1. 从内存取翻炒进度(_loadBlockDataToMemory 已提前加载)
2. 检查冷却时间,没冷却好就忽略
3. 翻炒次数+1,更新进度
4. 给玩家发进度提示("翻炒中... 3/10")
5. 翻够了 → 标记 finished=True,或者直接替换方块
"""
now = time.time()
# ---- 第一步:从内存取翻炒数据(入口已通过 _loadBlockDataToMemory 加载) ----
data = self.stirData.get(key)
if not data:
# 全新的翻炒,初始化数据
data = {
"count": 0,
"lastTime": 0.0,
"finished": False,
"dropIfDone": recipe.get("dropIfDone", []),
"dropIfNot": recipe.get("dropIfNot", []),
}
else:
# 兜底防止 None 值(方块数据可能不完整)
for field, default in [("lastTime", 0.0), ("count", 0), ("finished", False)]:
if data.get(field) is None:
data[field] = default
self.stirData[key] = data
# ---- 第二步:冷却检查 ----
# 防止玩家狂点右键,一秒翻100次直接熟了
# stirCd 默认 1.0 秒,即两次翻炒之间至少间隔 1 秒
if now - data["lastTime"] < recipe.get("stirCd", 1.0):
# 还在冷却中,这次点击被忽略
return
# ---- 第三步:翻炒次数+1 ----
data["count"] += 1
data["lastTime"] = now
self.stirData[key] = data
# 同步保存到方块实体数据,防止服务器重启丢失
self.saveStirData(playerId, pos, dim, data)
# ---- 第四步:给玩家发进度提示 ----
comp.CreateMsg(playerId).NotifyOneMessage(
playerId, "翻炒中... %s/%s" % (data["count"], recipe["stirCount"]))
# 玩家会看到 "翻炒中... 3/10" 这样的提示
# ---- 第五步:判断是否翻够了 ----
if data["count"] >= recipe["stirCount"]:
# 翻够了!根据 onDone 字段决定后续行为
if recipe.get("onDone") == "finish":
# 标记为"熟了",方块不变,玩家可以继续右键出锅
data["finished"] = True
self.stirData[key] = data
self.saveStirData(playerId, pos, dim, data)
if recipe.get("doneMsg"):
# 发完成提示,比如 "菜炒好了!"
comp.CreateMsg(playerId).NotifyOneMessage(playerId, recipe["doneMsg"])
elif recipe.get("target"):
# 有 target 字段 → 直接替换方块(比如翻炒到一定程度自动变成下一个状态)
self.replaceBlock(pos, dim, recipe["target"], playerId)
self.clearStirData(key, playerId, pos, dim)
# ============================================================================
# 底座检查(入口统一拦截)
# ============================================================================
def checkBaseBlock(self, recipe, playerId, pos, dim):
"""
检查锅下面有没有合法的底座(灶台或营火)。
优先级:
1. 配方里明确写了 "below" 字段 → 用配方的值
- below=[] 表示不需要底座,直接通过
- below=["xxx"] 表示必须是这些方块之一
2. 配方没写 "below" → 调用 inferBaseBlock 自动推断
- 大多数烹饪方块默认需要灶台(BELOW_STOVE)
- 架锅系列需要营火(BELOW_CAMPFIRE)
- 手工工具(榨汁机、搅拌器等)不需要底座
返回 True=底座合法/不需要底座,False=底座不合法
"""
# 配方显式指定了 below
if "below" in recipe:
required = recipe["below"]
if not required: # 空列表 = 不需要底座
return True
else:
# 自动推断
required = inferBaseBlock(recipe["source"])
if not required: # 推断结果为空 = 不需要底座
return True
# 检查下面一格方块
belowPos = (pos[0], pos[1] - 1, pos[2])
try:
belowBlock = comp.CreateBlockInfo(playerId).GetBlockNew(belowPos, dim)
if belowBlock.get("name") in required:
return True
except:
pass
# ---- 底座不通过,给玩家提示 ----
if required == BELOW_CAMPFIRE:
tip = "§c架锅下面需要放一个营火,才能做饭!"
else:
tip = "§c锅下面需要放一个灶台(点燃的炉灶),才能做饭!"
comp.CreateMsg(playerId).NotifyOneMessage(playerId, tip)
return False
# ============================================================================
# 条件检查
# ============================================================================
def checkConditions(self, recipe, playerId, pos, dim, itemDict):
"""
检查配方的前置条件是否满足。
目前只检查 needOil:玩家手里的物品必须带有油标签。
底座检查已移到入口 checkBaseBlock 统一处理。
"""
# ---- 检查物品是否有油标签 ----
if recipe.get("needOil") and itemDict:
if not self.getItemOil(itemDict):
return False
# 所有条件都通过了
return True
# ============================================================================
# 工具方法:获取玩家手中物品
# ============================================================================
def getCarriedItem(self, playerId):
"""
获取玩家主手(右手)拿的物品。
返回格式:
{"newItemName": "rtdd:spatula", "count": 1, "newAuxValue": 0, "userData": {...}}
空手返回 None。
注意区分:
- CARRIED = 主手(右手)
- OFFHAND = 副手(左手)
- 这里只取主手
"""
itemComp = comp.CreateItem(playerId)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
# ============================================================================
# 工具方法:消耗物品
# ============================================================================
def consumeItem(self, playerId, itemDict, amount=1):
"""
消耗玩家主手物品的指定数量。
如果数量不够 → 清空主手
如果数量够 → 减少对应数量
重要:SetInvItemNum 的参数是背包槽位索引(int),不是 ItemPosType 枚举。
"""
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:
# 数量够 → 减少 amount 个
itemDict["count"] = count - amount
comp.CreateItem(playerId).SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict["count"],
"auxValue": itemDict.get("newAuxValue", 0)})
# ============================================================================
# 工具方法:替换方块
# ============================================================================
def replaceBlock(self, pos, dim, newBlockName, playerId, aux=None):
"""
把指定位置的方块替换成新方块。
参数:
pos: (x, y, z) 元组
dim: 维度ID
newBlockName: 新方块的完整ID,如 "rtdd:oilpan"
playerId: 玩家ID(用于创建组件)
aux: 方块附加值(朝向)。None=自动继承旧方块的朝向,数字=强制指定
对照 BlockBlock.py 的标准写法:
- 使用命名参数
- oldBlockHandling=0 表示直接替换(不保留旧方块数据)
- aux 默认继承旧方块朝向,这样替换后不会转方向
"""
# 如果没指定 aux,自动读取旧方块的朝向并继承
if aux is None:
try:
oldBlock = comp.CreateBlockInfo(playerId).GetBlockNew(pos, dim)
aux = oldBlock.get("aux", 0)
except:
aux = 0
comp.CreateBlockInfo(playerId).SetBlockNew(
pos,
blockDict={"name": newBlockName, "aux": aux},
oldBlockHandling=0,
dimensionId=dim)
# ============================================================================
# 工具方法:给玩家物品
# ============================================================================
def giveItem(self, playerId, itemName, count=1):
"""
往玩家背包里塞一个物品。
如果背包满了,物品会掉在地上。
"""
comp.CreateItem(playerId).SpawnItemToPlayerInv(
{"itemName": itemName, "count": count, "auxValue": 0}, playerId)
# ============================================================================
# 油标签操作
# ============================================================================
def getItemOil(self, itemDict):
"""
读取物品上的油标签。
物品的 userData 里有一个 rtdd.oil 字段,
为 True 表示"这个锅铲上有油",可以在锅里倒油。
这是纯本地 dict 操作,不调网易API,非常快。
"""
try:
return getUserDataField(itemDict, 'rtdd').get('oil', False)
except:
return False
def removeOil(self, itemDict, playerId):
"""
去掉物品上的油标签,并同步回玩家手里的物品。
流程:
1. 把本地 dict 里的 oil 标记改为 False
2. 调用 SetEntityItem 把修改后的 userData 写回玩家手里的物品
注意:SetEntityItem 会覆盖整个物品数据,
所以必须把 itemName、count、auxValue 都原样传回去。
"""
try:
# 本地修改 oil 标记
getUserDataField(itemDict, 'rtdd')['oil'] = False
# 同步回玩家手里的物品
itemComp = comp.CreateItem(playerId)
itemComp.SetEntityItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict.get("count", 1),
"auxValue": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {})})
except:
pass
# ============================================================================
# 通用持久化帮手(避免重复的 try/except/Get/Set 写法)
# ============================================================================
def _writeBlockData(self, dim, pos, **kwargs):
"""
通用方块实体数据写入。
GetBlockEntityData 返回的 BlockEntityData 对象支持 [] 操作,
直接赋值即可持久化。官方没有 SetBlockEntityData 方法。
"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if blockData is None:
return
for key, value in kwargs.items():
blockData[key] = value
except Exception as e:
pass
# ============================================================================
# 统一数据加载入口(玩家右键方块时先把方块数据恢复到内存)
# ============================================================================
def _loadBlockDataToMemory(self, key, playerId, pos, dim, recipe):
"""
在确定玩家右键命中了一个做菜配方后,立刻把方块里存的翻炒进度加载到内存。
这样 handleStir 里就不用自己加载了,直接从 self.stirData 取就行。
"""
if key not in self.stirData:
stirInfo = self.loadStirData(playerId, pos, dim, recipe)
if stirInfo:
self.stirData[key] = stirInfo
# ============================================================================
# 翻炒数据持久化(存方块里,不存JSON文件)
# ============================================================================
def loadStirData(self, playerId, pos, dim, recipe):
"""
从方块的实体数据里读取翻炒进度。
为什么要把翻炒进度存在方块里?
→ 因为服务器重启后内存就没了,存在方块实体数据里可以持久化。
→ 玩家重启服务器后,之前炒了一半的菜还能继续炒。
存储的三个字段(存在方块实体数据中):
rtdd_sc = stir count → 翻炒次数
rtdd_slt = stir last time → 上次翻炒时间戳
rtdd_sf = stir finished → 是否已熟
重要:GetBlockEntityData 返回的是 BlockEntityData 对象,不是普通 dict!
必须用方括号 [] 取值,不能用 .get(),否则会抛异常。
"""
try:
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
stirCount = blockData["rtdd_sc"] # 翻炒次数
return {
"count": stirCount,
"lastTime": blockData["rtdd_slt"], # 上次翻炒时间
"finished": blockData["rtdd_sf"], # 是否熟了
# dropIfDone 和 dropIfNot 从配方里取,不存方块数据中
"dropIfDone": recipe.get("dropIfDone", []),
"dropIfNot": recipe.get("dropIfNot", []),
}
except:
# 方块可能没有实体数据(从没被翻炒过),返回空 dict
return {}
def saveStirData(self, playerId, pos, dim, data):
"""
把翻炒进度写入方块实体数据。
"""
self._writeBlockData(dim, pos,
rtdd_sc=data["count"],
rtdd_slt=data["lastTime"],
rtdd_sf=data["finished"])
def clearStirData(self, key, playerId, pos, dim):
"""
清除方块上的翻炒数据(内存+方块实体数据)。
"""
self.stirData.pop(key, None)
self._writeBlockData(dim, pos, rtdd_sc=0, rtdd_slt=0.0, rtdd_sf=False)
# ============================================================================
# 拆方块处理
# ============================================================================
def OnDestroy(self, args):
"""
玩家拆方块时触发。
如果这个方块有翻炒数据(说明之前有人在炒菜):
- 熟了(finished=True)→ 掉落"熟的东西"(dropIfDone)
- 没熟(finished=False)→ 掉落"生的东西"(dropIfNot)
这样玩家拆锅时不会血本无归——炒了一半的菜至少能拿回食材。
args 参数:
- playerId: 拆方块的玩家
- x, y, z: 被拆的方块坐标
"""
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
key = (dim, x, y, z)
# ---- 先尝试从内存取翻炒数据 ----
data = self.stirData.get(key)
if not data:
# 内存没有 → 尝试从方块实体数据加载(服务器重启过的情况)
data = self.loadStirData(playerId, (x, y, z), dim, {})
if not data:
# 完全没有翻炒数据 → 不是烹饪方块,正常拆
return
# ---- 根据是否熟了决定掉落什么 ----
if data.get("finished"):
dropItems = data.get("dropIfDone", []) # 熟了 → 掉熟的东西
else:
dropItems = data.get("dropIfNot", []) # 没熟 → 掉生的东西
# ---- 逐个生成掉落物 ----
for itemName in dropItems:
itemDict = {"itemName": itemName, "count": 1, "auxValue": 0}
# 掉落物生成在方块上方 0.5 格的位置,让它自然掉到地上
self.CreateEngineItemEntity(itemDict, dim, (x, y + 0.5, z))
# ---- 清除数据 ----
self.clearStirData(key, playerId, (x, y, z), dim)
+1 -1
View File
@@ -1029,7 +1029,7 @@ tile.rtdd:sales_box_watermelon.name=售卖盒(西瓜)
tile.rtdd:rawnoodlesoup.name=汤锅_65(生面汤) tile.rtdd:rawnoodlesoup.name=汤锅_65(生面汤)
tile.rtdd:stove.name=炉灶(炉灶_空) tile.rtdd:stove.name=炉灶(炉灶_空)
tile.rtdd:b_16.name=土豆盒_1_禁用 tile.rtdd:b_16.name=土豆盒_1_禁用
tile.rtdd:b_15.name=木条_1 tile.rtdd:b_15.name=木条
tile.rtdd:b_14.name=木桶_1_禁用 tile.rtdd:b_14.name=木桶_1_禁用
tile.rtdd:b_12.name=西瓜盒_1_禁用 tile.rtdd:b_12.name=西瓜盒_1_禁用
tile.rtdd:fried_porkwith_potatoes.name=炒锅_64(土豆炒猪肉_生) tile.rtdd:fried_porkwith_potatoes.name=炒锅_64(土豆炒猪肉_生)
+4 -32
View File
@@ -10,8 +10,8 @@
一、章鱼小丸子 一、章鱼小丸子
======================================== ========================================
1. 章鱼小丸子(完整) -> 章鱼小丸子(半份) 1. 章鱼小丸子(完整) -> 章鱼小丸子(半份)
转换条件:[手持 筷子/刀叉 右键 进行分割] 转换条件:[右键 吃 进行转换]
机制: 手持筷子右键,分成半份 机制: 点击右键,转换为半份的章鱼小丸子 (属于吃的东西,后续实现)
======================================== ========================================
二、原版食物(静态成品盘) 二、原版食物(静态成品盘)
@@ -19,33 +19,5 @@
plate_16(南瓜派盘)、plate_17(生鱼片)、plate_18(西瓜盘)、plate_20(面包盘)、plate_21(曲奇盘)、plate_4(熟鸡肉盘) plate_16(南瓜派盘)、plate_17(生鱼片)、plate_18(西瓜盘)、plate_20(面包盘)、plate_21(曲奇盘)、plate_4(熟鸡肉盘)
以上均为静态成品,无转化关系 以上均为静态成品,无转化关系
======================================== todo 转换: 拿原版对应食物 右键盘子 进行转换
三、烤制·串烧(与烧烤分类一致,详见《烧烤转化关系.txt》) 生鱼片 拿 鳕鱼鲑鱼热带鱼右键都可以
========================================
2. 牛肉串(生) -> 牛肉串(半熟) -> 牛肉串(熟)
转换条件:[放到烧烤架,计时自动转换]
机制: 手持牛肉串_生右键烧烤架,计时约60秒升一级,共两级烤熟
3. 羊肉串(生) -> 羊肉串(半熟) -> 羊肉串(熟)
转换条件:[放到烧烤架,计时自动转换]
机制: 手持羊肉串_生右键烧烤架,计时约60秒升一级,共两级烤熟
4. 烤蔬菜串(生) -> 烤蔬菜串(半熟) -> 烤蔬菜串(熟)
转换条件:[放到烧烤架,计时自动转换]
机制: 手持烤蔬菜串_生右键烧烤架,计时约60秒升一级,共两级烤熟
5. 烤香菇串(生) -> 烤香菇串(半熟) -> 烤香菇串(熟)
转换条件:[放到烧烤架,计时自动转换]
机制: 手持烤香菇串_生右键烧烤架,计时约60秒升一级,共两级烤熟
6. 烤鸡/烤乳猪/烤鱼系列
转换条件:[与烧烤分类一致,详见《烧烤转化关系.txt》]
========================================
四、其他分组说明
========================================
- 汤面分组(food_02_rice_noodle): 胡萝卜蔬菜面、胡萝卜蔬菜牛肉面、鸡蛋面、香菇面、熟面条、
蛋花面、蛋花面(加葱花)、猪排蔬菜面、羊排蔬菜面、牛骨面、小鸡炖蘑菇面、猪排拉面、蔬菜面、蔬菜鱼片面
该分组与汤锅页共用,面条类成品静态方块,无后续转换
- 盖饭分组(food_11_rice_meal): 辣子鸡丁盖饭、鳕鱼盖饭、香菇滑鸡盖饭、麻婆豆腐盖饭
该分组与炒锅页共用,成品静态方块,无后续转换
+45
View File
@@ -185,3 +185,48 @@
五、布料已按标注禁用 [已处理] 五、布料已按标注禁用 [已处理]
- 售卖盒(黄绿布料 clothbox_1)、售卖盒(棕色布料 clothbox_2): 名字带"禁用", 已调去 禁用(xy_jinyong) 标签, 移出市场分组, 旧切换规则已移除 - 售卖盒(黄绿布料 clothbox_1)、售卖盒(棕色布料 clothbox_2): 名字带"禁用", 已调去 禁用(xy_jinyong) 标签, 移出市场分组, 旧切换规则已移除
########################################
【家具家私】(xy_furniture) 2026-09-03
########################################
(旧 BlockSwitchService 已废弃, 全部 7 个功能统一由 xiangyemod/jiaju 的
FurnitureData+FurnitureService 实现, 注册到 FeatureDispatcher)
一、箭桶 (arrowbarrel_0 无箭 / arrowbarrel_1 有箭)
- 存箭: 手持箭(普通箭/药水箭)右键, 批量存入手中所有箭, 至多32只, count记录
(类型/药水效果aux不符不能混存, 记录写方块实体数据 rtdd_swt=count, 退出重进不丢) [已实现]
- 取箭: 空手右键, 一次性取回方块记录的全部箭(数量+类型+药水效果aux) [已实现]
二、水槽 (flume_0~5)
- 加水: 空手右键空槽 → 满水 [已实现]
- 取水: 手持空水桶右键满水 → 空槽 + 返还装满水的水桶 [已实现]
- 放脏盘子: 满水起手持脏盘子逐个放入(最多4个), 消耗1个 [已实现]
- 取脏盘子: 空手右键逐个取回 [已实现]
三、盘子架 (trayracks_1 空 ~ trayracks_6 五个)
- 放盘子: 手持盘子逐个放入(最多5个), 消耗1个 [已实现]
- 取盘子: 空手右键逐个取回 [已实现]
四、书柜 (bookcase 系列, 后放先取, 至多13本)
- 放书: 手持书/附魔书右键逐个放入, 消耗1本, stack记录完整物品信息(附魔不丢,
字段 rtdd_swt=stack, 退出重进不丢) [已实现]
- 取书: 空手右键后放先取(LIFO) [已实现]
五、厨具架 (kitchenwarerack 系列, 纯切换, 方块状态即组合)
- 放入: 手持 锅铲/锅勺/菜刀/炒锅 右键空架/未满架 → 消耗1个 → 形态变为对应组合 [已实现]
(空+锅铲→单锅铲, 空+锅勺→单锅勺, 空+菜刀→单菜刀, 空+炒锅→单炒锅;
单锅铲+锅铲→双锅铲, 单锅铲+锅勺→锅铲+锅勺, 单锅勺+锅勺→双锅勺,
单炒锅+菜刀→炒锅+菜刀, 单菜刀+菜刀→双菜刀, 单菜刀+锅铲→菜刀+锅铲,
单菜刀+锅勺→菜刀+锅勺)
- 取回: 空手右键 → 掉落对应厨具 → 形态退回上一组合/空 [已实现]
六、古式唱片机 (b_108 无碟 / b_109 有碟)
- 放入: 手持任意原版唱片(minecraft:music_disc_*)右键无碟 → 有碟, 消耗1个, 记录唱片 [已实现]
- 取回: 空手右键 → 无碟 + 掉落该唱片(原样返还, 记录在方块实体数据, 退出重进不丢) [已实现]
- 注: 放入后播放对应唱片曲目 待后续实现 【未做】
七、垃圾桶 (storage_buckets 关闭 / rubbish_bin_2 打开 / b_160 有垃圾)
- 打开: 右键垃圾桶(空手/手持均可) → 打开桶盖(不消耗物品) [已实现]
- 扔垃圾: 打开时手持任意物品右键 → 变为 有垃圾 状态 + 清空主手物品 [已实现]
- 关闭: 右键(空手/手持均可) 打开/有垃圾 → 关闭桶盖 [已实现]
+25
View File
@@ -49,6 +49,11 @@
不会出现"取下"成功提示。这是设计如此, 不是遗漏。 不会出现"取下"成功提示。这是设计如此, 不是遗漏。
烤熟时已有完成提示 "§e烤鲑鱼烤熟了!" 烤熟时已有完成提示 "§e烤鲑鱼烤熟了!"
五、烤串(熟) 直接右键食用
- 方块: 熟牛肉串(beefkebabs_3)、熟羊肉串(lambkebabs_3)、熟蔬菜串(roasted_vegetables_3)、
熟香菇串(roastedmushrooms_3)
- 未做原因: 食用部分由统一的"总食用功能"汇总实现, 本次不实现
(烧烤转化关系.txt 已用 todo 标注 "熟的烤串也都可以直接右键食用")
######################################## ########################################
【死亡生物】(xy_deadmob) 【死亡生物】(xy_deadmob)
@@ -68,3 +73,23 @@
- 方块: fishingnet(网(渔网)) - 方块: fishingnet(网(渔网))
- 设想: 放在水中 → 一段时间后自动捕鱼 → 鱼以掉落物形式出现在渔网上方 - 设想: 放在水中 → 一段时间后自动捕鱼 → 鱼以掉落物形式出现在渔网上方
- 未做原因: 转化文档 todo 已标注"后续实现" (方块工具转化关系.txt 唯一一条 todo) - 未做原因: 转化文档 todo 已标注"后续实现" (方块工具转化关系.txt 唯一一条 todo)
########################################
【家具家私】(xy_furniture)
########################################
一、古式唱片机-播放曲目
- 方块: b_109(古式唱片机有碟)
- 设想: 放入唱片后播放对应唱片曲目
- 未做原因: 放/取碟已实现, 播放曲目标注"后续实现"
(家具家私转化关系.txt 六、古式唱片机 todo 已标注)
二、木条获得
- 设想: 切石机 放入任何原版木板 → 获得4个木条
- 未做原因: 未实现 (家具家私转化关系.txt 静态段 todo 已标注)
三、双人床-双人睡眠
- 方块: b_35(双人床)
- 设想: 床, 但是可以两个人一起睡
- 未做原因: 未实现 (家具家私转化关系.txt 静态段 todo 已标注)
+129 -108
View File
@@ -2,176 +2,197 @@
【家具家私】(xy_furniture) 转化关系 【家具家私】(xy_furniture) 转化关系
======================================== ========================================
说明: 说明:
- 家具家私分类无分组,按标识自动排序 - 家具家私分类方块: 箭桶、水槽、盘子架、书柜、厨具架、唱片机、垃圾桶 系列
- 部分家具有交互转换(箭桶/水槽/盘子架/书柜/壁炉/唱片机/垃圾桶/碗柜) - 有交互转换的: 箭桶(存/取箭)、水槽(加水/取水/放洗脏盘/取脏盘)、
- 其余为静态装饰家具,无转换 盘子架(放/取盘)、书柜(放/取书)、厨具架(放/取厨具)
- 【方块存储物品实现】(FurnitureService 两种记录, 写入方块实体数据, 退出重进不丢):
① count记录(箭桶): 记录一种物品+数量, 字段 rtdd_swt=count / rtdd_swc=数量 /
rtdd_swi=物品名 / rtdd_swa=aux值
② stack记录(书柜): 后放先取堆栈, 记录完整物品信息(含userData, 附魔不丢),
字段 rtdd_swt=stack / rtdd_sws=堆栈列表
- 【实现状态】: 箭桶/水槽/盘子架/书柜/厨具架/古式唱片机/垃圾桶 全部已实现
(统一由 xiangyemod/jiaju/FurnitureService 处理, 注册到 FeatureDispatcher;
旧 BlockSwitchService 已废弃); 唱片播放曲目 待后续实现
- 其余为静态装饰家具, 无转换
======================================== ========================================
一、箭桶(存箭/取箭) 一、箭桶(存箭/取箭)
存储实现: count记录(rtdd_swt=count), 记录一种箭的类型+数量
======================================== ========================================
1. 箭桶_0(无箭) -> 箭桶_1(有箭) 1. 箭桶_0(箭桶无箭) -> 箭桶_1(箭桶有箭)
转换条件:[手持 箭(普通箭/药水箭) 右键 进行转换,批量存入手中所有箭,至多32只,方块记录箭类型与数量] 转换条件:[手持 箭(普通箭/药水箭) 右键 进行转换, 批量存入手中所有箭, 至多32只,
机制: 手持箭右键,批量存入手中所有箭,最多32只,记录类型与数量 方块记录是什么箭多少数量]
机制:
- 手持箭右键, 批量存入手中所有箭, 最多32只(maxCount=32)
(如手持64只毒箭右键→存32只手上剩32只; 手持31只→存31只手上清空)
- 只能存一种箭: 方块内已有箭且与手中箭类型不同(物品名 minecraft:arrow /
minecraft:tipped_arrow 及药水效果aux) 时不能混存, 右键无反应
- 记录: 方块实体数据 rtdd_swt=count / rtdd_swc=数量 / rtdd_swi=箭类型 /
rtdd_swa=药水效果aux, 退出重进不丢
2. 箭桶_1(有箭) -> 箭桶_0(无箭) 2. 箭桶_1(箭桶有箭) -> 箭桶_0(箭桶无箭)
转换条件:[右键 进行转换,一次性取回方块记录的全部箭(类型+数量)] 转换条件:[右键 进行转换, 一次性取回方块记录的全部箭(类型+数量)]
机制: 空手右键,取回全部箭 机制: 空手右键, 按记录一次性取回全部箭(数量+类型+药水效果aux), 并清空记录
======================================== ========================================
二、水槽(加水/放脏盘子/取脏盘子) 二、水槽(加水/取水/放洗脏盘/取脏盘)
======================================== ========================================
3. 水槽_1(空) -> 水槽_2(满水) 3. 水槽_1(水槽_空) -> 水槽_2(水槽_满水)
转换条件:[右键 进行转换(加水)] 转换条件:[右键 进行转换(加水)]
机制: 空手右键,加水 机制: 空手右键, 加水
4. 水槽_2(满水) -> 水槽_1(空) 4. 水槽_2(水槽_满水) -> 水槽_1(水槽_空)
转换条件:[手持 水桶 右键 进行转换,消耗水桶获得装满水的水桶] 转换条件:[手持 水桶 右键 进行转换, 消耗空水桶, 获得装满水的水桶]
机制: 手持水桶右键,水桶变装满水的水桶,取水 机制: 手持空水桶右键, 取水, 消耗空水桶返还装满水的水桶(minecraft:water_bucket)
5. 水槽_2(满水) + 脏盘子 -> 水槽_3(一个脏盘子) 5. 水槽_2(满水) + 脏盘子 -> 水槽_3(一个脏盘子)
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少] 转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
机制: 手持脏盘子右键,消耗1个,放入脏盘子 机制: 手持脏盘子右键, 消耗1个, 放入脏盘子(需水槽有水才能放)
6. 水槽_3(一个) -> 水槽_4(两个) 6. 水槽_3(一个) -> 水槽_4(两个)
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少] 转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
机制: 手持脏盘子右键,消耗1个,放入第二个 机制: 手持脏盘子右键, 消耗1个, 放入第二个
7. 水槽_4(两个) -> 水槽_5(三个) 7. 水槽_4(两个) -> 水槽_5(三个)
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少] 转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
机制: 手持脏盘子右键,消耗1个,放入第三个 机制: 手持脏盘子右键, 消耗1个, 放入第三个
8. 水槽_5(三个) -> 水槽_6(四个) 8. 水槽_5(三个) -> 水槽_6(四个)
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少] 转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
机制: 手持脏盘子右键,消耗1个,放入第四个 机制: 手持脏盘子右键, 消耗1个, 放入第四个
9. 水槽_6(四个) -> 水槽_5(三个) 9. 水槽_6(四个) -> 水槽_5(三个)
转换条件:[右键 进行转换,获得脏盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个脏盘子 机制: 空手右键, 取回1个盘子
10. 水槽_5(三个) -> 水槽_4(两个) 10. 水槽_5(三个) -> 水槽_4(两个)
转换条件:[右键 进行转换,获得脏盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个脏盘子 机制: 空手右键, 取回1个盘子
11. 水槽_4(两个) -> 水槽_3(一个) 11. 水槽_4(两个) -> 水槽_3(一个)
转换条件:[右键 进行转换,获得脏盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个脏盘子 机制: 空手右键, 取回1个盘子
12. 水槽_3(一个) -> 水槽_2(满水) 12. 水槽_3(一个) -> 水槽_1(水槽_空)
转换条件:[右键 进行转换,获得脏盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回最后1个脏盘子 机制: 空手右键, 取回最后1个盘子
======================================== ========================================
三、盘子架(放盘子/取盘子) 三、盘子架(放盘子/取盘子)
======================================== ========================================
13. 盘子架_1(空) -> 盘子架_2(一个) 13. 盘子架_1(空) -> 盘子架_2(一个)
转换条件:[手持 盘子 右键 进行转换,盘子数量减少] 转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
机制: 手持盘子右键,消耗1个,放上盘子 机制: 手持盘子右键, 消耗1个, 放上盘子
14. 盘子架_2(一个) -> 盘子架_3(两个) 14. 盘子架_2(一个) -> 盘子架_3(俩个)
转换条件:[手持 盘子 右键 进行转换,盘子数量减少] 转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
机制: 手持盘子右键,消耗1个,放第二个 机制: 手持盘子右键, 消耗1个, 放第二个
15. 盘子架_3(两个) -> 盘子架_4(三个) 15. 盘子架_3(俩个) -> 盘子架_4(三个)
转换条件:[手持 盘子 右键 进行转换,盘子数量减少] 转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
机制: 手持盘子右键,消耗1个,放第三个 机制: 手持盘子右键, 消耗1个, 放第三个
16. 盘子架_4(三个) -> 盘子架_5(四个) 16. 盘子架_4(三个) -> 盘子架_5(四个)
转换条件:[手持 盘子 右键 进行转换,盘子数量减少] 转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
机制: 手持盘子右键,消耗1个,放第四个 机制: 手持盘子右键, 消耗1个, 放第四个
17. 盘子架_5(四个) -> 盘子架_6(五个) 17. 盘子架_5(四个) -> 盘子架_6(五个)
转换条件:[手持 盘子 右键 进行转换,盘子数量减少] 转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
机制: 手持盘子右键,消耗1个,放第五个 机制: 手持盘子右键, 消耗1个, 放第五个
18. 盘子架_6(五个) -> 盘子架_5(四个) 18. 盘子架_6(五个) -> 盘子架_5(四个)
转换条件:[右键 进行转换,获得盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个盘子 机制: 空手右键, 取回1个盘子
19. 盘子架_5(四个) -> 盘子架_4(三个) 19. 盘子架_5(四个) -> 盘子架_4(三个)
转换条件:[右键 进行转换,获得盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个盘子 机制: 空手右键, 取回1个盘子
20. 盘子架_4(三个) -> 盘子架_3(两个) 20. 盘子架_4(三个) -> 盘子架_3(俩个)
转换条件:[右键 进行转换,获得盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个盘子 机制: 空手右键, 取回1个盘子
21. 盘子架_3(两个) -> 盘子架_2(一个) 21. 盘子架_3(俩个) -> 盘子架_2(一个)
转换条件:[右键 进行转换,获得盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回1个盘子 机制: 空手右键, 取回1个盘子
22. 盘子架_2(一个) -> 盘子架_1(空) 22. 盘子架_2(一个) -> 盘子架_1(空)
转换条件:[右键 进行转换,获得盘子] 转换条件:[右键 进行转换, 获得盘子]
机制: 空手右键,取回最后1个盘子 机制: 空手右键, 取回最后1个盘子
======================================== ========================================
四、书柜(放书/取书,后放先取,至多13本) 四、书柜(放书/取书, 后放先取, 至多13本)
存储实现: stack记录(rtdd_swt=stack), 后放先取堆栈, 保留完整物品信息(含附魔userData)
======================================== ========================================
23. 书柜_1(空) -> 书柜_2(1本) 23. 书柜_1(空) -> 书柜_2(1本)
转换条件:[手持 书/附魔书 右键 进行转换,消耗1本] 转换条件:[手持 书/附魔书 右键 进行转换, 消耗1本]
机制: 手持书右键,消耗1本,放入1本 机制: 手持书(普通书/附魔书)右键, 消耗1本, 放入1本
24. 书柜_2(1本) -> 书柜_3(2本) -> 书柜_4(3本) -> 书柜_5(4本) -> 书柜_6(5本) -> 书柜_7(6本) 24. 书柜_2(1本) -> 书柜_3(2本) -> 书柜_4(3本) -> 书柜_5(4本) -> 书柜_6(5本) -> 书柜_7(6本)
-> 书柜_8(7本) -> 书柜_9(8本) -> 书柜_10(9本) -> 书柜_11(10本) -> 书柜_12(11本) -> 书柜_13(12本) -> 书柜_14(13本) -> 书柜_8(7本) -> 书柜_9(8本) -> 书柜_10(9本) -> 书柜_11(10本) -> 书柜_12(11本)
转换条件:[手持 书/附魔书 右键 依次放入,每本消耗1] -> 书柜_13(12本) -> 书柜_14(13本)
机制: 手持书右键逐本放入,共13本(注:书柜_11(10本)为b_196,书柜_12(11本)为bookcase_3) 转换条件:[手持 书/附魔书 右键 依次放入, 每本消耗1, 至多13本]
机制: 手持书右键逐本放入, 共13级; 方块记录完整物品信息(附魔书附魔不丢)
方块标识对应: 书柜_1(空)=bookcase, 书柜_2(1本)=bookcase_12, 书柜_3(2本)=bookcase_11,
书柜_4(3本)=bookcase_10, 书柜_5(4本)=bookcase_9, 书柜_6(5本)=bookcase_8,
书柜_7(6本)=bookcase_7, 书柜_8(7本)=bookcase_6, 书柜_9(8本)=bookcase_5,
书柜_10(9本)=bookcase_4, 书柜_11(10本)=b_196, 书柜_12(11本)=bookcase_3,
书柜_13(12本)=bookcase_2, 书柜_14(13本)=bookcase_1
25. 书柜_14(13本) -> 书柜_13(12本) -> 书柜_12(11本) -> ... -> 书柜_2(1本) -> 书柜_1(空) 25. 书柜_14(13本) -> 书柜_13(12本) -> 书柜_12(11本) -> 书柜_11(10本) -> 书柜_10(9本)
转换条件:[右键 进行转换,后放先取,取回书] -> 书柜_9(8本) -> 书柜_8(7本) -> 书柜_7(6本) -> 书柜_6(5本) -> 书柜_5(4本)
机制: 空手右键逐本取回,后放先取 -> 书柜_4(3本) -> 书柜_3(2本) -> 书柜_2(1本) -> 书柜_1(空)
转换条件:[右键 进行转换, 后放先取, 取回书]
机制: 空手右键逐本取回, 后放先取(LIFO), 取回的书保留原物品信息(附魔等)
======================================== ========================================
五、壁炉 五、厨具架(放厨具/取厨具, 至多2件)【已实现】
======================================== ========================================
26. 壁炉(空) -> 壁炉(加柴火) - 可放厨具: 锅铲(rtdd:spatula)、锅勺(rtdd:potandspoon)、菜刀(rtdd:cleaver)、炒锅(rtdd:wok)
转换条件:[手持 木条/木块 右键 进行转换,消耗1个] - 放入: 手持厨具右键 厨具架(空/未满) → 放入该厨具, 消耗1个, 厨具架形态变为对应组合
机制: 手持木条右键,消耗1个,加柴火 - 取回: 空手右键 → 取回厨具(后放先取), 厨具架形态退回上一组合/空
- 厨具架方块状态(方块名称即组合, 一块最多挂2件):
27. 壁炉(加柴火) -> 壁炉(点燃) kitchenwarerack = 厨具架(空)
转换条件:[手持 打火石/烈焰弹 右键 进行转换] kitchenwareracka_1 = 厨具架(单锅铲) ← 放1把锅铲
机制: 手持打火石右键不消耗/烈焰弹右键消耗1个,点燃 kitchenwareracka_2 = 厨具架(双锅铲) ← 单锅铲 + 锅铲
kitchenwareracka_3 = 厨具架(锅铲+锅勺) ← 单锅铲 + 锅勺
kitchenwarerackb_1 = 厨具架(单锅勺) ← 放1把锅勺
kitchenwarerackb_2 = 厨具架(双锅勺) ← 单锅勺 + 锅勺
kitchenwarerackc_1 = 厨具架(单炒锅) ← 放1口炒锅
kitchenwarerackc_2 = 厨具架(炒锅+菜刀) ← 单炒锅 + 菜刀
kitchenwarerackd_1 = 厨具架(单菜刀) ← 放1把菜刀
kitchenwarerackd_2 = 厨具架(菜刀+锅铲) ← 单菜刀 + 锅铲
kitchenwarerackd_3 = 厨具架(双菜刀) ← 单菜刀 + 菜刀
kitchenwarerackd_4 = 厨具架(菜刀+锅勺) ← 单菜刀 + 锅勺
======================================== ========================================
六、古式唱片机 六、古式唱片机(放碟/取碟)【已实现】
======================================== ========================================
28. 古式唱片机(无碟) -> 古式唱片机(有碟) - 古式唱片机(无碟) b_108 / 古式唱片机(有碟) b_109
转换条件:[手持 唱片 右键 进行转换,消耗1个] - 放入: 手持任意原版唱片(minecraft:music_disc_*)右键无碟 → 有碟, 消耗1个,
机制: 手持唱片右键,消耗1个,放入唱片 唱片(物品名+aux)写入方块实体数据(rtdd_disc), 退出重进不丢
- 取回: 空手右键 → 无碟 + 掉落该唱片(原样返还)
29. 古式唱片机(有碟) -> 古式唱片机(无碟) todo 放入唱片后 播放对应唱片曲目(后续实现)
转换条件:[右键 进行转换,取回唱片]
机制: 空手右键,取回唱片
======================================== ========================================
七、垃圾桶 七、垃圾桶(开关盖/扔垃圾)【已实现】
======================================== ========================================
30. 垃圾桶(关闭) -> 垃圾桶(打开) - 垃圾桶(关闭) storage_buckets / 垃圾桶(打开) rubbish_bin_2 / 垃圾桶(有垃圾) b_160
转换条件:[右键 进行转换,打开桶盖] - 打开: 右键垃圾桶(空手/手持均可) → 打开桶盖(不消耗物品)
机制: 空手右键,打开桶盖 - 扔垃圾: 打开时手持任意物品右键 → 变为 垃圾桶(有垃圾) 状态 + 清空主手物品
- 关闭: 右键(空手/手持均可) 打开/有垃圾 → 关闭桶盖
31. 垃圾桶(打开) -> 垃圾桶(有垃圾) todo 转换: 垃圾桶(关闭) -> 垃圾桶(打开) -> 垃圾桶(有垃圾) -> 垃圾桶(关闭)
转换条件:[手持 垃圾物品 右键 进行转换,消耗1个] 右键转换
机制: 手持垃圾物品右键,消耗1个,投入垃圾 功能: 打开时 主手拿任何东西右键 都会变成 垃圾桶(有垃圾) 状态 同时清空主手物品 以达到垃圾桶效果
32. 垃圾桶(打开/有垃圾) -> 垃圾桶(关闭)
转换条件:[右键 进行转换,关闭桶盖]
机制: 空手右键,关闭桶盖
======================================== ========================================
八、碗柜 八、静态家具方块(无转换)
======================================== ========================================
33. 碗柜(空) -> 碗柜(一个碗) -> 碗柜(两个碗) -> 碗柜(三个碗) b_102(烟囱)、b_103(烟道)、b_111(吧台)、b_112(台柜)、b_124(格子架)、b_125(格子窗)、
转换条件:[手持 碗/盘子 右键 依次放入] b_127(地图台)、b_15(木条)、b_35(双人床)、b_88(壁炉)、b_196~b_201(书本系列)、
机制: 手持碗右键逐格放入,共三个 bamboomeshfence(竹栏)、cabinet(橱柜(盘子+调料瓶))、cabinet_1(橱柜(仅调料瓶))、
seasoningrack(调料架)、shelving(置物架)、statueofadeity(神像)、stool(桌凳)
34. 碗柜(三个碗) -> 碗柜(两个碗) -> 碗柜(一个碗) -> 碗柜(空) todo 木条的获得: 切石机 放入任何原版木板 即可获得4个木条
转换条件:[右键 依次取出] 双人床 功能:床 但是可以两个人一起睡(后续实现)
机制: 空手右键逐格取出
========================================
九、静态家具方块(无转换)
========================================
b_102(烟囱)、b_103(烟道)、b_111(吧台)、b_112(台柜)、b_124(格子架)、b_125(格子窗)、b_127(地图台)、
b_15(木条_1)、b_35(双人床)、b_88(壁炉)、b_196~b_201(书本系列)、bamboomeshfence(竹栏)、
cabinet(橱柜(盘子+调料瓶))、cabinet_1(橱柜(仅调料瓶))、kitchenwarerack(厨具架)、kitchenwareracka_1~3、
kitchenwarerackb_1/2、kitchenwarerackc_1/2、kitchenwarerackd_1~4、seasoningrack(调料架)、
shelving(置物架)、statueofadeity(神像)、stool(桌凳)
-1
View File
@@ -1,7 +1,6 @@
====================================================================== ======================================================================
功能需求: 功能需求:
做饭工具 物品化 (铲子汤勺盘子)
厨具 按流程替换方块(比如烧烤架点火) 厨具 按流程替换方块(比如烧烤架点火)
切菜 切菜
绷带右键回血 绷带右键回血
File diff suppressed because it is too large. Load diff
-694
View File
@@ -1,694 +0,0 @@
================================================================================
未在菜谱(RecipeData.py)和方块切换(BlockSwitchData.py)中使用的方块列表
全部竖排,逐条列出
================================================================================
说明:
- "菜谱中用过" = 在 RecipeData.py 中作为 source 或 target 出现
- "方块切换中用过" = 在 BlockSwitchData.py 中作为 source 或 target 出现
- 以下列出的方块在两者中均未出现
一、纯装饰/建筑类方块
================================================================================
animalcarcasses (动物骸骨)
apple_leaves (苹果树树叶)
balance (天平)
bamboomeshfence (竹栏)
b_2 (土豆堆_1)
b_3 (鲑鱼架)
b_4 (摊位台)
b_6 (售卖盒_1)
b_12 (西瓜盒_1)
b_14 (木桶_1)
b_16 (土豆盒_1)
b_19 (苹果盒_1)
b_22 (鳕鱼盒_1)
b_23 (鳕鱼堆_1)
b_24 (南瓜盒_1)
b_25 (酒桶_1)
b_26 (鲑鱼钩-禁用)
b_27 (鲑鱼堆_1)
b_28 (黄色布料_2)
b_29 (黄色遮阳布_2)
b_31 (稻草人_1)
b_34 (支杆_1)
b_35 (双人床_1)
b_36 (木堆)
b_37 (木桩)
b_38 (面包堆_1)
b_39 (面包盒_1)
b_40 (胡萝卜堆_1)
b_41 (胡萝卜盒_1)
b_42 (支杆1)
b_43 (支杆_2)
b_44 (支杆_3)
b_45 (支杆_4)
b_51 (蓝色布料)
b_52 (红色布料_1)
b_53 (蓝色遮阳布)
b_54 (红色遮阳布_1)
b_55 (烤羊)
b_56 (烤熟羊)
b_57 (梯子_1)
b_58 (铁叉_1)
b_59 (旗子_1)
b_60 (斜旗子_1)
b_61 (斜旗子_2)
b_62 (旗子_2)
b_63 (旗子_3)
b_65 (烧锅_4)
b_67 (烧锅_5)
b_68 (烧锅_6)
b_69 (烧锅_7)
b_70 (烧锅_8)
b_71 (烧锅_9)
b_72 (烧锅_10)
b_82 (满酒桶_6)
b_83 (满酒桶_7)
b_84 (满酒桶_8)
b_85 (空酒桶)
b_86 (金币堆_1)
b_87 (铜币堆_1)
b_88 (壁炉_1)
b_89 (壁炉_2)
b_90 (壁炉_3)
b_91 (满酒桶_9)
b_92 (满酒桶_10)
b_93 (满酒桶_11)
b_94 (满酒桶_12)
b_95 (满酒桶_13)
b_96 (满酒桶_14)
b_97 (满酒桶_15)
b_98 (满酒桶_16)
b_102 (烟囱)
b_103 (烟道)
b_108 (古式唱片机_1)
b_109 (古式唱片机_2)
b_111 (吧台)
b_112 (台柜)
b_114 (酒杯_1)
b_115 (酒杯_2)
b_116 (酒杯_3)
b_117 (酒杯_4)
b_118 (酒杯_5)
b_121 (靶)
b_124 (格子架)
b_125 (格子窗)
b_126 (酒杯_6)
b_127 (地图台)
b_160 (木桶_2)
b_168 (烧锅_1)
b_169 (烧锅_2)
b_170 (烧锅_3)
b_173 (桃子_1)
b_176 (桃子_2)
b_197 (书本_1)
b_198 (书本_2)
b_199 (书本_3)
b_200 (书本_4)
b_201 (书本_5)
b_215 (木尖桩)
b_234 (酒杯_7)
b_235 (酒杯_8)
b_236 (酒杯_9)
b_238_t_1 (满酒桶_2)
b_238_t_2 (满酒桶_3)
b_238_t_3 (满酒桶_4)
b_239 (满酒桶_5)
b_241 (黄色布料_1)
b_241a (黄色遮阳布_1)
b_256casting_table_11 (铸造台_1)
b_258casting_table_12 (铸造台_2)
b_285 (碎铜矿)
cart (车)
chiliskewer (辣椒串)
cow_skull (牛头骨)
copper_coin_pile (铜币堆_2)
copperingotpile (铜锭堆)
dead_villager (村民尸体)
dead_villagera (烤村民)
double_bed (双人床_2)
fanblades (风车叶)
firewood (柴火_1)
firewood_2 (柴火_2)
fishtank (鱼缸)
flag (旗子_4)
flag_blue_lime_oblique (旗子_5)
flag_brown_blue (旗子_6)
flag_red_orange (旗子_7)
flag_red_orange_oblique (旗子_8)
garlicskewers (大蒜串)
gold_pile (金币堆_2)
goldingotpile (金锭堆)
grapebushes_1 (葡萄藤_1)
grapebushes_2 (葡萄藤_2)
hammer (锤子)
ingotpile (铁锭堆)
ironcoinpile (铁币堆)
ironplace (铁矿)
ladder (梯子_2)
lron_fork (铁叉_2)
magnifyingglass (放大镜)
menus (餐牌)
mortar (石臼)
mug (酒杯_10)
oilcan (油罐)
orange_leaves (橘树树叶)
peach_leaves (桃树树叶)
pinchers (钳子)
platepile (盘子堆)
potandspoon (锅勺)
prop_up (支撑)
scarecrow (稻草人_2)
seasoningrack (调料架)
shelving (置物架)
statueofadeity (神像)
stonemill (石磨)
stool (桌凳)
support_rod (支杆_5)
tent (帐篷_1)
tent_0 (帐篷_2)
vending_desk (售货台)
wheel (车轮)
applebox (货桶(苹果盒))
二、最终菜品展示方块(不参与source/target转换,直接放置使用)
================================================================================
plate_0 (盘子)
plate_0a (脏盘子)
plate_1 (轻炒猪肉_1)
plate_1a (轻炒猪肉_2)
plate_2 (黑椒牛排_1)
plate_2a (黑椒牛排_2)
plate_3 (轻炒羊肉)
plate_4 (大盘鸡_1)
plate_4a (大盘鸡_2)
plate_5 (煎鲑鱼_1)
plate_5a (煎鲑鱼_2)
plate_6 (酱香牛仔骨)
plate_7 (黑椒牛腿)
plate_8 (黄酱牛肉_1)
plate_8a (黄酱牛肉_2)
plate_9 (风味羊腿_1)
plate_9a (风味羊腿_2)
plate_10 (铁板里脊_1)
plate_10a (铁板里脊_2)
plate_11 (碳烤羊排_1)
plate_11a (碳烤羊排_2)
plate_12 (小炒五花肉_1)
plate_12a (小炒五花肉_2)
plate_13 (红烧排骨_1)
plate_13a (红烧排骨_2)
plate_14 (土豆片儿)
plate_15 (酱汁鱼儿)
plate_16 (南瓜派盘)
plate_17 (生鱼片)
plate_18 (西瓜盘)
plate_19 (麻辣兔肉_1)
plate_19a (麻辣兔肉_2)
plate_20 (面包盘)
plate_21 (曲奇盘)
plate_22 (元气鸡蛋)
plate_23 (炒时蔬)
plate_24 (小炒肉)
plate_25 (烧鱼肉)
plate_26 (盘装鱿鱼_1)
plate_26a (盘装鱿鱼_2)
plate_26b (盘装鱿鱼_3)
plate_26c (盘装鱿鱼_4)
plate_26d (盘装鱿鱼_5)
plate_27 (盘装寿司_1)
plate_28 (盘装寿司_2)
plate_29 (盘装寿司_3)
plate_30 (美味饺子_1)
plate_30a (美味饺子_2)
plate_31 (小鲜肉_1)
plate_31a (小鲜肉_2)
plate_32 (麻婆豆腐_1)
plate_32a (麻婆豆腐_2)
plate_33 (炒花蟹_1)
plate_33a (炒花蟹_2)
plate_34 (蛋液半成品)
plate_35 (布丁半成品)
plate_36 (盘装猪排)
plate_37 (盘装薯条)
plate_38 (盘装鸡块)
plate_39 (盘装炸鱼_1)
plate_40 (盘装炸鱼_2)
plate_40a (章鱼小丸子)
plate_41 (红烧鱼)
plate_42 (盘装寿司_4)
plate_43 (黑椒牛柳意面_1)
plate_43a (黑椒牛柳意面_2)
plate_44 (奶油培根意面_1)
plate_44a (奶油培根意面_2)
plate_45 (咖喱鸡肉意面_1)
plate_45a (咖喱鸡肉意面_2)
plate_46 (番茄肉酱意面_1)
plate_46a (番茄肉酱意面_2)
plate_47 (辣子鸡丁_1)
plate_47a (辣子鸡丁_2)
plate_48 (辣子鸡丁盖饭_1)
plate_48a (辣子鸡丁盖饭_2)
plate_49 (天妇罗炸虾)
plate_49a (辣子鸡丁盖饭_3)
plate_50 (鱼块盖饭)
plate_51 (炸饺子)
plate_52 (土肉炒肉)
plate_53 (萝卜炒肉)
plate_54 (香菇滑鸡)
plate_55 (辣子鸡丁盖饭_4)
plate_55a (香菇滑鸡盖饭)
plate_56 (麻婆豆腐盖饭)
roastchickenplace_0 (烤鸡_4)
roastchickenplace_2 (烤鸡_5)
roastchickenplace_3 (烤鸡_6)
roastchickenplace_4 (烤鸡_7)
roastpignew_1 (烤乳猪盘_1)
roastpignew_2 (烤乳猪盘_2)
roastpignew_3 (烤乳猪盘_3)
roastpignew_4 (烤乳猪盘_4)
roastpignew_5 (烤乳猪盘_5)
roastpignew_6 (烤乳猪盘_6)
bread_1a (法棍_2)
bread_1b (法棍_3)
bread_2a (吐司_2)
bread_2b (吐司_3)
bread_3a (可颂_2)
bread_3b (可颂_3)
bread_4a (甜甜圈_2)
bread_4b (甜甜圈_3)
bread_4c (甜甜圈_4)
bread_pile (面包堆_2)
cake_0 (奶油蛋糕)
cake_1 (蜂蜜蛋糕_1)
cake_1a (蜂蜜蛋糕_2)
cake_1b (蜂蜜蛋糕_3)
cake_1c (蜂蜜蛋糕_4)
cake_1d (蜂蜜蛋糕_5)
cake_2 (浆果蛋糕_1)
cake_2a (浆果蛋糕_2)
cake_2b (浆果蛋糕_3)
cake_2c (浆果蛋糕_4)
cake_2d (浆果蛋糕_5)
cake_3 (巧克力蛋糕_1)
cake_3a (巧克力蛋糕_2)
cake_3b (巧克力蛋糕_3)
cake_3c (巧克力蛋糕_4)
cake_3d (巧克力蛋糕_5)
cookedpancake (酱香饼)
cooked_dought (面饼_1)
creampastry_0 (奶油糕点)
shouroubao (肉包子_5)
shoubaozi_2 (肉包子_1)
shoubaozi_3 (肉包子_2)
shoubaozidui_2 (肉包子_3)
shoubaozidui_3 (肉包子_4)
roubaozi_3 (包子_2)
breaddough_1a (法棍面胚_2)
breaddough_4d (甜甜圈_5)
三、店铺方块
================================================================================
四、货桶/售卖箱/盒子变体
================================================================================
carrotbarrel (货桶(胡萝卜))
fishbucket_0 (货桶_1)
fishbucket_1 (货桶(鳕鱼))
fishbucket_2 (货桶(鲑鱼))
orangebarrel (货桶(橘子))
peachbarrel (货桶(桃子))
potatobarrel (货桶(马铃薯))
orangebox (橘子盒)
peachbox (桃子盒)
sales_box_apple (苹果盒_2)
sales_box_bread (面包盒_2)
sales_box_carrot (胡萝卜盒_2)
sales_box_cod (鳕鱼盒_2)
sales_box_potato (土豆盒_2)
sales_box_pumpkin (南瓜盒_2)
sales_box_watermelon (西瓜盒_2)
sellingboxes (售卖盒_2)
五、布料/遮阳布变体
================================================================================
cloth (衣物(布料))
cloth_blue (淡蓝色布料)
cloth_lime (黄绿色布料)
cloth_purple (紫色布料)
cloth_red (红色布料_2)
cloth_yellow (黄色布料_3)
sunshade_cloth_blue (淡蓝色遮阳布)
sunshade_cloth_lime (石灰遮阳布)
sunshade_cloth_purple (紫色遮阳布)
sunshade_cloth_red (红色遮阳布_2)
sunshade_cloth_yellow (黄色遮阳布_3)
sunshaden_cloth_lime (黄绿色遮阳布)
sunshaden_cloth_orange (橙色遮阳布)
六、手办/雕像类
================================================================================
cat_0 (萝卜纸巾猫手办_1)
cat_1 (萝卜纸巾猫手办_2)
cat_2 (萝卜纸巾猫手办_3)
cat_3 (萝卜纸巾猫手办_4)
figurines (赞助者手办_1)
figurines_1 (赞助者手办_2)
figurines_2 (赞助者手办_3)
figurines_3 (赞助者手办_4)
figurines_4 (赞助者手办_5)
figurines_5 (赞助者手办_6)
fox_0 (狐狸手办)
fox_1 (手办_1)
fox_2 (手办_2)
fox_3 (手办_3)
七、其他杂项方块
================================================================================
bountypaper (农贸清单)
bountypaper_1 (悬赏纸_1)
bountypaper_2 (悬赏纸_2)
cookstoves (炊灶)
crushed_copper_ore_1 (铜矿)
crushed_gold_ore (碎金矿)
crushed_gold_ore_1 (金矿)
crushedironplace (碎铁矿)
dead_cow (死牛)
dead_pig (死猪)
dead_sheep (死羊)
dead_squid_1 (死去的鱿鱼_1)
deadsquida (死去的鱿鱼_2)
feces_1 (粪便_1)
feces_2 (粪便_2)
feces_3 (粪便_3)
icelollybox_1 (冰棍盒_1)
icelollybox_2 (冰棍盒_2)
icelollyboxa (冰棍盒_3)
icelollyboxb (冰棍盒_4)
icelollyboxc (冰棍盒_5)
icelollyboxd (冰棍盒_6)
icelollyboxe (冰棍盒_7)
icelollyboxf (冰棍盒_8)
jewelrybox_0 (盲盒)
jewelrybox_1 (开过的箱子)
kitchenwarerack (厨具架_1)
kitchenwareracka_1 (厨具架_2)
kitchenwareracka_2 (厨具架_3)
kitchenwareracka_3 (厨具架_4)
kitchenwarerackb_1 (厨具架_5)
kitchenwarerackb_2 (厨具架_6)
kitchenwarerackc_1 (厨具架_7)
kitchenwarerackc_2 (厨具架_8)
kitchenwarerackd_1 (厨具架_9)
kitchenwarerackd_2 (厨具架_10)
kitchenwarerackd_3 (厨具架_11)
kitchenwarerackd_4 (厨具架_12)
medicinetank (玻璃罐(药罐))
medicinetank_1 (药罐_1)
medicinetank_2 (药罐_2)
medicinetank_3 (药罐_3)
medicinetank_4 (药罐_4)
melonicebox (炒冰盒_4)
orangeicebox (炒冰盒_5)
grapeicebox (炒冰盒_3)
frytheicebox_1 (炒冰盒_2)
rubbish_bin_2 (垃圾桶_1)
storage_buckets (垃圾桶_2)
woodenbox_0 (货桶_2(条箱_0))
woodenbox_1 (条箱_1)
woodenbox_2 (条箱_2)
woodenbox_3 (条箱_3)
woodenbox_4 (条箱_4)
woodenbox_5 (条箱_5)
woodenbox_6 (条箱_6)
woodenbox_7 (条箱_7)
violet_beer_kegs_0 (满酒桶_17)
cabinet (橱柜_1)
cabinet_1 (橱柜_2)
farineblock_1 (面粉_2)
farineblock_2 (面粉_3)
farineblock_3 (面粉_4)
casting_table_1 (铸造台_4)
casting_table_2 (铸造台_10)
casting_table_3 (铸造台_11)
casting_table_4 (铸造台_12)
casting_table_5 (铸造台_13)
casting_table_6 (铸造台_14)
casting_table_7 (铸造台_15)
casting_table_8 (铸造台_16)
casting_table_9 (铸造台_17)
casting_table_10 (铸造台_5)
casting_table_13 (铸造台_6)
casting_table_14 (铸造台_7)
casting_table_15 (铸造台_8)
casting_table_16 (铸造台_9)
八、冰棍/雪糕展示方块
================================================================================
appleicecreamplace (冰棍_2)
apple_popsicleplace (冰棍_1)
grapeicecreamplace (葡萄冰棍_2)
grape_popsicleplace (葡萄冰棍_1)
melonicecreamplace (西瓜冰棍_2)
melon_popsicleplace (西瓜冰棍_1)
orangeicecreamplace (冰棍_3)
orange_popsicle_place (橙子冰棍)
peachicecreamplace (桃子冰棍_2)
peach_popsicleplace (桃子冰棍_1)
popsiclepile_2 (冰棍_4)
popsiclepile_3 (冰棍_5)
九、作物/果实方块
================================================================================
apple_fruit_1 (苹果1)
apple_fruit_2 (苹果2)
apple_fruit_3 (苹果3)
apple_pile (苹果堆)
carrot_pile (胡萝卜堆_2)
cod_pile (鳕鱼堆_2)
egg_pile (鸡蛋堆)
orangepile_0 (橙子堆)
peachpile (桃子堆)
peachpile_0 (桃子堆)
potato_pile (土豆堆_4)
salmon_pile (鲑鱼堆_2)
riceseedlingsa (稻苗_1)
riceseedlingsb (稻苗_2)
riceseedlingsc (稻苗_3)
riceseedlingsd (稻苗_4)
tangerine_0 (橘子_1)
tangerine_1 (橘子_2)
tangerine_2 (橘子_3)
十、寿司/鱼肉展示方块
================================================================================
cutsushi (寿司_9)
codsushi_1 (寿司_1)
codsushi_2 (寿司_2)
codsushi_3 (寿司_3)
codsushi_4 (寿司_4)
codsushi_5 (寿司_5)
codsushi_6 (寿司_6)
codsushicubes_1 (寿司_7)
codsushicubes_2 (寿司_8)
fishmonsushi_6 (寿司_10)
fishsushi_1 (寿司_11)
fishsushi_2 (寿司_12)
fishsushi_3 (寿司_13)
fishsushi_4 (寿司_14)
fishsushi_5 (寿司_15)
fishsushicubes_1 (寿司_16)
fishsushicubes_2 (寿司_17)
nigirisushi (寿司_18)
nigirisushicod_2 (寿司_19)
nigirisushicod_3 (寿司_20)
nigirisushifish_1 (手握鱼寿司_1)
nigirisushifish_2 (寿司_21)
nigirisushifish_3 (寿司_22)
nigirisushifishcod (手握鳕鱼寿司)
nigirisushifishsalmon_1 (手握鲑鱼寿司)
nigirisushisalnom_2 (寿司_23)
nigirisushisalnom_3 (寿司_24)
salmonsushi_1 (寿司_25)
salmonsushi_2 (寿司_26)
salmonsushi_3 (寿司_27)
salmonsushi_4 (寿司_28)
salmonsushi_5 (寿司_29)
salmonsushi_6 (寿司_30)
salmonsushicubes_1 (寿司_31)
salmonsushicubes_2 (寿司_32)
squid_sushi (手握鱼寿司_2)
squidsushi_2 (手握鱿鱼寿司_1)
squidsushi_3 (手握鱿鱼寿司_2)
sushi_0 (寿司_33)
sushi_1 (寿司_34)
sushipile_0 (寿司_35)
sushipile_1 (寿司_36)
sushipile_2 (寿司_37)
sushipile_3 (寿司_38)
sushipile_4 (寿司_39)
sushipile_5 (寿司_40)
十一、面食/汤面展示方块
================================================================================
assortedramen (什锦拉面)
braisednoodles (红烧面)
egg_noodles (鸡蛋面)
mushroomnoodles (菌菇面)
noodle_soup (斋面_1)
noodles_1 (蛋花面_1)
noodles_2 (蛋花面_2)
noodlesoup_1 (猪杂面)
noodlesoup_2 (羊杂面)
noodlesoup_3 (牛杂面)
noodlesoup_4 (菌菇鸡面)
tonkotsu_ramen (豚骨拉面)
vegetable_noodles (斋面_2)
yushengramen (鱼生拉面)
十二、汤碗展示方块
================================================================================
bowlaeggcodsoup (蛋花鳕鱼汤)
bowlalambstew (乱炖羊肉汤)
bowlavegetablelambsoup (清汤羊肉_2)
bowlavegetableleanbroth (青菜瘦肉汤)
bowlasoup (木碗)
bowlacarrotbeefsoup (牛里脊肉汤)
bowlacarrotcodsoup (萝卜鳕鱼汤)
bowlacarrotribs (萝卜排骨汤)
bowlabeefbonebroth (牛骨汤)
bowla_lambchopsoup (萝卜土豆小排汤)
bowlavegetablebeefsoup (蔬菜肉汤)
bowlofmushroomsoup (小鸡炖蘑菇)
bowlofrice (米饭_1)
bowlofrice_2 (米饭_2)
bowlofrice_3 (米饭_3)
bowlofriceegg (蛋炒饭)
bowlatufufish (豆腐鱼汤)
bowla_roujiamo (清汤羊肉_1)
brown_norieggsoup (瘦肉蛋花汤)
browncarrotfishsoupsoup (萝卜鱼骨汤)
brownlambsoupwithturnipssoup (萝卜里脊肉汤)
brownmushroomsoup (棕蘑菇汤)
brownvegetablefishsoup (鱼片汤)
bowlavegetablesstew (蔬菜汤)
十三、其他未在菜谱中使用但存在于zh_CN.lang的方块
================================================================================
barwood (木条_2)
b_15 (木条_1)
b_30 (鲑鱼箱)
brownpigfootsoup (蹄花汤)
cookingdough_0 (煎面团_0)
cookingdough_1 (煎面团_1)
cookingdought_0 (煎面团_0)
cookingdought_1 (煎面团_1)
cookingfish_1 (煎鱼_1)
cookinggarlic (煎大蒜)
cookingmeat_1 (煎肉_1)
cookingpotato (煎土豆)
cookingpotato_1 (煎土豆_1)
cookingrabbit_1 (煎兔肉_1)
cookingscallions (煎葱)
cookingsquid_1 (煎鱿鱼_1)
cookingsquid_2 (煎鱿鱼_2)
cookingvegetables_1 (煎蔬菜_1)
cookedcoddought_2 (面团_1)
cookedcoddought_3 (面团_2)
cookedcoddought_4 (面团_3)
cookedcoddought_5 (面团_4)
cookedcoddought_6 (面团_5)
cookeddought_1 (面团_6)
cookeddought_2 (面团_7)
cookeddought_3 (面团_8)
cookeddought_4 (面团_9)
cookeddought_5 (面团_10)
cookeddought_6 (面团_11)
cookingpancake_2 (煎饼_2)
cutcarrot_0 (胡萝卜_1)
cutcarrot_1 (胡萝卜_2)
cutcarrot_2 (胡萝卜_3)
cutcarrot_3 (胡萝卜_4)
cutcarrot_4 (胡萝卜_5)
cutgarlic (大蒜)
cutpotato_0 (土豆_1)
cutpotato_1 (土豆_2)
cutpotato_2 (土豆_3)
cutpotato_3 (土豆_4)
cutpotato_4 (土豆_5)
cutscallions_1 (葱花_1)
cutscallions_2 (葱花_2)
cutscallions_3 (葱花_3)
cutscallions_4 (葱花_4)
cutvegetable_0 (青菜_1)
cutvegetable_1 (青菜_2)
cutvegetable_2 (青菜_3)
cutvegetable_3 (青菜_4)
dought_2 (面团_13)
dought_3 (面团_14)
dought_4 (面团_15)
dought_5 (面团_16)
dought_6 (面团_17)
dought_7 (面团_18)
dought_8 (加工面饼)
dumplings_5 (饺子皮_4)
dumplings_6 (饺子皮_5)
farineblcok (面粉_1)
fishingnet (网)
fishingnet_1 (渔网)
fruitjuice_0 (玻璃杯)
fruitjuice_1 (西瓜汁)
fruitjuice_2 (橙汁)
fruitjuice_3 (苹果汁)
fruitjuice_4 (桃子汁)
fruitjuice_5 (饮料)
fruitjuice_6 (葡萄汁)
fruitjuice_7 (豆浆)
killcod_0 (死去的鲑鱼_1)
killcod_1 (死去的鳕鱼_1)
killcod_2 (死去的鳕鱼_2)
killfish_0 (死去的鲑鱼_2)
killfish_1 (死去的鲑鱼_3)
killfish_2 (死去的鲑鱼_4)
killfish_3 (死去的鲑鱼_5)
manger_0 (饲料槽_1)
manger_1 (饲料槽_2)
manger_2 (饲料槽_3)
manger_3 (饲料槽_4)
mangers (饲料槽_5)
mangerwater (饲料槽_6)
chickenmanger_0 (鸡饲料盆_1)
chickenmanger_1 (鸡饲料盆_2)
pigmanger_1 (饲料槽_7)
pigmanger_2 (饲料槽_8)
pigmanger_3 (饲料槽_9)
rawdough_0 (生面团_2)
rawdough_1 (生面团_3)
rawdough_2 (生面团_4)
rawdough_3 (生面团_5)
rawdough_4 (生面团_6)
rollingpin_1 (擀面杖_2)
rollingpin_2 (擀面杖_3)
rollingpin_3 (擀面杖_4)
cookstoves (炊灶)
================================================================================
总结:以上方块在 zh_CN.lang 中存在,但均未在 RecipeData.py 和 BlockSwitchData.py
的 source/target 字段中出现。其中大部分是纯装饰方块、食物展示品、作物方块、
寿司展示品、汤碗展示品等,它们可能是通过其他方式(如直接放置物品、
工作台合成等)来使用的,不参与方块交互逻辑。
================================================================================