物品整理 功能列出
This commit is contained in:
1 parent
49c6f08f57
commit
1972208b56
4 files changed
+447
-93
No files matched your search
+376
-76
@@ -1,45 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
================================================================================
|
||||
通用烹饪引擎(服务端)
|
||||
=====================
|
||||
读取 RecipeData.py 里的配方,自动处理所有"右键方块→消耗物品→替换方块"的逻辑。
|
||||
================================================================================
|
||||
|
||||
设计思路:
|
||||
- 不再为每个方块写一个类,而是写一个通用引擎
|
||||
- 所有配方存在 RecipeData.py,引擎读取配方来执行
|
||||
- 加新菜 = 在 RecipeData.py 加一条配方,这里不用改一行代码
|
||||
这个文件是整个模组最核心的"大脑",所有做菜相关的逻辑都在这里。
|
||||
它不关心具体做什么菜——具体菜谱全在 RecipeData.py 里。
|
||||
它只负责"当玩家对某个方块右键时,查菜谱、耗材料、换方块、记进度"这套通用流程。
|
||||
|
||||
用法:
|
||||
【一句话概括】
|
||||
玩家右键烹饪方块 → 引擎查 RecipeData 有没有匹配的配方 →
|
||||
有就消耗手上物品 → 替换方块/记录翻炒进度 → 完成做菜
|
||||
|
||||
【设计原则】
|
||||
- 加新菜不改代码:在 RecipeData.py 加一条配方就行,这里一行不用动
|
||||
- 所有做菜逻辑一处维护:炒锅、煎锅、油锅、架锅、蒸笼……全走同一个引擎
|
||||
- 翻炒进度存方块里:服务器重启后数据不丢,玩家能继续翻
|
||||
|
||||
【用法】
|
||||
在 modMain.py 里注册这个系统即可:
|
||||
serverApi.RegisterSystem("rtdd", "CookingService",
|
||||
"Script_NeteaseModousIY5aSF.ceshi.service.CookingService.CookingService")
|
||||
|
||||
【事件监听】
|
||||
- ServerBlockUseEvent → 玩家右键方块(做菜主入口)
|
||||
- ServerPlayerTryDestroyBlockEvent → 玩家拆方块(掉落食材)
|
||||
================================================================================
|
||||
"""
|
||||
import time
|
||||
import mod.server.extraServerApi as serverApi
|
||||
|
||||
ServerSystem = serverApi.GetServerSystemCls()
|
||||
comp = serverApi.GetEngineCompFactory()
|
||||
# ---- 网易API基础对象 ----
|
||||
ServerSystem = serverApi.GetServerSystemCls() # 服务端系统基类
|
||||
comp = serverApi.GetEngineCompFactory() # 组件工厂,用来创建各种功能组件
|
||||
|
||||
# 继承 BlueFoxServerSystem 后,LoadMod() 会自动发现并注册本系统
|
||||
# ---- 继承 BlueFox 框架的系统基类 ----
|
||||
# BlueFoxServerSystem 会自动处理 LoadMod / UnloadMod 等生命周期,
|
||||
# 不需要手动写,继承后框架自动发现并注册
|
||||
from ...BlueFoxTools.SpecialFunciton.BlueFoxSystem import BlueFoxServerSystem
|
||||
from ...BlueFoxTools.ToolsConfig import LevelId
|
||||
|
||||
# 方块实体数据操作组件(和老系统 Function.py 用的同一个 LevelId,操作同一份数据)
|
||||
# ---- 方块实体数据组件 ----
|
||||
# 每个方块都可以存一点自定义数据(比如"翻炒了几次"),
|
||||
# 这个组件就是读写这些数据的。LevelId 是全局唯一的标识符,
|
||||
# 所有方块数据统一存在这个 LevelId 下,和之前的 Function.py 用同一个
|
||||
blockEntityDataComp = comp.CreateBlockEntityData(LevelId)
|
||||
|
||||
# 从配方数据文件导入所有配方
|
||||
# ---- 导入所有配方 ----
|
||||
# RECIPES 是一个列表,每条配方是一个 dict,格式见 RecipeData.py 顶部注释
|
||||
from ..RecipeData import RECIPES
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# 工具函数:读取物品 userData 里的嵌套字段
|
||||
# ================================================================================
|
||||
|
||||
def getUserDataField(itemDict, *keys):
|
||||
"""
|
||||
读取物品 dict 里的 userData 嵌套字段(纯本地操作,不调API)。
|
||||
例如 getUserDataField(itemDict, 'rtdd', 'oil') → 返回 itemDict['userData']['rtdd']['oil']
|
||||
如果中间任何一层的 key 不存在,自动创建空 dict。
|
||||
从物品 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, {})
|
||||
@@ -47,41 +84,70 @@ def getUserDataField(itemDict, *keys):
|
||||
return node
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# 烹饪引擎主类
|
||||
# ================================================================================
|
||||
|
||||
class CookingService(BlueFoxServerSystem):
|
||||
"""
|
||||
通用烹饪引擎
|
||||
|
||||
内部数据:
|
||||
【核心数据结构】
|
||||
stirData = {
|
||||
(dim, x, y, z): {
|
||||
"count": int, # 已经翻炒了多少次
|
||||
"lastTime": float, # 上次翻炒的时间戳
|
||||
"finished": bool, # 是否已经熟了
|
||||
"dropIfDone": list, # 熟了掉落什么
|
||||
"dropIfNot": list, # 没熟掉落什么
|
||||
(维度ID, x, y, z): { # 用方块坐标当 key,每个方块独立
|
||||
"count": 整数, # 已经翻炒了多少次(比如翻了3次)
|
||||
"lastTime": 浮点数, # 上次翻炒的时间戳(用于冷却判断)
|
||||
"finished": 布尔值, # 是否已经熟了(熟了才能出锅)
|
||||
"dropIfDone": [物品名列表], # 熟了拆掉方块时掉什么
|
||||
"dropIfNot": [物品名列表], # 没熟拆掉方块时掉什么
|
||||
}
|
||||
}
|
||||
|
||||
这个数据同时存在两处:
|
||||
1. 内存(self.stirData)—— 快,关服就没了
|
||||
2. 方块实体数据(blockEntityData)—— 慢一点,但关服重启后还在
|
||||
每次操作都会同步更新两处,保证重启不丢进度
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, systemName):
|
||||
"""
|
||||
初始化:清空翻炒数据、注册事件监听
|
||||
"""
|
||||
super(CookingService, self).__init__(namespace, systemName)
|
||||
print "===== CookingService init ====="
|
||||
self.stirData = {}
|
||||
self.ListenEvent()
|
||||
self.stirData = {} # 内存中的翻炒数据字典
|
||||
self.ListenEvent() # 注册游戏事件监听
|
||||
|
||||
def Destroy(self):
|
||||
"""
|
||||
系统销毁时取消所有事件监听,防止内存泄漏
|
||||
"""
|
||||
print "===== CookingService Destroy ====="
|
||||
self.UnListenEvent()
|
||||
|
||||
# ============================================================================
|
||||
# 事件注册与注销
|
||||
# ============================================================================
|
||||
|
||||
def ListenEvent(self):
|
||||
"""
|
||||
注册两个游戏事件:
|
||||
1. ServerBlockUseEvent —— 玩家右键方块(做菜、翻炒、加油等)
|
||||
2. ServerPlayerTryDestroyBlockEvent —— 玩家拆方块(拆锅时掉落食材)
|
||||
"""
|
||||
# 事件1:玩家右键方块
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(),
|
||||
serverApi.GetEngineSystemName(),
|
||||
"ServerBlockUseEvent", self, self.OnBlockUse)
|
||||
# 事件2:玩家尝试拆方块
|
||||
self.ListenForEvent(serverApi.GetEngineNamespace(),
|
||||
serverApi.GetEngineSystemName(),
|
||||
"ServerPlayerTryDestroyBlockEvent", self, self.OnDestroy)
|
||||
|
||||
def UnListenEvent(self):
|
||||
"""
|
||||
注销事件监听,和 ListenEvent 一一对应
|
||||
"""
|
||||
self.UnListenForEvent(serverApi.GetEngineNamespace(),
|
||||
serverApi.GetEngineSystemName(),
|
||||
"ServerBlockUseEvent", self, self.OnBlockUse)
|
||||
@@ -89,184 +155,377 @@ class CookingService(BlueFoxServerSystem):
|
||||
serverApi.GetEngineSystemName(),
|
||||
"ServerPlayerTryDestroyBlockEvent", self, self.OnDestroy)
|
||||
|
||||
# ==================== 主入口:玩家右键方块 ====================
|
||||
# ============================================================================
|
||||
# 主入口:玩家右键方块
|
||||
# ============================================================================
|
||||
|
||||
def OnBlockUse(self, args):
|
||||
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)
|
||||
"""
|
||||
玩家右键方块时触发,是整个做菜流程的入口。
|
||||
|
||||
流程:
|
||||
1. 拿到被右键的方块名、玩家ID、方块坐标
|
||||
2. 看玩家手里拿了什么物品
|
||||
3. 拿着"方块名+手里物品名"去 RecipeData 里找匹配的配方
|
||||
4. 找到配方后,判断是"需要翻炒"还是"直接替换"两种类型
|
||||
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) # 完整的唯一标识:维度+坐标
|
||||
|
||||
# ---- 第二步:拿到玩家手里拿的物品 ----
|
||||
itemDict = self.getCarriedItem(playerId)
|
||||
# itemDict 可能是 None(空手),也可能是 dict(手里有东西)
|
||||
# dict 格式:{"newItemName": "rtdd:spatula", "count": 1, "userData": {...}}
|
||||
|
||||
# ---- 第三步:查配方 ----
|
||||
recipe = self.findRecipe(blockName, itemDict)
|
||||
if not recipe:
|
||||
# 没找到匹配的配方 → 不是做菜操作,不拦截,让游戏正常处理
|
||||
return
|
||||
|
||||
# 从内存或方块数据加载翻炒进度
|
||||
# ---- 第四步:加载翻炒进度(如果这个方块之前翻过的话) ----
|
||||
# 先从内存取,内存没有就从方块实体数据加载(服务器重启后恢复)
|
||||
stirInfo = self.stirData.get(key, {})
|
||||
if not stirInfo:
|
||||
# 内存里没有,尝试从方块实体数据加载(服务器重启后恢复数据)
|
||||
stirInfo = self.loadStirData(playerId, pos, dim, recipe)
|
||||
if stirInfo:
|
||||
self.stirData[key] = stirInfo
|
||||
self.stirData[key] = stirInfo # 加载成功后写入内存缓存
|
||||
|
||||
# ---- 第五步:根据配方类型判断 ----
|
||||
# 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 findRecipe(self, blockName, itemDict):
|
||||
"""
|
||||
遍历 RECIPES 列表,找到第一个匹配的配方。
|
||||
|
||||
匹配条件:
|
||||
1. 配方的 source 字段 == 被右键的方块名(比如 "rtdd:wok")
|
||||
2. 如果配方指定了 item 字段,则玩家手里物品必须匹配
|
||||
3. 如果配方 item 是 None,表示空手也能触发(比如点锅盖)
|
||||
|
||||
返回:匹配到的配方 dict,找不到返回 None
|
||||
"""
|
||||
# 提取玩家手里物品的名字(空手时 itemName 为 None)
|
||||
itemName = itemDict.get("newItemName") if itemDict else None
|
||||
|
||||
for recipe in RECIPES:
|
||||
# 条件1:方块名必须匹配
|
||||
if recipe["source"] != blockName:
|
||||
continue
|
||||
# 条件2:物品名必须匹配(None 表示不需要特定物品)
|
||||
if recipe["item"] is not None and recipe["item"] != itemName:
|
||||
continue
|
||||
# 两个条件都满足 → 找到了
|
||||
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. 从内存或方块数据加载当前翻炒进度
|
||||
2. 检查冷却时间,没冷却好就忽略
|
||||
3. 翻炒次数+1,更新进度
|
||||
4. 给玩家发进度提示("翻炒中... 3/10")
|
||||
5. 翻够了 → 标记 finished=True,或者直接替换方块
|
||||
"""
|
||||
now = time.time()
|
||||
|
||||
# ---- 第一步:加载或初始化翻炒数据 ----
|
||||
data = self.stirData.get(key) or self.loadStirData(playerId, pos, dim, recipe)
|
||||
if not data:
|
||||
# 全新的翻炒,初始化数据
|
||||
data = {
|
||||
"count": 0, "lastTime": 0.0, "finished": False,
|
||||
"dropIfDone": recipe.get("dropIfDone", []),
|
||||
"dropIfNot": recipe.get("dropIfNot", [])
|
||||
"count": 0, # 从0次开始
|
||||
"lastTime": 0.0, # 上次翻的时间(初始为0,第一次翻不受冷却限制)
|
||||
"finished": False, # 还没熟
|
||||
"dropIfDone": recipe.get("dropIfDone", []), # 熟了拆掉掉什么
|
||||
"dropIfNot": recipe.get("dropIfNot", []), # 没熟拆掉掉什么
|
||||
}
|
||||
self.stirData[key] = data
|
||||
|
||||
# 冷却检查
|
||||
# ---- 第二步:冷却检查 ----
|
||||
# 防止玩家狂点右键,一秒翻100次直接熟了
|
||||
# stirCd 默认 1.0 秒,即两次翻炒之间至少间隔 1 秒
|
||||
if now - data["lastTime"] < recipe.get("stirCd", 1.0):
|
||||
# 还在冷却中,这次点击被忽略
|
||||
return
|
||||
|
||||
# 翻炒次数+1
|
||||
# ---- 第三步:翻炒次数+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 checkConditions(self, recipe, playerId, pos, dim, itemDict):
|
||||
# 检查下面是否有指定方块
|
||||
"""
|
||||
检查配方的前置条件是否满足。
|
||||
|
||||
目前支持两种条件:
|
||||
1. below:锅下面一格必须是特定方块(比如灶台或营火)
|
||||
2. needOil:玩家手里的物品必须带有油标签
|
||||
|
||||
两个条件都满足才返回 True,有一个不满足就返回 False。
|
||||
"""
|
||||
# ---- 条件1:检查下面是否有指定方块(底座检查) ----
|
||||
if recipe.get("below"):
|
||||
# 下面一格坐标:(x, y-1, z)
|
||||
belowPos = (pos[0], pos[1] - 1, pos[2])
|
||||
blockComp = comp.CreateBlockInfo(playerId)
|
||||
try:
|
||||
belowBlock = blockComp.GetBlockNew(belowPos, dim)
|
||||
# 比较下面方块的名字是否和配方要求的底座一致
|
||||
if belowBlock.get("name") != recipe["below"]:
|
||||
return False
|
||||
except:
|
||||
# 读取失败(比如方块不存在)→ 条件不满足
|
||||
return False
|
||||
|
||||
# 检查物品是否有油标签
|
||||
# ---- 条件2:检查物品是否有油标签 ----
|
||||
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):
|
||||
"""
|
||||
消耗玩家手里的物品(减少数量)。
|
||||
注意:SetEntityItem 接收的 key 是 itemName/auxValue/count,
|
||||
而 GetPlayerItem 返回的 key 是 newItemName/newAuxValue/count。
|
||||
消耗玩家手里物品的指定数量。
|
||||
|
||||
如果数量不够 → 直接清空这个格子
|
||||
如果数量够 → 减少对应数量
|
||||
|
||||
重要:网易API里 GetPlayerItem 返回的 key 是 newItemName/newAuxValue,
|
||||
但 SetEntityItem 接收的 key 是 itemName/auxValue,
|
||||
所以这里需要做字段名转换。
|
||||
"""
|
||||
count = itemDict.get("count", 0)
|
||||
if count <= amount:
|
||||
# 数量不够或刚好用完 → 清空这个格子
|
||||
comp.CreateItem(playerId).SetInvItemNum(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
|
||||
else:
|
||||
# 数量够 → 减少 amount 个
|
||||
itemDict["count"] = count - amount
|
||||
comp.CreateItem(playerId).SetEntityItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
|
||||
{"itemName": itemDict["newItemName"],
|
||||
{"itemName": itemDict["newItemName"], # 注意:这里是 itemName 不是 newItemName
|
||||
"count": itemDict["count"],
|
||||
"auxValue": itemDict.get("newAuxValue", 0)})
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:替换方块
|
||||
# ============================================================================
|
||||
|
||||
def replaceBlock(self, pos, dim, newBlockName, playerId):
|
||||
"""替换方块(对照 BlockBlock.py 标准写法:命名参数 + 带 aux)"""
|
||||
"""
|
||||
把指定位置的方块替换成新方块。
|
||||
|
||||
参数:
|
||||
pos: (x, y, z) 元组
|
||||
dim: 维度ID
|
||||
newBlockName: 新方块的完整ID,如 "rtdd:oilpan"
|
||||
playerId: 玩家ID(用于创建组件)
|
||||
|
||||
对照 BlockBlock.py 的标准写法:
|
||||
- 使用命名参数
|
||||
- oldBlockHandling=0 表示直接替换(不保留旧方块数据)
|
||||
- aux=0 表示默认附加值
|
||||
"""
|
||||
comp.CreateBlockInfo(playerId).SetBlockNew(
|
||||
pos,
|
||||
blockDict={"name": newBlockName, "aux": 0},
|
||||
oldBlockHandling=0,
|
||||
dimensionId=dim)
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法:给玩家物品
|
||||
# ============================================================================
|
||||
|
||||
def giveItem(self, playerId, itemName, count=1):
|
||||
"""给玩家背包里塞一个物品"""
|
||||
"""
|
||||
往玩家背包里塞一个物品。
|
||||
如果背包满了,物品会掉在地上。
|
||||
"""
|
||||
comp.CreateItem(playerId).SpawnItemToPlayerInv(
|
||||
{"itemName": itemName, "count": count, "auxValue": 0}, playerId)
|
||||
|
||||
# ==================== 油标签操作(纯本地 dict 操作,不调 API) ====================
|
||||
# ============================================================================
|
||||
# 油标签操作
|
||||
# ============================================================================
|
||||
|
||||
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
|
||||
# 更新玩家手里的物品(userData 跟着 itemDict 一起写回去)
|
||||
# 同步回玩家手里的物品
|
||||
itemComp = comp.CreateItem(playerId)
|
||||
itemComp.SetEntityItem(
|
||||
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
|
||||
@@ -277,45 +536,70 @@ class CookingService(BlueFoxServerSystem):
|
||||
except:
|
||||
pass
|
||||
|
||||
# ==================== 翻炒数据持久化(存方块里,不存JSON文件) ====================
|
||||
# ============================================================================
|
||||
# 翻炒数据持久化(存方块里,不存JSON文件)
|
||||
# ============================================================================
|
||||
|
||||
def loadStirData(self, playerId, pos, dim, recipe):
|
||||
"""
|
||||
从方块的实体数据里读取翻炒进度。
|
||||
注意:GetBlockEntityData 返回的是 BlockEntityData 对象,不是普通 dict,
|
||||
必须用方括号 [] 取值,不能用 .get()。
|
||||
|
||||
为什么要把翻炒进度存在方块里?
|
||||
→ 因为服务器重启后内存就没了,存在方块实体数据里可以持久化。
|
||||
→ 玩家重启服务器后,之前炒了一半的菜还能继续炒。
|
||||
|
||||
存储的三个字段(存在方块实体数据中):
|
||||
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"]
|
||||
stirCount = blockData["rtdd_sc"] # 翻炒次数
|
||||
return {
|
||||
"count": stirCount,
|
||||
"lastTime": blockData["rtdd_slt"],
|
||||
"finished": blockData["rtdd_sf"],
|
||||
"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):
|
||||
"""
|
||||
把翻炒进度写入方块的实体数据。
|
||||
用方括号 [] 直接赋值,网易API会自动保存。
|
||||
|
||||
每次翻炒后都调用一次,保证数据实时同步。
|
||||
用方括号 [] 直接赋值,网易API会自动保存到方块中。
|
||||
"""
|
||||
try:
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
blockData["rtdd_sc"] = data["count"]
|
||||
blockData["rtdd_slt"] = data["lastTime"]
|
||||
blockData["rtdd_sf"] = data["finished"]
|
||||
blockData["rtdd_sc"] = data["count"] # 保存翻炒次数
|
||||
blockData["rtdd_slt"] = data["lastTime"] # 保存最后翻炒时间
|
||||
blockData["rtdd_sf"] = data["finished"] # 保存是否熟了
|
||||
except:
|
||||
# 如果方块没有实体数据对象,忽略(下次加载会重新初始化)
|
||||
pass
|
||||
|
||||
def clearStirData(self, key, playerId, pos, dim):
|
||||
"""
|
||||
清除方块上的翻炒数据(方块被替换或拆掉时调用)。
|
||||
清除方块上的翻炒数据。
|
||||
|
||||
调用时机:
|
||||
- 方块被替换(比如炒锅变成了成品菜)
|
||||
- 方块被拆掉(玩家把锅拆了)
|
||||
|
||||
同时清除内存和方块实体数据。
|
||||
"""
|
||||
# 清除内存中的数据
|
||||
self.stirData.pop(key, None)
|
||||
# 清除方块实体数据(重置为初始值)
|
||||
try:
|
||||
blockData = blockEntityDataComp.GetBlockEntityData(dim, pos)
|
||||
blockData["rtdd_sc"] = 0
|
||||
@@ -324,33 +608,49 @@ class CookingService(BlueFoxServerSystem):
|
||||
except:
|
||||
pass
|
||||
|
||||
# ==================== 拆方块处理 ====================
|
||||
# ============================================================================
|
||||
# 拆方块处理
|
||||
# ============================================================================
|
||||
|
||||
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", [])
|
||||
dropItems = data.get("dropIfDone", []) # 熟了 → 掉熟的东西
|
||||
else:
|
||||
dropItems = data.get("dropIfNot", [])
|
||||
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)
|
||||
@@ -31,8 +31,8 @@ itemGroup.tabrbulid=乡野-建筑
|
||||
tile.rtdd:codsoup.name=汤锅_30(鳕鱼汤)
|
||||
tile.rtdd:apple_leaves.name=苹果树树叶(苹果树叶)
|
||||
tile.rtdd:lambsoupwithturnips_1.name=汤锅_47(萝卜羊肉汤_1)
|
||||
tile.rtdd:clothbox_1.name=售卖箱_1
|
||||
tile.rtdd:clothbox_2.name=售卖箱_2
|
||||
tile.rtdd:clothbox_1.name=售卖箱_1(深色材质)
|
||||
tile.rtdd:clothbox_2.name=售卖箱_2(正常材质)
|
||||
tile.rtdd:salmonsushicubes_1.name=寿司_31(鲑鱼寿司块_1)
|
||||
tile.rtdd:lambsoupwithturnips_3.name=汤锅_49(萝卜羊肉汤_3)
|
||||
tile.rtdd:plate_8a.name=黄酱牛肉_2(黄酱牛肉)
|
||||
@@ -272,7 +272,7 @@ tile.rtdd:bread_4b.name=甜甜圈_3
|
||||
tile.rtdd:carrotandbeefribs_1.name=煎锅_4(胡萝卜牛排骨_1)
|
||||
tile.rtdd:farineblcok.name=面粉_1(面粉)
|
||||
tile.rtdd:plate_0a.name=脏盘子
|
||||
tile.rtdd:spinner.name=纺纱机_2(纺纱机)
|
||||
tile.rtdd:spinner.name=纺纱机_2(制作完成)
|
||||
tile.rtdd:plate_28.name=盘装寿司_2
|
||||
tile.rtdd:plate_29.name=盘装寿司_3
|
||||
tile.rtdd:plate_26.name=盘装鱿鱼_1
|
||||
@@ -369,7 +369,7 @@ tile.rtdd:vegetableleanbroth_1.name=汤锅_103(蔬菜瘦肉汤_1)
|
||||
tile.rtdd:potandspoon.name=锅勺
|
||||
tile.rtdd:dryingrack_2.name=晾干架_3(晾干架_2)
|
||||
tile.rtdd:b_111.name=吧台
|
||||
tile.rtdd:b_110.name=纺纱机_1
|
||||
tile.rtdd:b_110.name=纺纱机_1(制作)
|
||||
tile.rtdd:hammer.name=锤子
|
||||
tile.rtdd:b_112.name=台柜
|
||||
tile.rtdd:b_115.name=酒杯_2
|
||||
@@ -471,9 +471,9 @@ tile.rtdd:riceseedlingsd.name=稻苗_4(稻苗)
|
||||
tile.rtdd:riceseedlingsb.name=稻苗_2(稻苗)
|
||||
tile.rtdd:riceseedlingsc.name=稻苗_3(稻苗)
|
||||
tile.rtdd:beefbonebroth_3.name=汤锅_6(牛骨汤_3)
|
||||
tile.rtdd:stove_1.name=炉灶_3(炉灶_1)
|
||||
tile.rtdd:stove_1.name=炉灶(炉灶_2)
|
||||
tile.rtdd:riceseedlingsa.name=稻苗_1(稻苗)
|
||||
tile.rtdd:stove_2.name=炉灶_4(炉灶_2)
|
||||
tile.rtdd:stove_2.name=炉灶(炉灶_3)
|
||||
tile.rtdd:noodlesoup_2.name=羊杂面(杂面_2)
|
||||
tile.rtdd:noodlesoup_3.name=牛杂面(杂面_3)
|
||||
tile.rtdd:noodlesoup_4.name=菌菇鸡面(杂面_4)
|
||||
@@ -537,7 +537,7 @@ tile.rtdd:riceegg_2.name=煎锅_86(蛋炒饭_2)
|
||||
tile.rtdd:kitchenwarerackc_1.name=厨具架_7(厨具架_1)
|
||||
tile.rtdd:riceegg_3.name=煎锅_87(蛋炒饭_3)
|
||||
tile.rtdd:scallion_pancake_0.name=面饼_2(葱油饼_0)
|
||||
tile.rtdd:cookstoves.name=炉灶_1(炉灶)
|
||||
tile.rtdd:cookstoves.name=炊灶
|
||||
tile.rtdd:peachicecreamplace.name=桃子冰棍_2(桃子冰棍)
|
||||
tile.rtdd:cloth.name=衣物(布料)
|
||||
tile.rtdd:sulphurore.name=硫矿
|
||||
@@ -655,7 +655,7 @@ tile.rtdd:menus.name=餐牌(菜单)
|
||||
tile.rtdd:carrotandmutton_1.name=煎锅_8(胡萝卜羊肉_1)
|
||||
tile.rtdd:stonemill.name=石磨
|
||||
tile.rtdd:cloth_red.name=红色布料_2(红色布料)
|
||||
tile.rtdd:meathook.name=挂肉钩_11(挂肉钩)
|
||||
tile.rtdd:meathook.name=挂肉钩(空钩)
|
||||
tile.rtdd:meathookcoddry.name=挂肉钩_14(挂干鳕鱼钩)
|
||||
tile.rtdd:woodenbox_0.name=货桶_2(条箱_0)
|
||||
tile.rtdd:woodenbox_3.name=条箱_3
|
||||
@@ -696,9 +696,9 @@ tile.rtdd:yushengramen.name=鱼生拉面
|
||||
tile.rtdd:oilpotfish_2.name=油锅_13(炸鱼块_2)
|
||||
tile.rtdd:bowlacarrotcodsoup.name=萝卜鳕鱼汤
|
||||
tile.rtdd:oilpotfish_1.name=油锅_12(炸鱼块_1)
|
||||
tile.rtdd:hang_meat_hooks_1.name=挂肉钩_7(挂肉钩_1)
|
||||
tile.rtdd:hang_meat_hooks_1.name=挂肉钩_7(鲑鱼挂肉钩_1)
|
||||
tile.rtdd:oilpotfish_0.name=油锅_11(炸鱼块_0)
|
||||
tile.rtdd:hang_meat_hooks_2.name=挂肉钩_8(挂肉钩_2)
|
||||
tile.rtdd:hang_meat_hooks_2.name=挂肉钩_8(鲑鱼挂肉钩_2)
|
||||
tile.rtdd:hang_meat_hooks_3.name=挂肉钩_9(挂肉钩_3)
|
||||
tile.rtdd:hang_meat_hooks_4.name=挂肉钩_10(挂肉钩_4)
|
||||
tile.rtdd:vegetablepotatoesandlambsteaksoup.name=汤锅_106(蔬菜土豆羊排汤)
|
||||
@@ -740,13 +740,13 @@ tile.rtdd:cakebatterblock_0.name=碗装蛋糕半成品_2(蛋糕面糊_0)
|
||||
tile.rtdd:salmonsushi_1.name=寿司_25(鲑鱼寿司_1)
|
||||
tile.rtdd:soybeanseedling_0.name=豆苗_1(豆苗_0)
|
||||
tile.rtdd:soybeanseedling_1.name=豆苗_2(豆苗_1)
|
||||
tile.rtdd:barbecuegrill_0.name=烧烤架_1(烧烤架_0)
|
||||
tile.rtdd:barbecuegrill_0.name=烧烤架_1(烧烤架无火)
|
||||
tile.rtdd:codsushi_1.name=寿司_1(鳕鱼寿司_1)
|
||||
tile.rtdd:cookingscallions.name=煎锅_51(煎葱)
|
||||
tile.rtdd:codsushi_6.name=寿司_6(鳕鱼寿司_6)
|
||||
tile.rtdd:codsushi_4.name=寿司_4(鳕鱼寿司_4)
|
||||
tile.rtdd:codsushi_5.name=寿司_5(鳕鱼寿司_5)
|
||||
tile.rtdd:barbecuegrill_1.name=烧烤架_2(烧烤架_1)
|
||||
tile.rtdd:barbecuegrill_1.name=烧烤架_2(烧烤架点燃)
|
||||
tile.rtdd:codsushi_2.name=寿司_2(鳕鱼寿司_2)
|
||||
tile.rtdd:codsushi_3.name=寿司_3(鳕鱼寿司_3)
|
||||
tile.rtdd:fishbucket_1.name=货桶(鳕鱼)(鱼桶_1)
|
||||
@@ -758,7 +758,7 @@ tile.rtdd:meathookcod_2.name=挂肉钩_13(挂鳕鱼钩_2)
|
||||
tile.rtdd:meathookcod_1.name=挂肉钩_12(挂鳕鱼钩_1)
|
||||
tile.rtdd:roujiamosoup_1.name=汤锅_78(羊肉泡馍汤_1)
|
||||
tile.rtdd:cooking_pancake_1.name=煎锅_18(煎饼_1)
|
||||
tile.rtdd:bandage_2.name=绷带(绷带_2)
|
||||
tile.rtdd:bandage_2.name=绷带
|
||||
tile.rtdd:double_bed.name=双人床_2(双人床)
|
||||
tile.rtdd:orange_leaves.name=橘树树叶
|
||||
tile.rtdd:potatobarrel.name=货桶(马铃薯)(土豆桶)
|
||||
@@ -838,8 +838,8 @@ tile.rtdd:pig_3.name=挂肉钩_18(猪_3)
|
||||
tile.rtdd:pig_2.name=挂肉钩_17(猪_2)
|
||||
tile.rtdd:pig_1.name=挂肉钩_16(猪_1)
|
||||
tile.rtdd:pig_0.name=挂肉钩_15(猪_0)
|
||||
tile.rtdd:arrowbarrel_1.name=箭桶_2(箭桶_1)
|
||||
tile.rtdd:arrowbarrel_0.name=箭桶_1(箭桶_0)
|
||||
tile.rtdd:arrowbarrel_1.name=箭桶_1(箭桶有箭)
|
||||
tile.rtdd:arrowbarrel_0.name=箭桶_0(箭桶无箭)
|
||||
tile.rtdd:cutvegetable_3.name=青菜_4(切青菜_3)
|
||||
tile.rtdd:cutvegetable_2.name=青菜_3(切青菜_2)
|
||||
tile.rtdd:cutvegetable_1.name=青菜_2(切青菜_1)
|
||||
@@ -1026,7 +1026,7 @@ tile.rtdd:sales_box_apple.name=苹果盒_2(苹果盒)
|
||||
tile.rtdd:cloth_blue.name=淡蓝色布料(蓝色布料)
|
||||
tile.rtdd:sales_box_watermelon.name=西瓜盒_2(西瓜盒)
|
||||
tile.rtdd:rawnoodlesoup.name=汤锅_65(生面汤)
|
||||
tile.rtdd:stove.name=炉灶_2(炉灶)
|
||||
tile.rtdd:stove.name=炉灶(炉灶_1)
|
||||
tile.rtdd:b_16.name=土豆盒_1
|
||||
tile.rtdd:b_15.name=木条_1
|
||||
tile.rtdd:b_14.name=木桶_1
|
||||
@@ -1065,7 +1065,7 @@ tile.rtdd:kitchenwarerackd_1.name=厨具架_9(厨具架_1)
|
||||
tile.rtdd:b_29.name=黄色遮阳布_2
|
||||
tile.rtdd:b_28.name=黄色布料_2
|
||||
tile.rtdd:b_27.name=鲑鱼堆_1
|
||||
tile.rtdd:b_26.name=鲑鱼钩
|
||||
tile.rtdd:b_26.name=鲑鱼钩(正面挂)
|
||||
tile.rtdd:b_25.name=酒桶_1
|
||||
tile.rtdd:b_24.name=南瓜盒_1
|
||||
tile.rtdd:icelollybox_1.name=冰棍盒_1
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
做饭工具 物品化 (铲子汤勺)
|
||||
厨具 按流程替换方块(烧烤架点火)
|
||||
底座方块分析(锅下面是灶)
|
||||
菜品做出后方块化
|
||||
盘子 清洗/盛饭/放置
|
||||
附魔书柜 放置/取回
|
||||
切菜
|
||||
|
||||
不知道干嘛用的:
|
||||
铸造台
|
||||
鲑鱼架
|
||||
铁叉
|
||||
绷带
|
||||
搅拌器 10 11 12
|
||||
售卖箱1 2
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
1. 箭桶_0(箭桶无箭) -> 箭桶_1(箭桶有箭)
|
||||
转换条件:[手持 箭(包含药水箭) 右键 进行转换 并且减少手中的箭数量,至多32只箭 方块记录是什么箭多少数量]
|
||||
|
||||
2. 箭桶_1(箭桶有箭) -> 箭桶_0(箭桶无箭0)
|
||||
转换条件:[右键 进行转换 并且增加箭数量,数量为 方块内记录的 是什么箭 有多少数量]
|
||||
|
||||
3. 纺纱机_1(制作) -> 纺纱机_2(制作完成)
|
||||
转换条件:待定 (先设定右键一下转换,手持物品无所谓)
|
||||
|
||||
4.挂肉钩(空钩) -> 挂肉钩_7(鲑鱼挂肉钩_1) ->挂肉钩_8(鲑鱼挂肉钩_2)
|
||||
转换条件:[手持 鲑鱼 右键 进行转换]
|
||||
|
||||
5.挂肉钩_8(鲑鱼挂肉钩_2) -> 挂肉钩_7(鲑鱼挂肉钩_1) -> 挂肉钩(空钩)
|
||||
转换条件:[右键 进行转换,同时获得鲑鱼]
|
||||
|
||||
6.售卖箱_1(深色材质) / 售卖箱_2(正常材质) ->鲑鱼箱
|
||||
转换条件:[手持 鲑鱼 右键 进行转换]
|
||||
|
||||
7.鲑鱼箱 ->售卖箱_2(正常材质)
|
||||
转换条件:[右键 进行转换,同时获得鲑鱼]
|
||||
|
||||
8.烧烤架_1(烧烤架无火) -> 烤烤架_2(烧烤架点燃)
|
||||
转换条件:[打火石/烈焰弹 右键 进行转换]
|
||||
|
||||
9.烧烤架_2(烧烤架点燃) -> 烤烤架_1(烧烤架无火)
|
||||
转换条件:[水桶 右键 进行转换,消耗水桶变成桶]
|
||||
|
||||
10.
|
||||
|
||||
|
||||
|
||||
11
|
||||
|
||||
|
||||
|
||||
12
|
||||
|
||||
|
||||
Reference in new issue
Block a user