# -*- 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 # ================================================================================ # 工具函数:读取物品 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 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)