シフトクリック時の処理

提供:Minecraft Modding Wiki
移動: 案内, 検索

この記事は"Minecraft Forge Universal 10.13.0.1197~"を前提MODとしています。

シフトクリック時の処理


ここではシフトクリック時に呼ばれるtransferStackInSlotの実装の仕方を解説します。

例として、以下のようなGUIを考えます。

----------------------------------
|素材
|□□
|        結果
|        □□□□□
|燃料
|□□□
----------------------------------
|□□□□□□□□□
|□□□□□□□□□
|□□□□□□□□□
----------------------------------
|□□□□□□□□□
----------------------------------

素材を入れるスロットが2個、燃料を入れるスロットが3個、素材を燃やしたアイテムを格納するスロットが5個ある、拡張されたかまどのようなGUIです。

下にある□はプレイヤーのインベントリと思ってください。

public class TileEntitySample extends TileEntity implements IInventory {
	private ItemStack[] itemStacks = new ItemStack[10];
}

ItemStack配列のサイズが10なのは素材2スロット、燃料3スロット、結果5スロットの合計です。

public class ContainerSample extends Container {
	TileEntitySample tile;
 
	static final int sourceSize = 2;
	static final int fuelSize = 3;
	static final int productSize = 5;
	static final int inventorySize = 27;
	static final int hotbarSize = 9;
 
	static final int sourceIndex = 0;
	static final int fuelIndex = sourceIndex + sourceSize;
	static final int productIndex = fuelIndex + fuelSize;
	static final int inventoryIndex = productIndex + productSize;
	static final int hotbarIndex = inventoryIndex + inventorySize;
}

Containerのスロットは1次元の配列なので、わかりやすいように素材・燃料・結果・インベントリ・ホットバーの開始位置とスロット数を定義しています。

つまり、スロットは以下のような並び順になっています。

□□_□□□_□□□□□_□□□□□□□□□□□□□□□□□□□□□□□□□□□_□□□□□□□□□

↑↑_↑↑↑_↑↑↑↑↑_↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑_↑↑↑↑↑↑↑↑↑

素材_燃料__結果____インベントリ________________________ホットバー    

public class ContainerSample extends Container {
 
	public ContainerSample(InventoryPlayer inventory, TileEntitySample tile) {
		this.tile = tile;
 
		//素材スロットを追加(2個)
		for(int i = 0; i < sourceSize; ++i) {
			this.addSlotToContainer(new SlotSampleSource(tile, sourceIndex + i, x, y));
		}
 
		//燃料スロットを追加(3個)
		for(int i = 0; i < fuelSize; ++i) {
			this.addSlotToContainer(new SlotSampleFuel(tile, fuelIndex + i, x, y));
		}
 
		//結果スロットを追加(5個)
		for(int i = 0; i < productSize; ++i) {
			this.addSlotToContainer(new SlotSampleProduct(tile, productIndex + i, x, y));
		}
 
		//インベントリスロットを追加(27個)
		for (int i = 0; i < 3; ++i) {
			for (int j = 0; j < 9; ++j) {
				this.addSlotToContainer(new Slot(inventory, j + i * 9 + inventoryIndex, x, y));
			}
		}
 
		//ホットバースロットを追加(9個)
		for (int i = 0; i < 9; ++i) {
			this.addSlotToContainer(new Slot(inventory, i, x, y));
		}
	}
}

先に述べた並び順でスロットを追加しています。

スロットの表示位置を決めるx,yや受け入れるアイテムを制限するSlotSampleSource,SlotSampleFuel,SlotSampleProductはこのサンプルでは定義されていません。

が、想像力を働かせてあるものと思って見ていただければ幸いです。


ここから先はいよいよtransferStackInSlotの実装ですが、その前にシフトクリック時にどのような動作をさせたいのかを決めておきます。

ここでは以下の動作をするように実装していきます。

  • ホットバーのスロットをシフトクリック→素材アイテムなら素材スロットに移動、燃料アイテムなら燃料スロットに移動、どちらでもなければインベントリの空いてるスロットに移動
  • インベントリのスロットをシフトクリック→素材アイテムなら素材スロットに移動、燃料アイテムなら燃料スロットに移動、どちらでもなければホットバーに移動
  • 燃料のスロットをシフトクリック→素材アイテムなら素材スロットに移動、そうでなければインベントリ・ホットバーに移動
  • 素材のスロットをシフトクリック→インベントリ・ホットバーに移動
  • 結果のスロットをシフトクリック→インベントリ・ホットバーに移動
public class ContainerSample extends Container {
 
