老实说我已经忘了这个mod是怎么写出来的了。
所以下面的代码不能运行是非常正常的。
上回书说到,我们处理了玩家拿着背包右键时触发的事件,发送了一条打开背包窗口的命令,但是背包窗口还没有写,这回就来解决这个问题。
这次因为嵌套关系太多,所以我按执行顺序反过来说。
那么先不管上次没有打开的那个GUI。
MC中的每一个能放进物品的容器,不管是箱子、熔炉还是酿造台,各自都持有一个自己的内容空间(Inventory)实例,在这个内容空间中各自保存自己的内容物。
内容空间的基类是InventoryBasic,实现了IInventory接口,为了方便,咱直接继承InventoryBasic类。
InventoryBasic类已经实现了大部分内容空间需要的功能,可以粗读一下代码,挺好懂的。
于是我们黑猫背包的内容空间类只需要处理内容物的保存和读取。
1 public class InventoryKuroNeko extends InventoryBasic { 2 public static final String TITLE = "黑猫"; 3 public static final int SLOTS_PER_LINE = 9; 4 public static final int LINES = 2; 5 6 private ItemStack itemStack; 7 8 private String id; 9 10 public InventoryKuroNeko(ItemStack itemStack) { 11 super(TITLE, true, LINES * SLOTS_PER_LINE); 12 13 this.itemStack = itemStack; 14 15 if (!itemStack.hasTagCompound()) { 16 itemStack.setTagCompound(new NBTTagCompound()); 17 id = UUID.randomUUID().toString(); 18 } 19 20 readFromNBT(itemStack.getTagCompound()); 21 } 22 23 public NBTTagCompound writeToNBT(NBTTagCompound compound) { 24 NBTTagList items = new NBTTagList(); 25 26 for (int i = 0; i < getSizeInventory(); i++) { 27 ItemStack itemStack = getStackInSlot(i); 28 if (itemStack != null) { 29 NBTTagCompound item = new NBTTagCompound(); 30 item.setInteger("slot", i); 31 itemStack.writeToNBT(item); 32 items.appendTag(item); 33 } 34 } 35 36 compound.setTag("items", items); 37 38 compound.setString("id", id); 39 40 return compound; 41 } 42 43 public void readFromNBT(NBTTagCompound compound) { 44 if (id == null) { 45 id = compound.getString("id"); 46 } 47 if (id == null) { 48 id = UUID.randomUUID().toString(); 49 } 50 51 NBTTagList items = compound.getTagList("items", 10); 52 53 for (int i = 0; i < items.tagCount(); i++) { 54 NBTTagCompound item = items.getCompoundTagAt(i); 55 56 int slot = item.getInteger("slot"); 57 if (slot >= 0 && slot < getSizeInventory()) { 58 ItemStack itemStack = ItemStack.loadItemStackFromNBT(item); 59 setInventorySlotContents(slot, itemStack); 60 } 61 } 62 } 63 64 @Override 65 public void markDirty() { 66 super.markDirty(); 67 writeToNBT(this.itemStack.stackTagCompound); 68 } 69 }
先扔代码
自己写两个方法,名字可以随便起,我这里一个叫writeToNBT,用于保存内容物,一个叫readFromNBT,用于读取内容物。
保存和读取用到的是NBT机制,NBT全称是什么我已经忘了。
先说保存。
首先创建一个NBT的列表NBTTagList,用于保存各个物品和它们的位置。这个类可以简单理解为一个列表。
然后遍历所有内容空间中的格子,只要发现格子中有物品,就把当前的格子位置保存在一个NBTTagCompound中,这个类理解为一个存储单元。
光保存位置不够,调用现成的API把这个物品的信息也加到存储单元中。
最后把这个存储单元添加到列表里。
这样遍历下来,所有物品的信息都保存起来了,最后把这个列表放到参数中传来的存储单元中。
怎么处理参数中的那个存储单元就不是你的事了。
最后是writeToNBT这个方法的调用,按照经验,应该会有一个回调方法,由系统在需要保存内容空间的内容的时候自动调用。
确实有。
找得我好苦啊。
重写父类的markDirty方法,先调用一遍父类的实现,然后加上
writeToNBT(this.itemStack.stackTagCompound);
这里的itemStack指的是使用了这个内容空间的物品,也就是黑猫背包。
在构造方法里接收并保存一下就好了。
这样系统就会自动把背包中的所有物品保存到这个背包实例中了。
但是光保存不行,还得读取。
于是来处理readFromNBT。
首先回到构造方法里。
第一次打开背包的时候肯定是不会有已经保存了的内容物的,this.itemStack.stackTagCompound这个东西也是不存在的,所以判断一下,初始化一个。
if (!itemStack.hasTagCompound()) { itemStack.setTagCompound(new NBTTagCompound()); id = UUID.randomUUID().toString(); }
再给当前内容空间一个id用来识别不同的背包。
不作id的区分的话就变成末影背包了。
现在已经不会有空指针的问题了,于是不管三七二十一,读取一下吧。
我这上面用访问器下面直接访问变量效果是一样的,请不要在意。
首先从物品中保存的存储单元中拿到当前内容空间的id。
然后取出在刚才保存了所有物品的列表NBTTagList,这个方法的第二个参数我忘了是干嘛用的了,照抄吧。
接下来就是遍历了,拿出一个存储单元,找出格子的位置,然后用ItemStack类中的方法和存储单元中保存的信息创建一个物品栈并设置到指定的位置上。
就完美地恢复了之前保存的东西。
最后说一下三个常量。
TITLE是内容空间的名称,貌似会显示在界面上。
因为为了方便之后会直接拿箱子的界面来用,所以SLOTS_PER_LINE请不要乱动。
LINES就是行数了,我为了平衡给了个2,箱子貌似是4?给65535应该也是可行的。如果你显示器纵向分辨率有那么高的话……
写到这里背包的基本功能就已经完成了,后面就要开始蛋疼了。
如刚才所说,我打算直接复用箱子的界面,但是箱子相关的两个类都用了硬编码,所以没办法用继承的方式来创建背包的界面。
用英文说就是用了一个非常蛋疼的workaround。
下一篇再说。