筛选界面与数据同步

基本信息

属性 值
容器 com.encraft.dz.container.ContainerOreFinder
客户端界面 com.encraft.dz.gui.GuiOreFinder(@SideOnly(Side.CLIENT))
界面贴图 assets/ifu/textures/gui/gui_ore_finder.png
自定义库存 com.encraft.dz.inventory.InventoryOreFinder
玩家扩展属性 com.encraft.dz.OreFinderPlayerData(IExtendedEntityProperties)
界面标题 gui.ifu_buildingKit.title(中文"寻矿魔杖")
GUI ID 10
打开方式 潜行 + 右键(ItemOreFinderTool.onItemUse),服务端 openGui

xSize / ySize 两个类都没有覆写,因此沿用 GuiContainer 的默认 176 × 166。

库存模型

项 值 来源
INV_SIZE 2 InventoryOreFinder.java:16
界面上实际暴露的槽位 1(SlotFilter 绑在 index 0) ContainerOreFinder.java:19
index 1 没有对应的 Slot,只存在于 ItemStack[2] 数组里,界面上拿不到 —
getInventoryStackLimit() 1 InventoryOreFinder.java:81-83
getInventoryName() gui.ifu_buildingKit.title InventoryOreFinder.java:71-73
hasCustomInventoryName() true InventoryOreFinder.java:76-78
isUseableByPlayer() 恒 true(任何玩家都能开) InventoryOreFinder.java:95-97
isItemValidForSlot() !(stack.getItem() instanceof ItemOreFinderTool) && OreFinderSearch.isAcceptableFilter(stack) InventoryOreFinder.java:106-108
tagName "BuildingKit" InventoryOreFinder.java:14

markDirty() 会把 stackSize == 0 的槽位直接置 null,所以空筛选格读出来是 null 而不是一个空栈 —— ItemOreFinderTool 与 GuiOreFinder 都依赖这一点做判空。

槽位布局(共 37 个)

容器索引 数量 绑定 坐标 槽位类
0 1 InventoryOreFinder 槽 0 (80, 26) SlotFilter
1 ~ 27 27 玩家主物品栏(j + i * 9 + 9) (8 + j×18, 84 + i×18) 原版 Slot
28 ~ 36 9 玩家快捷栏 (8 + i×18, 142) 其中恰好 1 个是 SlotLocked

被锁定的是玩家当前手持的那一格:if (i == player.inventory.currentItem) 时改用 SlotLocked(ContainerOreFinder.java:29-33)。锁定目的就是防止玩家把正在生效的魔杖从界面里拿走。

SlotFilter / SlotLocked 子类枚举

⚠️ 事实澄清:SlotFilter 与 SlotLocked 没有任何子类。源码 grep 证据:

$ grep -rn "extends SlotFilter\|extends SlotLocked" src/main/java
(零命中)

两个类各自在 ContainerOreFinder 构造器里被实例化恰好一次(1 个 SlotFilter + 1 个 SlotLocked),行为完全由它们自己覆写的方法决定。Slot 的其余 35 个子类全部是原版 Slot。

槽位类 实例数 覆写方法 行为
SlotFilter 1 isItemValid !(stack.getItem() instanceof ItemOreFinderTool) && OreFinderSearch.isAcceptableFilter(stack) —— 拒绝魔杖自己,拒绝完全无法解析的物品
SlotLocked 1 isItemValid 恒 false
SlotLocked 同上 canTakeStack 恒 false
SlotLocked 同上 putStack 空方法,写入被丢弃
SlotLocked 同上 onPickupFromSlot 空方法
SlotLocked 同上 getHasStack 恒 false
SlotLocked 同上 decrStackSize 恒 null

SlotLocked 通过 6 个覆写把该快捷栏格变成完全只读,任何取放路径都被堵死。

isAcceptableFilter(stack) = target.canSearch() || target.isBlocklisted(),含义是:被方块黑名单拒绝的物品仍然可以放进筛选格(放进去后界面顶部显示"黑名单方块!"警告),只有"完全解析不出目标"的物品才放不进去。

容器操作规则

transferStackInSlot(EntityPlayer player, int index):

index 行为
≠ 0 源是 SlotLocked → 直接 return null;否则从源只取 1 个(toPlace.stackSize = 1)放进筛选格,前提是筛选格当前为空且 isItemValid 通过;源里减 1,减到 0 则置 null
= 0 先遍历 1 ~ 36,把筛选物与"同物品(GTUtility.areStacksEqual)且未达堆叠上限"的槽位合并(每找到第一个就 break);若一条都没合并上,再找第一个空槽整体放入

slotClick(int slotId, int clickedButton, int mode, EntityPlayer player) 覆写了两条拦截:

  1. 该槽的 getStack() == player.getHeldItem() → 直接 return null,防止点击 / 拖动正在手持的魔杖
  2. mode == 2(数字键换位)且 clickedButton 落在 0 ~ 8 → 目标快捷栏索引 1 + (9 * 3) + clickedButton;若那是 SlotLocked 则 return null

两条分支结尾都走 detectAndSendChanges()。

NBT 存档路径

筛选物存在玩家的扩展属性里,不存在魔杖的 NBT 里。