	@Override
	public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int clickedIndex) {
 
		//クリックされたスロットを取得
		Slot slot = (Slot)this.inventorySlots.get(clickedIndex);
		if(slot == null) {
			return null;
		}
 
		if(slot.getHasStack() == false) {
			return null;
		}
 
		//クリックされたスロットのItemStackを取得
		ItemStack itemStack = slot.getStack();
 
		//書き換えるた後比較したいので変更前のItemStackの状態を保持しておく
		ItemStack itemStackOrg = slot.getStack().copy();
 
		//素材スロットがクリックされたらインベントリかホットバーの空いてるスロットに移動
		if(sourceIndex <= clickedIndex && clickedIndex <= sourceIndex + sourceSize) {
			if (!this.mergeItemStack(itemStack, inventoryIndex, inventoryIndex + inventorySize + hotbarSize, false)) {
				return null;
			}
 
			slot.onSlotChange(itemStack, itemStackOrg);
		}
		//燃料スロットがクリックされた
		else if(fuelIndex <= clickedIndex && clickedIndex < fuelIndex + fuelSize) {
			//素材アイテムか判定(※isSourceItemはこのサンプルでは実装されていないメソッドです)
			if(isSourceItem(itemStack)) {
				//素材アイテムなので素材スロットへ移動
				if (!this.mergeItemStack(itemStack, sourceIndex, sourceIndex + sourceSize, false)) {
					return null;
				}
 
				slot.onSlotChange(itemStack, itemStackOrg);
			}
			else {
				//素材アイテムではないのでインベントリかホットバーの空いてるスロットに移動
				if (!this.mergeItemStack(itemStack, inventoryIndex, inventoryIndex + inventorySize + hotbarSize, false)) {
					return null;
				}
 
				slot.onSlotChange(itemStack, itemStackOrg);
			}
		}
		// 結果スロットがクリックされたらインベントリかホットバーの空いてるスロットに移動
		else if(productIndex <= clickedIndex && clickedIndex < productIndex + productSize) {
			if (!this.mergeItemStack(itemStack, inventoryIndex, inventoryIndex + inventorySize + hotbarSize, false)) {
				return null;
			}
 
			slot.onSlotChange(itemStack, itemStackOrg);
		}
		//インベントリがクリックされた
		else if(inventoryIndex <= clickedIndex && clickedIndex < inventoryIndex + inventorySize) {
			if(isSourceItem(itemStack)) {
				//素材アイテムなので素材スロットへ移動
				if (!this.mergeItemStack(itemStack, sourceIndex, sourceIndex + sourceSize, false)) {
					return null;
				}
			}
			else if(isFuelItem(itemStack)) {
				//燃料アイテムなので燃料スロットへ移動
				if (!this.mergeItemStack(itemStack, fuelIndex, fuelIndex + fuelSize, false)) {
					return null;
				}
			}
			else {
				//どちらでもないのでホットバーに移動
				if (!this.mergeItemStack(itemStack, hotbarIndex, hotbarIndex + hotbarSize, false)) {
					return null;
				}
			}
		}
		//ホットバーがクリックされた
		else if(hotbarIndex <= clickedIndex && clickedIndex < hotbarIndex + hotbarSize) {
			if(isSourceItem(itemStack)) {
				//素材アイテムなので素材スロットへ移動
				if (!this.mergeItemStack(itemStack, sourceIndex, sourceIndex + sourceSize, false)) {
					return null;
				}
			}
			else if(isFuelItem(itemStack)) {
				//燃料アイテムなので燃料スロットへ移動
				if (!this.mergeItemStack(itemStack, fuelIndex, fuelIndex + fuelSize, false)) {
					return null;
				}
			}
			else {
				//どちらでもないのでインベントリに移動
				if (!this.mergeItemStack(itemStack, inventoryIndex, inventoryIndex + inventorySize, false)) {
					return null;
				}
			}
		}
 
		//シフトクリックで移動先スロットが溢れなかった場合は移動元スロットを空にする
		if (itemStack.stackSize == 0) {
			slot.putStack((ItemStack)null);
		}
		//移動先スロットが溢れた場合は数だけ変わって元スロットにアイテムが残るので更新通知
		else {
			slot.onSlotChanged();
		}
 
		//シフトクリック前後で数が変わらなかった=移動失敗
		if (itemStack.stackSize == itemStackOrg.stackSize) {
			return null;
		}
 
		slot.onPickupFromSlot(par1EntityPlayer, itemStack);
 
		return itemStackOrg;
	}
}

ベタ書きで書き下しているので見辛いかもしれませんがご容赦ください。 ソースコード中のコメントに書いてある通りですが、クリック位置が素材・燃料・結果・インベントリ・ホットバーのどのスロットなのかを判定し、mergeItemStackで移動先にマージしています。

mergeItemStackは第1引数のItemStackをインデックス番号が第2引数から第3引数のスロットに順番にマージしていくメソッドです。

第4引数は順番を指定し、falseなら先頭から、trueなら末尾からマージします。

チュートリアル
個人用ツール