Files
rtdd/behavior_pack_v8jz0zSR/Script_NeteaseModousIY5aSF/ceshi/service/BlockSwitchService.py
T
2026-08-06 11:37:28 +08:00

771 lines
35 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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)
print "[BlockSwitchService] _writeBlockData GetBlockEntityData returned: %s (pos=%s dim=%s)" % (type(blockData).__name__ if blockData is not None else "None", pos, dim)
if blockData is None:
print "[BlockSwitchService] _writeBlockData blockData is None: pos=%s dim=%s" % (pos, dim)
return
for key, value in kwargs.items():
print "[BlockSwitchService] _writeBlockData setting: [%s]=%s (type=%s)" % (key, value, type(value).__name__)
blockData[key] = value
# 写入后立即读取验证
verifyData = blockEntityDataComp.GetBlockEntityData(dim, pos)
if verifyData:
for key in kwargs:
try:
val = verifyData[key]
print "[BlockSwitchService] _writeBlockData verify: [%s]=%s" % (key, val)
except:
print "[BlockSwitchService] _writeBlockData verify: [%s]=<KEY_ERROR>" % key
else:
print "[BlockSwitchService] _writeBlockData verify FAILED: blockData is None after write!"
except Exception as e:
import traceback
print "[BlockSwitchService] _writeBlockData 写入失败: pos=%s dim=%s err=%s trace=%s" % (pos, dim, str(e), traceback.format_exc())
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 还能从方块里把物品读回来。
"""
print "[BlockSwitchService] saveSwitchRecord pos=%s dim=%s record=%s" % (pos, dim, record)
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)
print "[BlockSwitchService] loadSwitchRecord GetBlockEntityData returned: %s (pos=%s dim=%s)" % (type(blockData).__name__ if blockData is not None else "None", pos, dim)
if blockData is None:
print "[BlockSwitchService] loadSwitchRecord blockData is None!"
return None
# 注意:BlockEntityData 对象不支持 .keys() 和 .get() 方法
# 只支持 [] 操作符(__getitem__),key 不存在会抛 KeyError
swt = blockData["rtdd_swt"] # 记录类型:"count" 或 "stack"
print "[BlockSwitchService] loadSwitchRecord rtdd_swt=%s" % swt
if swt == "count":
count = blockData["rtdd_swc"]
itemName = blockData["rtdd_swi"]
try:
auxValue = blockData["rtdd_swa"]
except (KeyError, Exception):
auxValue = 0
print "[BlockSwitchService] loadSwitchRecord count data: count=%s itemName=%s auxValue=%s" % (count, itemName, auxValue)
return {
"count": count,
"itemName": itemName,
"auxValue": auxValue,
}
elif swt == "stack":
stack = blockData["rtdd_sws"]
print "[BlockSwitchService] loadSwitchRecord stack data: len=%s" % len(stack)
return {
"stack": stack,
}
else:
print "[BlockSwitchService] loadSwitchRecord unknown swt: %s" % swt
return None
except Exception as e:
import traceback
print "[BlockSwitchService] loadSwitchRecord exception: %s trace=%s" % (str(e), traceback.format_exc())
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 导致写入静默失败。
先更新内存,再延迟写入方块实体。
"""
print "[BlockSwitchService] _saveRecordDelayed key=%s record=%s" % (key, record)
self.switchRecord[key] = record
gameComp.AddTimer(0.1, self.saveSwitchRecord, playerId, pos, dim, record)
def _clearRecordDelayed(self, key, playerId, pos, dim):
"""
延迟一帧清除方块实体数据。
"""
print "[BlockSwitchService] _clearRecordDelayed key=%s" % (key,)
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)