破坏掉落存储物品 (keyong)

This commit is contained in:
wenhe committed 2026-09-03 17:07:41 +08:00
1 parent 0cb1da680d
commit 620500ab45
11 files changed
+373 -101

No files matched your search

@@ -19,7 +19,8 @@
dropItem: 以掉落物形式给的物品 [{"name": "xx", "count": N}]
record: "count"=记录一种物品+数量(箭桶) / "stack"=后放先取堆栈(书柜),
记录写入方块实体数据(rtdd_swt/rtdd_swc/rtdd_swi/rtdd_swa/rtdd_sws),
退出重进不丢
退出重进不丢; 附魔以扁平ench字段([[附魔ID,等级],...])存储,
不存含__type__/__value__的userData(引擎写不进方块实体)
maxCount: record 类型最大容量(箭桶32 / 书柜13)
arrowMode:"store"=箭桶存箭(批量) / "retrieve"=箭桶取箭(一次性全取)
discPut: True=唱片机放入时把唱片写入方块实体(取回时原样返还)
@@ -160,6 +161,43 @@ for _i in range(len(_BOOKCASE_CHAIN) - 1, 0, -1):
"record": "stack", "tip": "§a取回了书",
})
# ============================================================
# 四b、药水架 (lotion 系列, 后放先取, 至多3瓶)
# 放药水(任意药水: 普通/喷溅/滞留 minecraft:potion/splash_potion/lingering_potion):
# 逐个放入, 消耗1瓶, stack记录物品名+aux(aux决定药水效果)
# 取药水: 空手右键, 后放先取(LIFO), 原样返还该瓶药水
# 方块链: 药水架_1(空)=lotion_0, 药水架_2(一瓶)=lotion_1,
# 药水架_3(两瓶)=lotion_2, 药水架_4(三瓶)=lotion_3
# ============================================================
_POTIONRACK_CHAIN = [
"rtdd:lotion_0", # 药水架_1(空)
"rtdd:lotion_1", # 药水架_2(一瓶)
"rtdd:lotion_2", # 药水架_3(两瓶)
"rtdd:lotion_3", # 药水架_4(三瓶)
]
POTIONRACK_RULES = []
# 放入: 第 i 级 -> 第 i+1 级, 任意药水, 消耗1, stack记录
for _i in range(len(_POTIONRACK_CHAIN) - 1):
_src, _dst = _POTIONRACK_CHAIN[_i], _POTIONRACK_CHAIN[_i + 1]
POTIONRACK_RULES.append({
"source": _src, "item": "*potion", "target": _dst,
"consume": 1, "record": "stack", "maxCount": 3,
"tip": "§a放入1瓶药水",
})
# 取回: 第 i 级 -> 第 i-1 级, 空手, 后放先取
for _i in range(len(_POTIONRACK_CHAIN) - 1, 0, -1):
_src, _dst = _POTIONRACK_CHAIN[_i], _POTIONRACK_CHAIN[_i - 1]
POTIONRACK_RULES.append({
"source": _src, "item": None, "target": _dst,
"record": "stack", "tip": "§a取回了1瓶药水",
})
# 满架: 手持药水右键三瓶满的药水架 -> 提示已满, 拦截默认行为(防止误喝药水)
POTIONRACK_RULES.append({
"source": "rtdd:lotion_3", "item": "*potion",
"tip": "§c药水架已满!",
})
# ============================================================
# 五、厨具架 (kitchenwarerack, 至多挂2件)
# 组合由方块状态+stack记录决定, 与放入顺序无关, 取回后放先取。
@@ -203,6 +241,23 @@ KITCHENRACK_COMBO_BLOCK = {
("rtdd:cleaver", "rtdd:cleaver"): "rtdd:kitchenwarerackd_3", # 双菜刀
}
# 方块状态 -> 厨具物品列表(名称即组合, 顺序按方块名从左到右, 最后一件=后放入)
# 用于"无记录"时推断(如创造模式直接放置带厨具的厨具架), 取回按此顺序后放先取
KITCHENRACK_BLOCK_ITEMS = {
"rtdd:kitchenwarerack": [], # 空
"rtdd:kitchenwareracka_1": ["rtdd:spatula"], # 单锅铲
"rtdd:kitchenwareracka_2": ["rtdd:spatula", "rtdd:spatula"], # 双锅铲
"rtdd:kitchenwareracka_3": ["rtdd:spatula", "rtdd:potandspoon"], # 锅铲+锅勺
"rtdd:kitchenwarerackb_1": ["rtdd:potandspoon"], # 单锅勺
"rtdd:kitchenwarerackb_2": ["rtdd:potandspoon", "rtdd:potandspoon"], # 双锅勺
"rtdd:kitchenwarerackc_1": ["rtdd:wok"], # 单炒锅
"rtdd:kitchenwarerackc_2": ["rtdd:wok", "rtdd:cleaver"], # 炒锅+菜刀
"rtdd:kitchenwarerackd_1": ["rtdd:cleaver"], # 单菜刀
"rtdd:kitchenwarerackd_2": ["rtdd:cleaver", "rtdd:spatula"], # 菜刀+锅铲
"rtdd:kitchenwarerackd_3": ["rtdd:cleaver", "rtdd:cleaver"], # 双菜刀
"rtdd:kitchenwarerackd_4": ["rtdd:cleaver", "rtdd:potandspoon"], # 菜刀+锅勺
}
# ============================================================
# 六、古式唱片机 (b_108 无碟 / b_109 有碟)
# 放入: 手持任意原版唱片 -> 有碟, 消耗1, 记录唱片(rtdd_disc)
@@ -239,8 +294,22 @@ TRASHBIN_RULES = [
"target": "rtdd:storage_buckets", "tip": "§a垃圾桶已关闭"},
]
# ============================================================
# 八、左键破坏存物方块的掉落(书柜/药水架/厨具架)
# 存了东西的方块被左键打碎时, 掉 空方块本体 + 全部存储物品(附魔/药水效果保留)。
# 与售卖盒不同: 破坏后记录随方块一起消失, 重新放置右键取不到东西。
# ============================================================
STORAGE_EMPTY_BLOCK = {}
for _b in _BOOKCASE_CHAIN[1:]: # 书柜_2~14(有书) -> 书柜_1(空)
STORAGE_EMPTY_BLOCK[_b] = _BOOKCASE_CHAIN[0]
for _b in _POTIONRACK_CHAIN[1:]: # 药水架_2~4(有药水) -> 药水架_1(空)
STORAGE_EMPTY_BLOCK[_b] = _POTIONRACK_CHAIN[0]
for _b in KITCHENRACK_BLOCKS[1:]: # 厨具架各形态 -> 厨具架(空)
STORAGE_EMPTY_BLOCK[_b] = KITCHENRACK_BLOCKS[0]
# ============================================================
# 汇总: 家具家私右键转换规则(厨具架为通用逻辑, 不在规则表, 单独注册)
# ============================================================
FURNITURE_SWITCHES = (ARROWBARREL_RULES + FLUME_RULES + TRAYRACKS_RULES
+ BOOKCASE_RULES + RECORDPLAYER_RULES + TRASHBIN_RULES)
+ BOOKCASE_RULES + POTIONRACK_RULES
+ RECORDPLAYER_RULES + TRASHBIN_RULES)
@@ -27,8 +27,9 @@
记录写入方块实体数据, 退出重进不丢(rtdd_swt 类型标记):
- count记录(箭桶): 记录一种箭的类型+数量, 字段 rtdd_swt=count /
rtdd_swc=数量 / rtdd_swi=物品名 / rtdd_swa=aux
- stack记录(书柜): 后放先取堆栈, 记录完整物品信息(含userData, 附魔不丢),
字段 rtdd_swt=stack / rtdd_sws=堆栈列表
- stack记录(书柜): 后放先取堆栈, 记录物品信息, 附魔以扁平格式 [[附魔ID,等级],...]
存字段 ench(不存含__type__/__value__的userData, 引擎写不进方块实体),
字段 rtdd_swt=stack / rtdd_sws=堆栈(原始扁平列表存储, 参考 demo.py)
唱片机单独用 rtdd_disc 记录唱片。SetBlockNew 后 block entity 可能未初始化,
写入带 0.1s 延迟兜底。
@@ -41,6 +42,7 @@
================================================================================
"""
import time
import json
import mod.server.extraServerApi as serverApi
# ---- 网易API基础对象 ----
@@ -59,7 +61,8 @@ gameComp = comp.CreateGame(LevelId)
# ---- 只导入家具家私自己的数据库 ----
from ..FurnitureData import (FURNITURE_SWITCHES,
KITCHENRACK_BLOCKS, KITCHENWARE_ITEMS,
KITCHENRACK_SINGLE_BLOCK, KITCHENRACK_COMBO_BLOCK)
KITCHENRACK_SINGLE_BLOCK, KITCHENRACK_COMBO_BLOCK,
KITCHENRACK_BLOCK_ITEMS, STORAGE_EMPTY_BLOCK)
# ---- 统一分发器 ----
from ...service.FeatureDispatcher import FeatureDispatcher
@@ -133,7 +136,7 @@ class FurnitureService(BlueFoxServerSystem):
if blockName in self.kitchenrackBlockSet:
if not self.checkCooldown(key, 0.5):
return True
self._handleKitchenRack(playerId, pos, dim, itemDict, itemName)
self._handleKitchenRack(playerId, pos, dim, itemDict, itemName, blockName)
return True
recipe = self.findRecipe(blockName, itemDict)
@@ -153,6 +156,61 @@ class FurnitureService(BlueFoxServerSystem):
self.handleSwitch(recipe, playerId, pos, dim, itemDict, key)
return True
def handleDestroy(self, args):
"""
由 FeatureDispatcher 转发的"玩家左键破坏方块"入口。
存了东西的家具方块(书柜/药水架/厨具架)被打碎时:
取消默认掉落 → 掉 空方块本体(保留朝向) + 全部存储物品(附魔/药水效果保留)
→ 方块清成空气。
与售卖盒不同: 破坏后记录随方块一起消失, 重新放置右键取不到东西。
返回 True=本功能处理了这次破坏; False=空方块本体/不归本功能管(交还默认掉落)。
"""
# ServerPlayerTryDestroyBlockEvent 的方块名字段是 fullName(不是 blockName)
blockName = args.get("fullName") or args.get("blockName")
emptyBlock = STORAGE_EMPTY_BLOCK.get(blockName)
if not emptyBlock:
return False
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()
# 读存储记录(附魔/药水效果都在里面)
record = self._loadRecord(pos, dim)
stack = list(record.get("stack") or []) if record else []
# 取消默认破坏+默认掉落(不让"有东西的方块"本身掉出来)
args['cancel'] = True
args['spawnResources'] = False
# 记录朝向aux, 再清空方块为空气(aux必须为0)
aux = 0
try:
old = comp.CreateBlockInfo(LevelId).GetBlockNew(pos, dim)
aux = old.get("aux", 0)
except Exception:
aux = 0
try:
comp.CreateBlockInfo(LevelId).SetBlockNew(
pos, blockDict={"name": "minecraft:air", "aux": 0},
oldBlockHandling=0, dimensionId=dim)
except Exception:
pass
# 掉落空方块本体(保留朝向)
try:
self.CreateEngineItemEntity(
{"itemName": emptyBlock, "count": 1, "auxValue": aux},
dim, (x, y + 0.5, z))
except Exception:
pass
# 掉落全部存储物品
for itemObj in stack:
self._dropItemObj(dim, pos, itemObj)
return True
# ============================================================================
# 二、规则匹配(O(1) 字典查找)
# ============================================================================
@@ -185,6 +243,13 @@ class FurnitureService(BlueFoxServerSystem):
if recipe:
return recipe
# 3b. 药水架:手持任意药水(普通/喷溅/滞留)
if itemName in ("minecraft:potion", "minecraft:splash_potion",
"minecraft:lingering_potion"):
recipe = self.furnitureRecipes.get((blockName, "*potion"))
if recipe:
return recipe
# 4. 任意物品匹配(垃圾桶)
if itemName and itemName != "minecraft:air":
recipe = self.furnitureRecipes.get((blockName, "*any"))
@@ -284,29 +349,36 @@ class FurnitureService(BlueFoxServerSystem):
# 四、厨具架通用逻辑(组合由 stack 记录计算, 与放入顺序无关, 取回后放先取)
# ============================================================================
def _handleKitchenRack(self, playerId, pos, dim, itemDict, itemName):
def _handleKitchenRack(self, playerId, pos, dim, itemDict, itemName, blockName):
"""
厨具架:放入/取回通用处理。
放入: 手持 锅铲/锅勺/菜刀/炒锅 右键空架/单件架 -> 按组合(顺序无关)成型
取回: 空手右键 -> 弹出最后放入的厨具并返还, 形态退回剩余单件/空
无记录时(如创造模式直接放置带厨具的厨具架)按方块状态推断厨具。
"""
record = self._loadRecord(pos, dim)
stack = record["stack"] if record else []
if record and record.get("stack"):
stack = record["stack"] # 有记录: 物品对象列表
stackNames = [s.get("itemName") for s in stack]
else:
stack = [] # 无记录: 按方块状态推断
stackNames = list(KITCHENRACK_BLOCK_ITEMS.get(blockName, []))
# ---- 取回: 空手 ----
if not itemName or itemName == "minecraft:air":
if not stack:
if not stackNames:
return
topItem = stack.pop()
self.giveItemObj(playerId, topItem)
poppedName = stackNames.pop() # 后放先取, 弹最后一件
if stack:
target = KITCHENRACK_SINGLE_BLOCK.get(
stack[0]["itemName"], "rtdd:kitchenwarerack")
topItem = stack.pop()
self.giveItemObj(playerId, topItem)
else:
target = "rtdd:kitchenwarerack"
self.giveItem(playerId, poppedName)
# 计算剩余 -> 目标方块, 并保存剩余记录(保证后续取回一致)
target = self._rackTarget(stackNames)
self.replaceBlock(pos, dim, target, playerId)
if stack:
self._saveRecordDelayed(pos, dim, {"stack": stack})
if stackNames:
self._saveRecordDelayed(pos, dim, {"stack": self._namesToObjs(stackNames)})
else:
self._clearRecord(pos, dim)
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a取回了厨具")
@@ -315,25 +387,34 @@ class FurnitureService(BlueFoxServerSystem):
# ---- 放入: 手持厨具 ----
if itemName not in KITCHENWARE_ITEMS:
return
if len(stack) >= 2:
if len(stackNames) >= 2:
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§c厨具架已满!")
return
# 计算组合目标方块(顺序无关: 先放A再放B == 先放B再放A)
newItems = [s["itemName"] for s in stack] + [itemName]
if len(newItems) == 1:
target = KITCHENRACK_SINGLE_BLOCK.get(newItems[0])
else:
target = KITCHENRACK_COMBO_BLOCK.get(tuple(sorted(newItems)))
newItems = stackNames + [itemName]
target = self._rackTarget(newItems)
if not target:
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§c这个搭配放不上厨具架!")
return
# 消耗1个, 压栈并替换
self.consumeItem(playerId, itemDict, 1)
stack.append(self._toItemObj(itemDict))
stackNames.append(itemName)
self.replaceBlock(pos, dim, target, playerId)
self._saveRecordDelayed(pos, dim, {"stack": stack})
self._saveRecordDelayed(pos, dim, {"stack": self._namesToObjs(stackNames)})
comp.CreateMsg(playerId).NotifyOneMessage(playerId, "§a放上了厨具")
def _rackTarget(self, itemNames):
"""根据厨具物品名列表计算厨具架目标方块(0件=空, 1件=单件, 2件=组合, 顺序无关)"""
if not itemNames:
return "rtdd:kitchenwarerack"
if len(itemNames) == 1:
return KITCHENRACK_SINGLE_BLOCK.get(itemNames[0])
return KITCHENRACK_COMBO_BLOCK.get(tuple(sorted(itemNames)))
def _namesToObjs(self, itemNames):
"""把厨具物品名字符串列表转成可持久化的物品对象列表"""
return [{"itemName": n, "count": 1, "aux": 0} for n in itemNames]
# ============================================================================
# 五、切换型规则处理(水槽/盘子架/书柜/唱片机/垃圾桶)
# ============================================================================
@@ -341,16 +422,31 @@ class FurnitureService(BlueFoxServerSystem):
def handleSwitch(self, recipe, playerId, pos, dim, itemDict, key):
"""
执行"右键直接转换"类规则:
书柜取回/唱片机取回 → 先读记录
消耗物品/清空主手 → 替换方块 → 记录放入 → 给/掉物品 → 提示
书柜(记录堆栈): 替换方块会清空方块实体, 所以堆栈必须
"替换前从旧方块读出 → 替换后写回新方块", 否则多本书只留最新一本
唱片机取回: 先读记录
消耗物品/清空主手 → 替换方块 → 给/掉物品 → 提示
"""
# ---- 取回类: 先读记录(此时方块还是满的/有碟的) ----
# ---- 书柜 stack 记录: 替换方块前从旧方块读出堆栈 ----
bookStack = None
droppedBook = None
if recipe.get("record") == "stack":
oldRecord = self._loadRecord(pos, dim)
bookStack = list(oldRecord.get("stack") or []) if oldRecord else []
if recipe.get("item") is None:
# 取回: 后放先取, 弹栈, 以掉落物形式给玩家
if not bookStack:
return
droppedBook = bookStack.pop()
elif itemDict:
# 放入: 压栈(扁平化物品信息, 附魔存ench字段, 取回时重建)
bookStack.append(self._toItemObj(itemDict))
# ---- 唱片机取回: 先读记录(此时方块还是有碟的) ----
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"):
@@ -362,13 +458,20 @@ class FurnitureService(BlueFoxServerSystem):
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 bookStack is not None:
if bookStack:
self._saveRecordDelayed(pos, dim, {"stack": bookStack})
else:
self._clearRecord(pos, dim)
# ---- 唱片机放入 ----
if recipe.get("discPut") and itemDict:
self._writeDiscData(dim, pos, itemDict)
# ---- 掉落物(厨具架取回/唱片取回) ----
# ---- 掉落物(书柜取书/唱片取回) ----
if droppedBook is not None:
self._dropItemObj(dim, pos, droppedBook)
if discToDrop:
self._dropItemDict(dim, pos, discToDrop)
if recipe.get("dropItem"):
@@ -399,24 +502,43 @@ class FurnitureService(BlueFoxServerSystem):
"itemName": blockData["rtdd_swi"],
"aux": blockData["rtdd_swa"]}
if swt == "stack":
return {"stack": blockData["rtdd_sws"]}
raw = blockData["rtdd_sws"]
stack = []
if isinstance(raw, list):
# 当前格式: 原始扁平列表
stack = raw
elif isinstance(raw, basestring):
# 兼容历史遗留的JSON字符串数据
try:
parsed = json.loads(raw)
if isinstance(parsed, list):
stack = parsed
except Exception:
stack = []
# None/异常/损坏数据一律按空堆栈处理, 保证不报错
return {"stack": stack}
return None
except Exception:
return None
def _saveRecord(self, pos, dim, record):
"""把记录写入方块实体数据"""
"""把记录写入方块实体数据(参考 demo.py: GetBlockEntityData 返回对象 [] 赋值即持久化)。
stack 直接存原始扁平列表: 附魔只存[[ID,等级],...]的ench(不含引擎NBT编码的userData),
全部 str/int(无 py2 unicode), 才能被引擎 SetBlockEntityData 可靠写入。"""
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)
blockData["rtdd_swc"] = int(record["count"])
blockData["rtdd_swi"] = str(record.get("itemName", ""))
blockData["rtdd_swa"] = int(record.get("aux", 0))
elif "stack" in record:
blockData["rtdd_swt"] = "stack"
# 直接存原始扁平列表(见 demo.py 参考实现, GetBlockEntityData 的 []
# 赋值即持久化)。不要用 json.dumps: 引擎会把 JSON 字符串反解析回结构,
# 反解析出的 py2 unicode 会被引擎拒绝(SetBlockEntityData 报 type unicode)。
blockData["rtdd_sws"] = record["stack"]
except Exception:
pass
@@ -445,36 +567,49 @@ class FurnitureService(BlueFoxServerSystem):
except Exception:
pass
# ---- 书柜 stack 记录: 压栈/弹栈 ----
# ---- 书柜 stack 记录: 压栈/弹栈 已内联到 handleSwitch(替换前后读写) ----
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 _extractEnch(self, userData):
"""从附魔书 userData 提取附魔为引擎安全格式: [[附魔ID, 等级], ...]。
userData 里的 ench 是引擎 NBT 编码({__type__/__value__}), 直接存方块实体
会触发 SetBlockEntityData 报错(写不进去), 只取 __value__ 里的纯数字。"""
try:
entries = (userData or {}).get("ench") or []
out = []
for e in entries:
eid = int(e.get("id", {}).get("__value__", 0))
lvl = int(e.get("lvl", {}).get("__value__", 1))
out.append([eid, lvl])
return out
except Exception:
return None
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 _buildEnchUserData(self, ench):
"""把引擎安全格式 [[附魔ID, 等级], ...] 重建为引擎 NBT 编码的 userData(附魔书用)"""
entries = []
for pair in ench or []:
entries.append({
"id": {"__type__": 2, "__value__": int(pair[0])},
"lvl": {"__type__": 2, "__value__": int(pair[1])},
"modEnchant": {"__type__": 8, "__value__": ""},
})
return {"ench": entries}
def _toItemObj(self, itemDict):
"""从手持物品 dict 提取可持久化的物品对象"""
return {
"itemName": itemDict.get("newItemName", ""),
"count": itemDict.get("count", 1),
"aux": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {}),
"""从手持物品 dict 提取可持久化的物品对象。
只存扁平字段(itemName/count/aux/ench), 全部转成 str/int:
① 不存含引擎NBT编码(__type__/__value__)的userData —— 引擎写不进方块实体;
② 不存 py2 unicode —— GetPlayerItem 返回的字符串是 unicode, 存方块实体也会被
引擎拒绝(type unicode), 必须 str() 转成字节串。"""
obj = {
"itemName": str(itemDict.get("newItemName") or ""),
"count": int(itemDict.get("count", 1)),
"aux": int(itemDict.get("newAuxValue", 0)),
}
ench = self._extractEnch(itemDict.get("userData"))
if ench:
obj["ench"] = ench
return obj
# ============================================================================
# 六、唱片机唱片记录(rtdd_disc)
@@ -537,14 +672,30 @@ class FurnitureService(BlueFoxServerSystem):
except Exception:
pass
def _dropItemObj(self, dim, pos, itemObj):
"""以掉落物形式生成一个物品(书柜取书, 按扁平ench重建附魔)"""
try:
itemDict = {
"itemName": itemObj["itemName"],
"count": itemObj.get("count", 1),
"auxValue": itemObj.get("aux", 0),
}
ench = itemObj.get("ench")
if ench:
itemDict["userData"] = self._buildEnchUserData(ench)
self.CreateEngineItemEntity(itemDict, dim, (pos[0], pos[1] + 0.5, pos[2]))
except Exception:
pass
# ============================================================================
# 七、工具方法:获取玩家主手物品
# ============================================================================
def getCarriedItem(self, playerId):
itemComp = comp.CreateItem(playerId)
# getUserData=True 必须传, 否则取不到 userData(附魔书词条在 userData 里)
return itemComp.GetPlayerItem(
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0)
serverApi.GetMinecraftEnum().ItemPosType.CARRIED, 0, True)
# ============================================================================
# 八、工具方法:给玩家物品
@@ -561,12 +712,15 @@ class FurnitureService(BlueFoxServerSystem):
pass
def giveItemObj(self, playerId, itemObj):
"""按持久化物品对象给玩家(书柜弹栈取回, 保留附魔)"""
"""按持久化物品对象给玩家(厨具架取回; 附魔书按扁平ench重建附魔)"""
try:
itemDict = {"itemName": itemObj["itemName"],
"count": itemObj.get("count", 1),
"auxValue": itemObj.get("aux", 0)}
if itemObj.get("userData"):
ench = itemObj.get("ench")
if ench:
itemDict["userData"] = self._buildEnchUserData(ench)
elif itemObj.get("userData"):
itemDict["userData"] = itemObj["userData"]
comp.CreateItem(playerId).SpawnItemToPlayerInv(itemDict, playerId)
except Exception:
@@ -590,7 +744,8 @@ class FurnitureService(BlueFoxServerSystem):
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict["count"],
"auxValue": itemDict.get("newAuxValue", 0)})
"auxValue": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {})})
def _consumeCount(self, playerId, itemDict, amount):
"""批量扣减指定数量(箭桶存箭)"""
@@ -607,7 +762,8 @@ class FurnitureService(BlueFoxServerSystem):
serverApi.GetMinecraftEnum().ItemPosType.CARRIED,
{"itemName": itemDict["newItemName"],
"count": itemDict["count"],
"auxValue": itemDict.get("newAuxValue", 0)})
"auxValue": itemDict.get("newAuxValue", 0),
"userData": itemDict.get("userData", {})})
def clearHand(self, playerId):
"""清空主手全部物品(垃圾桶扔垃圾)"""
@@ -287,21 +287,31 @@ class FeatureDispatcher(BlueFoxServerSystem):
def OnDestroy(self, args):
"""
玩家拆方块时触发。
部分"放置的半成品/死鱼"方块被拆掉时, 取消默认掉落(不把方块本身掉出来),
手动清空方块并掉落对应的原版生食材:
- killfish_0(死鲑鱼) → 原版生鲑鱼
- killcod_0(死鳕鱼) → 原版生鳕鱼
- roastchickenplace_0(生鸡肉放置后) → 原版生鸡肉
注意: ServerPlayerTryDestroyBlockEvent 的方块名字段是 fullName(不是 blockName)!
优先级:
1. 有"破坏掉落"逻辑的服务(如家具的存物方块: 书柜/药水架/厨具架
被拆掉 → 掉 空方块本体+全部存储物品, 与售卖盒不同, 破坏后取不到)
2. 静态映射: 放置的死鱼/生鸡肉掉回原版生食材
"""
blockName = args.get("blockName")
blockName = args.get("fullName") or args.get("blockName")
args["blockName"] = blockName # 统一成 blockName 字段, 服务内部直接用
# 1. 优先: 归属的右键服务实现了 handleDestroy → 交给服务处理
service = self.blockOwner.get(blockName)
if service and hasattr(service, "handleDestroy"):
if service.handleDestroy(args):
return
# 2. 静态映射: 放置的死鱼/生鸡肉 → 原版生食材
itemName = self.DESTROY_DROP.get(blockName)
if not itemName:
return
playerId = args.get("playerId")
x, y, z = args.get("x"), args.get("y"), args.get("z")
dim = comp.CreateDimension(playerId).GetEntityDimensionId()
# 取消默认破坏(不让方块本身作为掉落物掉出来)
# 取消默认破坏+默认掉落(不让方块本身作为掉落物掉出来)
args['cancel'] = True
args['spawnResources'] = False
# 手动清空方块
try:
comp.CreateBlockInfo(LevelId).SetBlockNew(
@@ -10,26 +10,26 @@
"netease:aabb": {
"collision": {
"min": [
-0.0625,
0.0,
-0.4533
0,
0,
0.1
],
"max": [
0.875,
1.125,
1.3125
1,
1.1,
1
]
},
"clip": {
"min": [
-0.0625,
0.0,
-0.4533
0,
0,
0.1
],
"max": [
0.875,
1.125,
1.3125
1,
1.1,
1
]
}
},
@@ -4,7 +4,7 @@
"description": {
"identifier": "rtdd:lotion_0",
"register_to_creative_menu": true,
"category": "xy_medicine"
"category": "xy_furniture"
},
"components": {
"netease:aabb": {
@@ -4,7 +4,7 @@
"description": {
"identifier": "rtdd:lotion_1",
"register_to_creative_menu": true,
"category": "xy_medicine"
"category": "xy_furniture"
},
"components": {
"netease:aabb": {
@@ -4,7 +4,7 @@
"description": {
"identifier": "rtdd:lotion_2",
"register_to_creative_menu": true,
"category": "xy_medicine"
"category": "xy_furniture"
},
"components": {
"netease:aabb": {
@@ -4,7 +4,7 @@
"description": {
"identifier": "rtdd:lotion_3",
"register_to_creative_menu": true,
"category": "xy_medicine"
"category": "xy_furniture"
},
"components": {
"netease:aabb": {
+21 -3
View File
@@ -209,9 +209,21 @@ FurnitureData+FurnitureService 实现, 注册到 FeatureDispatcher)
- 取盘子: 空手右键逐个取回 [已实现]
四、书柜 (bookcase 系列, 后放先取, 至多13本)
- 放书: 手持书/附魔书右键逐个放入, 消耗1本, stack记录完整物品信息(附魔不丢,
字段 rtdd_swt=stack, 退出重进不丢) [已实现]
- 取书: 空手右键后放先取(LIFO) [已实现]
- 放书: 手持书/附魔书右键逐个放入, 消耗1本; 堆栈"替换方块前从旧方块读出→替换后写回新方块",
多本连续放入不会丢 [已实现]
- 取书: 空手右键后放先取(LIFO), 取回的书以掉落物形式掉在书柜前(不进背包) [已实现]
- 附魔书: 附魔以扁平ench字段([[附魔ID,等级],...])存方块实体(rtdd_swt=stack/rtdd_sws),
取回时重建userData; 不存含__type__/__value__的userData(引擎写不进方块实体),
退出重进词条不丢(掉落保护仍是掉落保护) [已实现]
- 健壮性: 方块实体里损坏/为None的旧堆栈数据按空堆栈处理, 不再报错(TypeError) [已实现]
四b、药水架 (lotion 系列, 后放先取, 至多3瓶)
- 从药物系统(xy_medicine)移至家具分类(xy_furniture) [已实现]
- 放药水: 手持任意药水(普通/喷溅/滞留)右键逐个放入, 消耗1瓶, 记录药水名+aux(aux决定效果) [已实现]
- 取药水: 空手右键后放先取(LIFO), 取回的药水以掉落物形式掉在架前(原样返还, 含aux效果) [已实现]
- 满架: 手持药水右键三瓶满的药水架 → 提示"已满", 拦截默认行为(防止误喝药水) [已实现]
- 方块链: 药水架_1(空)=lotion_0, 药水架_2(一瓶)=lotion_1, 药水架_3(两瓶)=lotion_2,
药水架_4(三瓶)=lotion_3 [已实现]
五、厨具架 (kitchenwarerack 系列, 组合顺序无关, 后放先取, 至多2件)
- 放入: 手持 锅铲/锅勺/菜刀/炒锅 右键空架/未满架 → 消耗1个 → 按组合成型 [已实现]
@@ -230,3 +242,9 @@ FurnitureData+FurnitureService 实现, 注册到 FeatureDispatcher)
- 扔垃圾: 打开时手持任意物品右键 → 变为 有垃圾 状态 + 清空主手物品 [已实现]
- 关闭: 右键(空手/手持均可) 打开/有垃圾 → 关闭桶盖 [已实现]
八、左键破坏存物方块掉落 (书柜/药水架/厨具架)
- 存了东西的家具方块被左键打碎时: 取消默认掉落 → 掉 空方块本体(保留朝向) + 全部存储物品 [已实现]
(书柜掉空书柜bookcase+里面的书; 药水架掉空药水架lotion_0+里面的药水; 厨具架掉空架+厨具)
- 附魔书/药水效果在掉落物中保留(取回时重建, 词条/效果不丢) [已实现]
- 与售卖盒区分: 售卖盒破坏重放仍可右键取物; 书柜这类破坏后记录随方块消失, 重放取不到 [已实现]
+22 -6
View File
@@ -2,17 +2,20 @@
【家具家私】(xy_furniture) 转化关系
========================================
说明:
- 家具家私分类方块: 箭桶、水槽、盘子架、书柜、厨具架、唱片机、垃圾桶 系列
- 家具家私分类方块: 箭桶、水槽、盘子架、书柜、药水架、厨具架、唱片机、垃圾桶 系列
- 有交互转换的: 箭桶(存/取箭)、水槽(加水/取水/放洗脏盘/取脏盘)、
盘子架(放/取盘)、书柜(放/取书)、厨具架(放/取厨具)
盘子架(放/取盘)、书柜(放/取书)、药水架(放/取药水)、厨具架(放/取厨具)
- 【方块存储物品实现】(FurnitureService 两种记录, 写入方块实体数据, 退出重进不丢):
① count记录(箭桶): 记录一种物品+数量, 字段 rtdd_swt=count / rtdd_swc=数量 /
rtdd_swi=物品名 / rtdd_swa=aux值
② stack记录(书柜): 后放先取堆栈, 记录完整物品信息(含userData, 附魔不丢),
字段 rtdd_swt=stack / rtdd_sws=堆栈列表
- 【实现状态】: 箭桶/水槽/盘子架/书柜/厨具架/古式唱片机/垃圾桶 全部已实现
② stack记录(书柜/药水架): 后放先取堆栈, 记录物品名+aux(药水)或附魔ench(书),
字段 rtdd_swt=stack / rtdd_sws=堆栈(原始扁平列表, 不存引擎NBT编码的userData)
- 【实现状态】: 箭桶/水槽/盘子架/书柜/药水架/厨具架/古式唱片机/垃圾桶 全部已实现
(统一由 xiangyemod/jiaju/FurnitureService 处理, 注册到 FeatureDispatcher;
旧 BlockSwitchService 已废弃); 唱片播放曲目 待后续实现
- 【左键破坏掉落】: 书柜/药水架/厨具架这类"存了东西"的方块被打碎时, 掉 空方块本体
+ 全部存储物品(附魔/药水效果保留); 与售卖盒不同: 售卖盒破坏重放仍可右键取物,
这类方块破坏后记录随方块消失, 重放取不到
- 其余为静态装饰家具, 无转换
========================================
@@ -147,7 +150,19 @@
-> 书柜_9(8本) -> 书柜_8(7本) -> 书柜_7(6本) -> 书柜_6(5本) -> 书柜_5(4本)
-> 书柜_4(3本) -> 书柜_3(2本) -> 书柜_2(1本) -> 书柜_1(空)
转换条件:[右键 进行转换, 后放先取, 取回书]
机制: 空手右键逐本取回, 后放先取(LIFO), 取回的书保留原物品信息(附魔等)
机制: 空手右键逐本取回, 后放先取(LIFO), 取回的书以掉落物形式掉在书柜前(不进背包),
保留附魔(附魔以扁平ench字段存方块实体, 取回时重建userData, 退出重进词条不丢)
========================================
四b、药水架(放药水/取药水, 后放先取, 至多3瓶)【已实现】
========================================
- 从药物系统(xy_medicine)移至家具分类(xy_furniture)
- 可放药水: 任意药水(普通/喷溅/滞留 minecraft:potion / splash_potion / lingering_potion)
- 放入: 手持任意药水右键 药水架(空/未满) → 消耗1瓶, 记录药水名+aux(aux决定药水效果)
- 取回: 空手右键 → 后放先取(LIFO), 取回最后放入的那瓶药水(掉落物形式, 原样返还)
- 满架: 手持药水右键三瓶满的药水架 → 提示"已满", 拦截默认行为(防止误喝药水)
- 方块链: 药水架_1(空)=lotion_0, 药水架_2(一瓶)=lotion_1,
药水架_3(两瓶)=lotion_2, 药水架_4(三瓶)=lotion_3
========================================
五、厨具架(放厨具/取厨具, 至多2件)【已实现】
@@ -156,6 +171,7 @@
- 放入: 手持厨具右键 厨具架(空/未满) → 放入该厨具, 消耗1个, 厨具架形态变为对应组合
(组合与放入顺序无关: 先放锅再放刀 == 先放刀再放锅, 都是 炒锅+菜刀)
- 取回: 空手右键 → 后放先取(LIFO), 取回最后放入的厨具, 形态退回剩余单件/空
(创造模式等直接放置的"带厨具"厨具架, 无记录时按方块状态推断厨具, 同样可右键取回)
- 厨具架方块状态(方块名称即组合, 一块最多挂2件):
kitchenwarerack = 厨具架(空)
kitchenwareracka_1 = 厨具架(单锅铲) ← 放1把锅铲
+4 -1
View File
@@ -7,8 +7,11 @@
- 【待确认】表示该步骤所需的具体操作或配方尚未最终确定
========================================
一、药水架(存/取药水,后放先取,至多3瓶)
一、药水架(存/取药水,后放先取,至多3瓶)【已移至家具分类,见 家具家私转化关系.txt 四b】
========================================
说明: 药水架(lotion_0~3)已从药物系统移至家具分类(xy_furniture), 由 FurnitureService 实现:
手持任意药水右键放入(消耗1瓶,记录药水名+aux), 空手右键后放先取取回(掉落物)。
具体规则见 家具家私转化关系.txt 四b、药水架。
1. 药水架_1(空) -> 药水架_2(一瓶药水)
转换条件:[手持 任意药水 右键 进行转换,药水数量减少,方块记录药水类型]
机制: 手持药水右键,消耗1瓶,放入药水并记录类型