From 620500ab45ffa4f74e676893a050dfacca61bba6 Mon Sep 17 00:00:00 2001 From: wenhe <1046926120@qq.com> Date: Thu, 3 Sep 2026 17:07:41 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A0=B4=E5=9D=8F=E6=8E=89=E8=90=BD=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E7=89=A9=E5=93=81=20(keyong)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiangyemod/jiaju/FurnitureData.py | 73 ++++- .../jiaju/service/FurnitureService.py | 288 ++++++++++++++---- .../xiangyemod/service/FeatureDispatcher.py | 24 +- .../netease_blocks/b_160.json | 24 +- .../netease_blocks/lotion_0.json | 2 +- .../netease_blocks/lotion_1.json | 2 +- .../netease_blocks/lotion_2.json | 2 +- .../netease_blocks/lotion_3.json | 2 +- tuilun/全部功能做了什么.txt | 24 +- tuilun/家具家私转化关系.txt | 28 +- tuilun/药物系统转化关系.txt | 5 +- 11 files changed, 373 insertions(+), 101 deletions(-) diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/FurnitureData.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/FurnitureData.py index b3c3235..bd6b25c 100644 --- a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/FurnitureData.py +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/FurnitureData.py @@ -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) diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/service/FurnitureService.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/service/FurnitureService.py index 5d0470d..af897d6 100644 --- a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/service/FurnitureService.py +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/jiaju/service/FurnitureService.py @@ -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): """清空主手全部物品(垃圾桶扔垃圾)""" diff --git a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py index 90c4d1c..36b65e8 100644 --- a/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py +++ b/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/xiangyemod/service/FeatureDispatcher.py @@ -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( diff --git a/behavior_pack_v8jz0zSR/netease_blocks/b_160.json b/behavior_pack_v8jz0zSR/netease_blocks/b_160.json index f5539be..fe2450f 100644 --- a/behavior_pack_v8jz0zSR/netease_blocks/b_160.json +++ b/behavior_pack_v8jz0zSR/netease_blocks/b_160.json @@ -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 ] } }, diff --git a/behavior_pack_v8jz0zSR/netease_blocks/lotion_0.json b/behavior_pack_v8jz0zSR/netease_blocks/lotion_0.json index 509e204..54f8eec 100644 --- a/behavior_pack_v8jz0zSR/netease_blocks/lotion_0.json +++ b/behavior_pack_v8jz0zSR/netease_blocks/lotion_0.json @@ -4,7 +4,7 @@ "description": { "identifier": "rtdd:lotion_0", "register_to_creative_menu": true, - "category": "xy_medicine" + "category": "xy_furniture" }, "components": { "netease:aabb": { diff --git a/behavior_pack_v8jz0zSR/netease_blocks/lotion_1.json b/behavior_pack_v8jz0zSR/netease_blocks/lotion_1.json index 9a3785c..2c5ef0c 100644 --- a/behavior_pack_v8jz0zSR/netease_blocks/lotion_1.json +++ b/behavior_pack_v8jz0zSR/netease_blocks/lotion_1.json @@ -4,7 +4,7 @@ "description": { "identifier": "rtdd:lotion_1", "register_to_creative_menu": true, - "category": "xy_medicine" + "category": "xy_furniture" }, "components": { "netease:aabb": { diff --git a/behavior_pack_v8jz0zSR/netease_blocks/lotion_2.json b/behavior_pack_v8jz0zSR/netease_blocks/lotion_2.json index 3df1922..656b6eb 100644 --- a/behavior_pack_v8jz0zSR/netease_blocks/lotion_2.json +++ b/behavior_pack_v8jz0zSR/netease_blocks/lotion_2.json @@ -4,7 +4,7 @@ "description": { "identifier": "rtdd:lotion_2", "register_to_creative_menu": true, - "category": "xy_medicine" + "category": "xy_furniture" }, "components": { "netease:aabb": { diff --git a/behavior_pack_v8jz0zSR/netease_blocks/lotion_3.json b/behavior_pack_v8jz0zSR/netease_blocks/lotion_3.json index 01a499e..7265bea 100644 --- a/behavior_pack_v8jz0zSR/netease_blocks/lotion_3.json +++ b/behavior_pack_v8jz0zSR/netease_blocks/lotion_3.json @@ -4,7 +4,7 @@ "description": { "identifier": "rtdd:lotion_3", "register_to_creative_menu": true, - "category": "xy_medicine" + "category": "xy_furniture" }, "components": { "netease:aabb": { diff --git a/tuilun/全部功能做了什么.txt b/tuilun/全部功能做了什么.txt index 9174f70..efd5b31 100644 --- a/tuilun/全部功能做了什么.txt +++ b/tuilun/全部功能做了什么.txt @@ -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+里面的药水; 厨具架掉空架+厨具) +- 附魔书/药水效果在掉落物中保留(取回时重建, 词条/效果不丢) [已实现] +- 与售卖盒区分: 售卖盒破坏重放仍可右键取物; 书柜这类破坏后记录随方块消失, 重放取不到 [已实现] + diff --git a/tuilun/家具家私转化关系.txt b/tuilun/家具家私转化关系.txt index baab563..d8d65ac 100644 --- a/tuilun/家具家私转化关系.txt +++ b/tuilun/家具家私转化关系.txt @@ -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把锅铲 diff --git a/tuilun/药物系统转化关系.txt b/tuilun/药物系统转化关系.txt index 9d4f50e..fb9d86f 100644 --- a/tuilun/药物系统转化关系.txt +++ b/tuilun/药物系统转化关系.txt @@ -7,8 +7,11 @@ - 【待确认】表示该步骤所需的具体操作或配方尚未最终确定 ======================================== -一、药水架(存/取药水,后放先取,至多3瓶) +一、药水架(存/取药水,后放先取,至多3瓶)【已移至家具分类,见 家具家私转化关系.txt 四b】 ======================================== +说明: 药水架(lotion_0~3)已从药物系统移至家具分类(xy_furniture), 由 FurnitureService 实现: + 手持任意药水右键放入(消耗1瓶,记录药水名+aux), 空手右键后放先取取回(掉落物)。 + 具体规则见 家具家私转化关系.txt 四b、药水架。 1. 药水架_1(空) -> 药水架_2(一瓶药水) 转换条件:[手持 任意药水 右键 进行转换,药水数量减少,方块记录药水类型] 机制: 手持药水右键,消耗1瓶,放入药水并记录类型