家具实现
This commit is contained in:
1 parent
cae9bcb19e
commit
1413a3abc6
19 files changed
+1076
-9480
No files matched your search
@@ -1,6 +1,4 @@
|
||||
from BlueFoxTools.ModLoader import *
|
||||
from xiangyemod.service.CookingService import CookingService
|
||||
from xiangyemod.service.BlockSwitchService import BlockSwitchService
|
||||
from xiangyemod.service.FeatureDispatcher import FeatureDispatcher
|
||||
from xiangyemod.dongwuyangzhi.service.AnimalService import AnimalService
|
||||
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.shichangmaoyi.service.MarketService import MarketService
|
||||
from xiangyemod.yuanbanfangzhi.service.YuanbanService import YuanbanService
|
||||
from xiangyemod.jiaju.service.FurnitureService import FurnitureService
|
||||
|
||||
if not serverApi.IsInServer():
|
||||
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 ====="
|
||||
Whitespace-only changes.
@@ -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 -*-
|
||||
+603
@@ -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 -*-
|
||||
-764
@@ -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)
|
||||
-791
@@ -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)
|
||||
@@ -1029,7 +1029,7 @@ tile.rtdd:sales_box_watermelon.name=售卖盒(西瓜)
|
||||
tile.rtdd:rawnoodlesoup.name=汤锅_65(生面汤)
|
||||
tile.rtdd:stove.name=炉灶(炉灶_空)
|
||||
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_12.name=西瓜盒_1_禁用
|
||||
tile.rtdd:fried_porkwith_potatoes.name=炒锅_64(土豆炒猪肉_生)
|
||||
|
||||
+4
-32
@@ -10,8 +10,8 @@
|
||||
一、章鱼小丸子
|
||||
========================================
|
||||
1. 章鱼小丸子(完整) -> 章鱼小丸子(半份)
|
||||
转换条件:[手持 筷子/刀叉 右键 进行分割]
|
||||
机制: 手持筷子右键,分成半份
|
||||
转换条件:[右键 吃 进行转换]
|
||||
机制: 点击右键,转换为半份的章鱼小丸子 (属于吃的东西,后续实现)
|
||||
|
||||
========================================
|
||||
二、原版食物(静态成品盘)
|
||||
@@ -19,33 +19,5 @@
|
||||
plate_16(南瓜派盘)、plate_17(生鱼片)、plate_18(西瓜盘)、plate_20(面包盘)、plate_21(曲奇盘)、plate_4(熟鸡肉盘)
|
||||
以上均为静态成品,无转化关系
|
||||
|
||||
========================================
|
||||
三、烤制·串烧(与烧烤分类一致,详见《烧烤转化关系.txt》)
|
||||
========================================
|
||||
2. 牛肉串(生) -> 牛肉串(半熟) -> 牛肉串(熟)
|
||||
转换条件:[放到烧烤架,计时自动转换]
|
||||
机制: 手持牛肉串_生右键烧烤架,计时约60秒升一级,共两级烤熟
|
||||
|
||||
3. 羊肉串(生) -> 羊肉串(半熟) -> 羊肉串(熟)
|
||||
转换条件:[放到烧烤架,计时自动转换]
|
||||
机制: 手持羊肉串_生右键烧烤架,计时约60秒升一级,共两级烤熟
|
||||
|
||||
4. 烤蔬菜串(生) -> 烤蔬菜串(半熟) -> 烤蔬菜串(熟)
|
||||
转换条件:[放到烧烤架,计时自动转换]
|
||||
机制: 手持烤蔬菜串_生右键烧烤架,计时约60秒升一级,共两级烤熟
|
||||
|
||||
5. 烤香菇串(生) -> 烤香菇串(半熟) -> 烤香菇串(熟)
|
||||
转换条件:[放到烧烤架,计时自动转换]
|
||||
机制: 手持烤香菇串_生右键烧烤架,计时约60秒升一级,共两级烤熟
|
||||
|
||||
6. 烤鸡/烤乳猪/烤鱼系列
|
||||
转换条件:[与烧烤分类一致,详见《烧烤转化关系.txt》]
|
||||
|
||||
========================================
|
||||
四、其他分组说明
|
||||
========================================
|
||||
- 汤面分组(food_02_rice_noodle): 胡萝卜蔬菜面、胡萝卜蔬菜牛肉面、鸡蛋面、香菇面、熟面条、
|
||||
蛋花面、蛋花面(加葱花)、猪排蔬菜面、羊排蔬菜面、牛骨面、小鸡炖蘑菇面、猪排拉面、蔬菜面、蔬菜鱼片面
|
||||
该分组与汤锅页共用,面条类成品静态方块,无后续转换
|
||||
- 盖饭分组(food_11_rice_meal): 辣子鸡丁盖饭、鳕鱼盖饭、香菇滑鸡盖饭、麻婆豆腐盖饭
|
||||
该分组与炒锅页共用,成品静态方块,无后续转换
|
||||
todo 转换: 拿原版对应食物 右键盘子 进行转换
|
||||
生鱼片 拿 鳕鱼鲑鱼热带鱼右键都可以
|
||||
@@ -185,3 +185,48 @@
|
||||
五、布料已按标注禁用 [已处理]
|
||||
- 售卖盒(黄绿布料 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 有垃圾)
|
||||
- 打开: 右键垃圾桶(空手/手持均可) → 打开桶盖(不消耗物品) [已实现]
|
||||
- 扔垃圾: 打开时手持任意物品右键 → 变为 有垃圾 状态 + 清空主手物品 [已实现]
|
||||
- 关闭: 右键(空手/手持均可) 打开/有垃圾 → 关闭桶盖 [已实现]
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
不会出现"取下"成功提示。这是设计如此, 不是遗漏。
|
||||
烤熟时已有完成提示 "§e烤鲑鱼烤熟了!"
|
||||
|
||||
五、烤串(熟) 直接右键食用
|
||||
- 方块: 熟牛肉串(beefkebabs_3)、熟羊肉串(lambkebabs_3)、熟蔬菜串(roasted_vegetables_3)、
|
||||
熟香菇串(roastedmushrooms_3)
|
||||
- 未做原因: 食用部分由统一的"总食用功能"汇总实现, 本次不实现
|
||||
(烧烤转化关系.txt 已用 todo 标注 "熟的烤串也都可以直接右键食用")
|
||||
|
||||
########################################
|
||||
【死亡生物】(xy_deadmob)
|
||||
@@ -68,3 +73,23 @@
|
||||
- 方块: fishingnet(网(渔网))
|
||||
- 设想: 放在水中 → 一段时间后自动捕鱼 → 鱼以掉落物形式出现在渔网上方
|
||||
- 未做原因: 转化文档 todo 已标注"后续实现" (方块工具转化关系.txt 唯一一条 todo)
|
||||
|
||||
|
||||
########################################
|
||||
【家具家私】(xy_furniture)
|
||||
########################################
|
||||
|
||||
一、古式唱片机-播放曲目
|
||||
- 方块: b_109(古式唱片机有碟)
|
||||
- 设想: 放入唱片后播放对应唱片曲目
|
||||
- 未做原因: 放/取碟已实现, 播放曲目标注"后续实现"
|
||||
(家具家私转化关系.txt 六、古式唱片机 todo 已标注)
|
||||
|
||||
二、木条获得
|
||||
- 设想: 切石机 放入任何原版木板 → 获得4个木条
|
||||
- 未做原因: 未实现 (家具家私转化关系.txt 静态段 todo 已标注)
|
||||
|
||||
三、双人床-双人睡眠
|
||||
- 方块: b_35(双人床)
|
||||
- 设想: 床, 但是可以两个人一起睡
|
||||
- 未做原因: 未实现 (家具家私转化关系.txt 静态段 todo 已标注)
|
||||
+129
-108
@@ -2,176 +2,197 @@
|
||||
【家具家私】(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(有箭)
|
||||
转换条件:[手持 箭(普通箭/药水箭) 右键 进行转换,批量存入手中所有箭,至多32只,方块记录箭类型与数量]
|
||||
机制: 手持箭右键,批量存入手中所有箭,最多32只,记录类型与数量
|
||||
1. 箭桶_0(箭桶无箭) -> 箭桶_1(箭桶有箭)
|
||||
转换条件:[手持 箭(普通箭/药水箭) 右键 进行转换, 批量存入手中所有箭, 至多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(一个脏盘子)
|
||||
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少]
|
||||
机制: 手持脏盘子右键,消耗1个,放入脏盘子
|
||||
转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
|
||||
机制: 手持脏盘子右键, 消耗1个, 放入脏盘子(需水槽有水才能放)
|
||||
|
||||
6. 水槽_3(一个) -> 水槽_4(两个)
|
||||
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少]
|
||||
机制: 手持脏盘子右键,消耗1个,放入第二个
|
||||
转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
|
||||
机制: 手持脏盘子右键, 消耗1个, 放入第二个
|
||||
|
||||
7. 水槽_4(两个) -> 水槽_5(三个)
|
||||
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少]
|
||||
机制: 手持脏盘子右键,消耗1个,放入第三个
|
||||
转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
|
||||
机制: 手持脏盘子右键, 消耗1个, 放入第三个
|
||||
|
||||
8. 水槽_5(三个) -> 水槽_6(四个)
|
||||
转换条件:[手持 脏盘子 右键 进行转换,脏盘子数量减少]
|
||||
机制: 手持脏盘子右键,消耗1个,放入第四个
|
||||
转换条件:[手持 脏盘子 右键 进行转换, 脏盘子数量减少]
|
||||
机制: 手持脏盘子右键, 消耗1个, 放入第四个
|
||||
|
||||
9. 水槽_6(四个) -> 水槽_5(三个)
|
||||
转换条件:[右键 进行转换,获得脏盘子]
|
||||
机制: 空手右键,取回1个脏盘子
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
10. 水槽_5(三个) -> 水槽_4(两个)
|
||||
转换条件:[右键 进行转换,获得脏盘子]
|
||||
机制: 空手右键,取回1个脏盘子
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
11. 水槽_4(两个) -> 水槽_3(一个)
|
||||
转换条件:[右键 进行转换,获得脏盘子]
|
||||
机制: 空手右键,取回1个脏盘子
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
12. 水槽_3(一个) -> 水槽_2(满水)
|
||||
转换条件:[右键 进行转换,获得脏盘子]
|
||||
机制: 空手右键,取回最后1个脏盘子
|
||||
12. 水槽_3(一个) -> 水槽_1(水槽_空)
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回最后1个盘子
|
||||
|
||||
========================================
|
||||
三、盘子架(放盘子/取盘子)
|
||||
========================================
|
||||
13. 盘子架_1(空) -> 盘子架_2(一个)
|
||||
转换条件:[手持 盘子 右键 进行转换,盘子数量减少]
|
||||
机制: 手持盘子右键,消耗1个,放上盘子
|
||||
转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
|
||||
机制: 手持盘子右键, 消耗1个, 放上盘子
|
||||
|
||||
14. 盘子架_2(一个) -> 盘子架_3(两个)
|
||||
转换条件:[手持 盘子 右键 进行转换,盘子数量减少]
|
||||
机制: 手持盘子右键,消耗1个,放第二个
|
||||
14. 盘子架_2(一个) -> 盘子架_3(俩个)
|
||||
转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
|
||||
机制: 手持盘子右键, 消耗1个, 放第二个
|
||||
|
||||
15. 盘子架_3(两个) -> 盘子架_4(三个)
|
||||
转换条件:[手持 盘子 右键 进行转换,盘子数量减少]
|
||||
机制: 手持盘子右键,消耗1个,放第三个
|
||||
15. 盘子架_3(俩个) -> 盘子架_4(三个)
|
||||
转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
|
||||
机制: 手持盘子右键, 消耗1个, 放第三个
|
||||
|
||||
16. 盘子架_4(三个) -> 盘子架_5(四个)
|
||||
转换条件:[手持 盘子 右键 进行转换,盘子数量减少]
|
||||
机制: 手持盘子右键,消耗1个,放第四个
|
||||
转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
|
||||
机制: 手持盘子右键, 消耗1个, 放第四个
|
||||
|
||||
17. 盘子架_5(四个) -> 盘子架_6(五个)
|
||||
转换条件:[手持 盘子 右键 进行转换,盘子数量减少]
|
||||
机制: 手持盘子右键,消耗1个,放第五个
|
||||
转换条件:[手持 盘子 右键 进行转换, 盘子数量减少]
|
||||
机制: 手持盘子右键, 消耗1个, 放第五个
|
||||
|
||||
18. 盘子架_6(五个) -> 盘子架_5(四个)
|
||||
转换条件:[右键 进行转换,获得盘子]
|
||||
机制: 空手右键,取回1个盘子
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
19. 盘子架_5(四个) -> 盘子架_4(三个)
|
||||
转换条件:[右键 进行转换,获得盘子]
|
||||
机制: 空手右键,取回1个盘子
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
20. 盘子架_4(三个) -> 盘子架_3(两个)
|
||||
转换条件:[右键 进行转换,获得盘子]
|
||||
机制: 空手右键,取回1个盘子
|
||||
20. 盘子架_4(三个) -> 盘子架_3(俩个)
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
21. 盘子架_3(两个) -> 盘子架_2(一个)
|
||||
转换条件:[右键 进行转换,获得盘子]
|
||||
机制: 空手右键,取回1个盘子
|
||||
21. 盘子架_3(俩个) -> 盘子架_2(一个)
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回1个盘子
|
||||
|
||||
22. 盘子架_2(一个) -> 盘子架_1(空)
|
||||
转换条件:[右键 进行转换,获得盘子]
|
||||
机制: 空手右键,取回最后1个盘子
|
||||
转换条件:[右键 进行转换, 获得盘子]
|
||||
机制: 空手右键, 取回最后1个盘子
|
||||
|
||||
========================================
|
||||
四、书柜(放书/取书,后放先取,至多13本)
|
||||
四、书柜(放书/取书, 后放先取, 至多13本)
|
||||
存储实现: stack记录(rtdd_swt=stack), 后放先取堆栈, 保留完整物品信息(含附魔userData)
|
||||
========================================
|
||||
23. 书柜_1(空) -> 书柜_2(1本)
|
||||
转换条件:[手持 书/附魔书 右键 进行转换,消耗1本]
|
||||
机制: 手持书右键,消耗1本,放入1本
|
||||
转换条件:[手持 书/附魔书 右键 进行转换, 消耗1本]
|
||||
机制: 手持书(普通书/附魔书)右键, 消耗1本, 放入1本
|
||||
|
||||
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本)
|
||||
转换条件:[手持 书/附魔书 右键 依次放入,每本消耗1]
|
||||
机制: 手持书右键逐本放入,共13本(注:书柜_11(10本)为b_196,书柜_12(11本)为bookcase_3)
|
||||
-> 书柜_8(7本) -> 书柜_9(8本) -> 书柜_10(9本) -> 书柜_11(10本) -> 书柜_12(11本)
|
||||
-> 书柜_13(12本) -> 书柜_14(13本)
|
||||
转换条件:[手持 书/附魔书 右键 依次放入, 每本消耗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. 壁炉(空) -> 壁炉(加柴火)
|
||||
转换条件:[手持 木条/木块 右键 进行转换,消耗1个]
|
||||
机制: 手持木条右键,消耗1个,加柴火
|
||||
|
||||
27. 壁炉(加柴火) -> 壁炉(点燃)
|
||||
转换条件:[手持 打火石/烈焰弹 右键 进行转换]
|
||||
机制: 手持打火石右键不消耗/烈焰弹右键消耗1个,点燃
|
||||
- 可放厨具: 锅铲(rtdd:spatula)、锅勺(rtdd:potandspoon)、菜刀(rtdd:cleaver)、炒锅(rtdd:wok)
|
||||
- 放入: 手持厨具右键 厨具架(空/未满) → 放入该厨具, 消耗1个, 厨具架形态变为对应组合
|
||||
- 取回: 空手右键 → 取回厨具(后放先取), 厨具架形态退回上一组合/空
|
||||
- 厨具架方块状态(方块名称即组合, 一块最多挂2件):
|
||||
kitchenwarerack = 厨具架(空)
|
||||
kitchenwareracka_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. 古式唱片机(无碟) -> 古式唱片机(有碟)
|
||||
转换条件:[手持 唱片 右键 进行转换,消耗1个]
|
||||
机制: 手持唱片右键,消耗1个,放入唱片
|
||||
- 古式唱片机(无碟) b_108 / 古式唱片机(有碟) b_109
|
||||
- 放入: 手持任意原版唱片(minecraft:music_disc_*)右键无碟 → 有碟, 消耗1个,
|
||||
唱片(物品名+aux)写入方块实体数据(rtdd_disc), 退出重进不丢
|
||||
- 取回: 空手右键 → 无碟 + 掉落该唱片(原样返还)
|
||||
|
||||
29. 古式唱片机(有碟) -> 古式唱片机(无碟)
|
||||
转换条件:[右键 进行转换,取回唱片]
|
||||
机制: 空手右键,取回唱片
|
||||
todo 放入唱片后 播放对应唱片曲目(后续实现)
|
||||
|
||||
========================================
|
||||
七、垃圾桶
|
||||
七、垃圾桶(开关盖/扔垃圾)【已实现】
|
||||
========================================
|
||||
30. 垃圾桶(关闭) -> 垃圾桶(打开)
|
||||
转换条件:[右键 进行转换,打开桶盖]
|
||||
机制: 空手右键,打开桶盖
|
||||
|
||||
31. 垃圾桶(打开) -> 垃圾桶(有垃圾)
|
||||
转换条件:[手持 垃圾物品 右键 进行转换,消耗1个]
|
||||
机制: 手持垃圾物品右键,消耗1个,投入垃圾
|
||||
|
||||
32. 垃圾桶(打开/有垃圾) -> 垃圾桶(关闭)
|
||||
转换条件:[右键 进行转换,关闭桶盖]
|
||||
机制: 空手右键,关闭桶盖
|
||||
- 垃圾桶(关闭) storage_buckets / 垃圾桶(打开) rubbish_bin_2 / 垃圾桶(有垃圾) b_160
|
||||
- 打开: 右键垃圾桶(空手/手持均可) → 打开桶盖(不消耗物品)
|
||||
- 扔垃圾: 打开时手持任意物品右键 → 变为 垃圾桶(有垃圾) 状态 + 清空主手物品
|
||||
- 关闭: 右键(空手/手持均可) 打开/有垃圾 → 关闭桶盖
|
||||
todo 转换: 垃圾桶(关闭) -> 垃圾桶(打开) -> 垃圾桶(有垃圾) -> 垃圾桶(关闭)
|
||||
右键转换
|
||||
功能: 打开时 主手拿任何东西右键 都会变成 垃圾桶(有垃圾) 状态 同时清空主手物品 以达到垃圾桶效果
|
||||
|
||||
========================================
|
||||
八、碗柜
|
||||
八、静态家具方块(无转换)
|
||||
========================================
|
||||
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. 碗柜(三个碗) -> 碗柜(两个碗) -> 碗柜(一个碗) -> 碗柜(空)
|
||||
转换条件:[右键 依次取出]
|
||||
机制: 空手右键逐格取出
|
||||
|
||||
========================================
|
||||
九、静态家具方块(无转换)
|
||||
========================================
|
||||
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(桌凳)
|
||||
todo 木条的获得: 切石机 放入任何原版木板 即可获得4个木条
|
||||
双人床 功能:床 但是可以两个人一起睡(后续实现)
|
||||
@@ -1,7 +1,6 @@
|
||||
======================================================================
|
||||
功能需求:
|
||||
|
||||
做饭工具 物品化 (铲子汤勺盘子)
|
||||
厨具 按流程替换方块(比如烧烤架点火)
|
||||
切菜
|
||||
绷带右键回血
|
||||
|
||||
-1353
File diff suppressed because it is too large.
Load diff
-694
@@ -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 字段中出现。其中大部分是纯装饰方块、食物展示品、作物方块、
|
||||
寿司展示品、汤碗展示品等,它们可能是通过其他方式(如直接放置物品、
|
||||
工作台合成等)来使用的,不参与方块交互逻辑。
|
||||
================================================================================
|
||||
Reference in new issue
Block a user