层级 字段名 来源
玩家 NBT ExtendedPlayerProp(OreFinderPlayerData.EXT_PROP_NAME) saveNBTData → compound.setTag("ExtendedPlayerProp", properties)
该标签下 BuildingKit(InventoryOreFinder.tagName) writeToNBT → properties.setTag("BuildingKit", items)
列表每项 Slot(byte,槽位下标)+ ItemStack 标准 NBT item.setByte("Slot", (byte) i); getStackInSlot(i).writeToNBT(item);

结构:

ExtendedPlayerProp
└── BuildingKit                (NBTTagList)
    ├── { Slot: 0b, <物品 NBT> }
    └── { Slot: 1b, <物品 NBT> }

不持久化的字段:OreFinderPlayerData 的 hasLastScan、lastScanX、lastScanY、lastScanZ、lastScanFilter 是纯内存扫描缓存 —— saveNBTData 只写 filterInventory,所以重登后 hasLastScan 归 false,会立即重新扫描一次。

copy(OreFinderPlayerData props) 也只复制 filterInventory(由 PlayerDataEventHandler.onClonePlayer 在玩家死亡 / 重生时调用),扫描缓存不复制。

一处遗留写法:readFromNBT 用 compound.getTagList(tagName, compound.getId()) 取列表,第二个参数传的是 compound 自身的 NBT 类型 id,而不是 NBTTagList 的 10。

网络消息

通道:NetworkRegistry.INSTANCE.newSimpleChannel("ifu")(PacketDispatcher),packetId 从 0 起递增,在 preInit 由 PacketDispatcher.registerPackets() 注册。

消息类 继承自 packetId 方向 载荷 何时发送
SyncPlayerDataMessage AbstractMessage.AbstractClientMessage 0 服务端 → 客户端 一个 NBTTagCompound,内容即上文 ExtendedPlayerProp / BuildingKit 结构(OreFinderPlayerData.saveNBTData 的产物) 玩家进世界时:PlayerDataEventHandler.onEntityJoinWorld 在 !event.entity.worldObj.isRemote 下用 PacketDispatcher.sendTo(msg, (EntityPlayerMP) event.entity) 只发给该玩家本人

process 在客户端执行 OreFinderPlayerData.get(player).loadNBTData(data),用收到的数据覆盖本地筛选格。

AbstractMessage 的三级结构(AbstractMessage.java):

抽象类 isValidOnSide 说明
AbstractMessage<T> 恒 true(默认双向) 基类;onMessage 先校验 side(不合法直接抛 RuntimeException),再调 process,玩家实体由 proxy 的 getPlayerEntity(ctx) 提供
AbstractClientMessage<T> side.isClient() 只能服务端 → 客户端
AbstractServerMessage<T> side.isServer() 只能客户端 → 服务端

PacketDispatcher.registerMessage 按子类决定注册哪一侧:AbstractClientMessage 子类注册 Side.CLIENT;AbstractServerMessage 子类注册 Side.SERVER;两者都不是则用同一个 packetId 在两侧各注册一次。

⚠️ 本 mod 没有任何 AbstractServerMessage 的具体子类,也没有直接继承 AbstractMessage 的双向消息 —— 客户端 → 服务端的自定义消息数为 0(魔杖的扫描全部在服务端完成,客户端只负责播放音效与界面绘制)。

事件订阅

PlayerDataEventHandler(在 load() 里注册到 MinecraftForge.EVENT_BUS):

事件 处理
EntityEvent.EntityConstructing 玩家实体构造时,若 OreFinderPlayerData.get(player) == null 则 register
EntityJoinWorldEvent 服务端:向该玩家发一次 SyncPlayerDataMessage
PlayerEvent.Clone 死亡 / 重生:把原玩家的 filterInventory 复制到新实体

界面文字与颜色

GuiOreFinder.drawGuiContainerForegroundLayer 画 4 类文字:

行 lang 键 位置 颜色常量 默认色
标题 gui.ifu_buildingKit.title 居中于 x = 88,y = 12 ColorUtils.title 0x404040
黑名单警告(仅 isBlocklisted()) IFU.BlocklistedBlock(“黑名单方块!”) (27, ySize − 116 = 50) ColorUtils.blocklistWarning 0x404040
名称行 IFU.NameTip(“名称: %s”,筛选格为空时用 IFU.Empty) (10, ySize − 106 = 60) ColorUtils.displayText 0x404040
搜索行(仅筛选格非空) IFU.SearchTip(“搜索: %s”,参数是 MatchTarget.getLocalizedName()) (10, ySize − 96 = 70) canSearch() 为真用 searchAvailable,否则 searchUnavailable 0x404040 / 0xAA0000

文字超宽时由私有方法 fit() 截断:trimStringToWidth(text, maxWidth - width("...")) + "...",其中 maxTextWidth = xSize - 10 - 8。

ColorUtils 用 GTNHLib 的 ColorResource.Factory("ifu") 声明 5 个颜色:

常量 默认值 用处
title 0x404040 界面标题
displayText 0x404040 名称行
blocklistWarning 0x404040 黑名单警告
searchAvailable 0x404040 搜索行(可搜索)
searchUnavailable 0xAA0000 搜索行(不可搜索,全文件唯一非深灰色)

ColorResource 机制允许按名字覆盖这些默认值。这些颜色只用于筛选界面的文字,与矿石稀有度 / 矿层显示无关 —— ColorUtils 全文件仅这 5 个常量,唯一引用者是 GuiOreFinder。

背景层只是绑定贴图并 drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSize, ySize),没有额外绘制逻辑。

相关条目