初心者modderの備忘録

マイクラのmodを作りたくて、初めて見たのですが難しくて忘れそうなので自分用の備忘録も兼ねてブログにしようと思います

参考にしているサイト

www.youtube.com

 

1.10.2と1.11.1に対応したチュートリアルをあげています。

非常に項目が多いので、このシリーズを一通り見ればある程度好きなことができると思います。

 

 

 

www.youtube.com

 

1.12.Xがメインで1.13も少しあげています。

こちらも非常に多くの解説動画をあげてくれています。

結構難しいこともやっているので、マスターできればmodの幅が広がると思います。

 

 

www.tntmodders.com

 

主なバージョンが1.7.10で少し古いのですが、日本語のサイトで多くの項目を解説しています。

 

12日目 他Modのアイテム、ブロックの使用

メモ

どこかで宣言
Mainクラスでも他クラスでも好きなところで。
その後、postInitでBlock.REGSTRY.getObjectを使って登録(代入)する。
一応、登録の前に目的のModが読み込まれているかをif分で確認したほうがベター。

例として、testmodのblock_testを利用。

public static Block otherModBlock;

~~~

    @EventHandler
    public void postInit(FMLPostInitializationEvent event)
    {

//Loader.isModLoaded(MOD_ID)として使用
    	if(Loader.isModLoaded("testmod"))
    	{
    		TEST = Block.REGISTRY.getObject(new ResourceLocation("testmod:block_test"));
    	}
    }


アイテムもBlockをItemに変えるだけでほぼ同様かと。

ちなみに他ModのmodidとregystryNameを見る方法は、一度そのModをいれてゲームを起動して、世界を作成して終了。
その後、Modを削除して起動して、先ほど作った世界を開く。
そうするとアイテムが見つかりません的なエラーが出るが気にせず開いてから終了する。
そのあと、logsフォルダの中のlatest.logを見るとエラーメッセージに関するlogが出ているのでこれを参照する。


latest.log

************************************************

~~~

20:19:05] [Server thread/INFO] [FML]: Registry Block: Found a missing id from the world testmod:block_test
[20:19:05] [Server thread/INFO] [FML]: Registry Item: Found a missing id from the world testmod:item_test

~~~

************************************************

missingとかで検索かけると楽な気がする。

他にもっといい方法がありそうな気がするので知っている人がいたら教えてください!!

0日目 環境設定(Forge1.13.2)

forgeの1.13がリリースされたので、そちらでMod作成をしていきたいと思います。
1.12とは結構変わっていて戸惑いました。

また、ネットに落ちてるコードの数がまだ少ないのであまり参考にするものがないのも厳しいです。
少しずつ情報を集めて更新していこうと考えています。

今日はとりあえずセットアップまでを書いていきます。

大まかな手順は以下のとおりです。

0. JavaおよびEclipseの設定
1. Forge1.13mdkのダウンロード
2. gradlewでの環境設定
3. eclipseでの環境設定


0. JavaおよびEclipseの設定
これに関しては1.12までのModを作成する環境があった方は不要だと思います。
私も1.12の時から更新していませんが1.13でも使えています。
参考までにこの記事の1~3に該当する作業です。
0日目 セットアップ - 初心者modderの備忘録



1. Forge1.13mdkのダウンロード
Forgeのサイトから1.13.2mdkをダウンロードして任意のフォルダに解凍します。
Minecraft Forge 1.13.2
2019/04/17の時点でrecommendのバージョンがないのでlatestをダウンロードします。
ダウンロードしたら好きな場所に解凍します。
今回は
C:\WorkSpace\Minecraft\1.13\sample1
にしました。

2. gradlewでの環境設定
続いて環境を構築します。forgeのフォルダでコマンドプロンプトを開きます。
C:\WorkSpace\Minecraft\1.13\sample1\forge-1.13.2-25.0.145-mdk
のフォルダに移動してアドレスバーにcmdと打つと、このフォルダでコマンドプロンプトが開きます。

f:id:json_fileman:20190417234831p:plain

コマンドプロンプトから
cd C:\WorkSpace\Minecraft\1.13\sample1\forge-1.13.2-25.0.145-mdk
で移動しても大丈夫です。

そしたら、次のコマンドを入力します。
gradlew genEclipseRuns

"BUILD SUCCESSFUL"と表示されたら成功です。

f:id:json_fileman:20190417235523p:plain

続いて、もう1つコマンドを入力します。
gradlew eclipse
これもうまくいくと、"BUILD SUCCESSFUL"が表示されます。

f:id:json_fileman:20190417235544p:plain


3. eclipseでの環境設定
次にeclipseを開きます。ワークスペースとしてforgeのフォルダが置いてあるフォルダを開きます。
今回の場合だと、
C:\WorkSpace\Minecraft\1.13\sample1
になります。

f:id:json_fileman:20190418000022p:plain

eclipseが起動したら、
[ファイル]→[ファイル・システムからプロジェクトを開く] を選択、

[インポート・ソース]の[ディレクトリー] を選択してください。

f:id:json_fileman:20190418000553p:plain

[完了]をクリックします。


うまくいくとパッケージエクスプローラーのところに
forgeのフォルダが表示されると思います。

f:id:json_fileman:20190418001136p:plain



続いて、
[実行]→[実行構成]→[Java アプリケーション]→[runClient] と進んで
[引数]タブをクリック、
作業ディレクトリーのその他に
C:\WorkSpace\Minecraft\1.13\sample2\forge-1.13.2-25.0.88-mdk\run
のようにrunのフォルダが選択されていることを確認して
[実行]をクリックします。

MineCraftが起動します。
これで基本的なセットアップは完了です。

次回はModのメインファイルを作成しようと思います。


(追記:20200313)
久しぶりにログインしたら約1年が経過していました;
いつの間にかforgeの最新版も1.13から1.15になってしまいましたね;;;
しかもアクセス解析を見てみたら、このブログを見てくれている人がいるみたいで驚きました!
次回更新の予告をしておきながら1年も放置してしまって申し訳ないです。
とりあえず近日中に1.15でのアイテムの作成くらいまで記事にしたいと思います。。。。

お久しぶりです

すみません、前回の更新から数ヶ月経ってしまいました。

 

個人的に忙しかったり、マイクラ以外のことに興味が移ったりであまりmod作成を出来ていませんでした。

modでプログラムを触っているうちにプログラミングとかゲーム作成そのものに関心が移って、java3DとかC++DirectXとか最近?ちょっと前?に流行ったUnityとかその辺りに手を出していました。

 

結論から言うと、0からゲームを作るのはめちゃめちゃ難しいですね。。。笑

java3DとかdirectXなんかは画面に3Dの球体を表示させるだけで一苦労だったりで、ゲーム作成なんて夢のまた夢でした。

その点、Unityは比較的扱いやすかった印象です。画面上で動きを設定できたりするので、冗長なプログラムを書く必要もないですし、人気なもの納得という感じでした。

 

まあ、どれも少しずつかじっただけなので感想が間違っているかもしれません。

またそのうち勉強したいと考えているので、もしかしたらそっち関連の記事等もいつかは書くかもしれません。

 

そんなこんなしてる間にforgeの1.13がリリースされたのを知って、どうせ以前のバージョンの知識もあんまりないし最新版で勉強しようと思って、またマイクラのModに戻ってきました。

 

ただやはりリリースされて間もないので、1.12と比べると圧倒的に情報量が少ないですね。なので、のんびり更新にはなると思いますが1.13の記事も書いていけたらいいなと思っています。

 

とりあえず、次は1.13の環境設定に関する記事を書くつもりです。

それでは。

 

11日目 WorldGeneratorの利用

前回から少し間が開きましたたが、今回は追加したブロック等を自然生成させるためのWorldGeneratorの使い方を調べて見ました。

基本的には
親クラスWorldGeneratorを継承したクラスと
インターフェースIWorldGeneratorを実装したクラスの
合計2つのクラスを作ります。

まずこの時点でややこしかったのに加えて、サンプルコードとかを見るとforとifがいっぱい書いてあってさらに難しくてちょっと手を出さないでいたのですが、実際にコードを書いてみると思ったよりは難しくなかったので、忘れないうちにここに記録しておこうと思います。

まず、極めて単純なコードを書いていきます。
WorldGeneratorの方から。

WorldGenSample.java

package test6mod;

import java.util.Random;

import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.feature.WorldGenerator;

public class WorldGenSample extends WorldGenerator {

	@Override
	public boolean generate(World world, Random rand, BlockPos pos)
	{
			world.setBlockState(pos, Blocks.DIAMOND_BLOCK.getDefaultState());
		return true;
	}

}

ダイヤモンドブロックを与えられた座標に1つ置きます。


続いてIWorldGeneratorの方を。

IWorldGenSample.java

package test6mod;

import java.util.Random;

import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraft.world.gen.feature.WorldGenerator;
import net.minecraftforge.fml.common.IWorldGenerator;

public class IWorldGenSample implements IWorldGenerator {

	public WorldGenerator gen = new WorldGenSample();

	@Override
	public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator,IChunkProvider chunkProvider) 
	{
		switch (world.provider.getDimension()) 
		{
		case -1:
			break;

		case 0:
			runGenerator(gen, world, random, chunkX, chunkZ);
			break;

		case 1:

		}
	}

	private void runGenerator(WorldGenerator gen, World world, Random rand, int chunkX, int chunkZ) 
	{
		int x = chunkX * 16 + 8;
		int y = 80;
		int z = chunkZ * 16 + 8;
	
		gen.generate(world, rand, new BlockPos(x, y, z));
	}
}


こっちの方では、まず後で"WorldGenSampleのgenerate()"を使うために、インスタンスを生成します。
続いてオーバーライドした"IWorldGenSampleのgenerate()"の中身ですが、ディメンションによって場合分けをしておきます。
それぞれ-1がネザー、0がオーバーワールド、1がエンドですね。自作のディメンションも使う場合にはここに自作のディメンションIDも追加します。

今回はオーバーワールドにのみ作用させるので、case 0にのみrunGenerator()を記述しました。


次にrunGeneratorの中身でWorldGenSampleのgenerate()をどこで使うかを書いておきます。
IWorldGeneratorでは与えられるのが座標ではなくチャンクなので、16を掛けて座標に変換してからgenerateに座標を渡します。
また+8の意味は、生成するチャンクの端っこではなく中央からgenerateを起こしてほしいためです。ちなみにこれがないと、よほど軽い処理の場合を除いて後述するカスケードという現象が頻発します。


最後に、これをメインのクラスや登録用のクラスで登録します。

Main.java

package test6mod;

import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;

@Mod(modid = Main.modId, name = Main.name, version = Main.version)
public class Main {

	public static final String modId = "testmod";
	public static final String name = "testmod";
	public static final String version = "1.0.0";

	@Mod.Instance(modId)
	public static Main instance;


	@Mod.EventHandler
	public void preInit(FMLPreInitializationEvent event)
	{
		GameRegistry.registerWorldGenerator(new IWorldGenSample(), 0);

    	if(event.getSide().isClient()) {
    		
    	}
	}


	@Mod.EventHandler
	public void init(FMLInitializationEvent event) {

	}

	@Mod.EventHandler
	public void postInit(FMLPostInitializationEvent event) {

	}
}

これで起動してみます。

f:id:json_fileman:20190119230634p:plain

乱数をまったく使ってないので、y=80のところに規則正しくダイヤモンドブロックが生成されました。
実際この程度単純であれば、WorldGeneratorクラスは使わずともIWorldGeneratorだけで記述できますが、もし複雑なものを作る場合は分けておいた方がわかりやすいかもしれないですね。
また、WorldGeneratorは世界の生成時以外でも、たとえば成長による木の生成にも使えるので使い回せるといった利点もあるかもしれません。

すこしだけ、生成条件と生成するものを変えてみます。

WorldGenSample.java

public class WorldGenSample extends WorldGenerator {

	@Override
	public boolean generate(World world, Random rand, BlockPos pos)
	{
		for(int i=-4; i<=4;i++)
		{
			for(int j=-4; j<=4; j++)
			{
				if(i%2==0 && j%2==0)
					world.setBlockState(pos.add(i, 0, j), Blocks.DIAMOND_BLOCK.getDefaultState());
				else
					world.setBlockState(pos.add(i, 0, j), Blocks.GOLD_BLOCK.getDefaultState());
			}
		}
			
		return true;
	}

}


IWorldGenSample.java

public class IWorldGenSample implements IWorldGenerator {

//省略

	private void runGenerator(WorldGenerator gen, World world, Random rand, int chunkX, int chunkZ) 
	{
		int x = chunkX * 16 +8+rand.nextInt(16);
		int z = chunkZ * 16 +8+rand.nextInt(16);
		
		int y = world.getHeight();
		boolean foundGround = false;
		
		while(!foundGround && y-- >= 0)
		{
			Block block = world.getBlockState(new BlockPos(x,y,z)).getBlock();
			foundGround = block == world.getBiome(new BlockPos(x, y, z)).topBlock.getBlock();
		}
		gen.generate(world, rand, new BlockPos(x, y+1, z));
	}
}

地表を見つけて、そこに金とダイヤでできた変なもの生成します。
実行した結果です。

f:id:json_fileman:20190119235945p:plain


ちなみに、このときのコンソールがこちらなんですが、

f:id:json_fileman:20190120000041p:plain


Preparing spawn area: XX%
っていうのが連続して表示されていますね。
これが普通の状態です。



一方、チャンク×16+8の+8をなくした結果がこちらです。

f:id:json_fileman:20190120000408p:plain

Preparing spawn area: XX%同士の間に
causing cascading worldgen lag
っていうエラーメッセージが表示されています。

これでも一応起動されるのですが、場合によってはかなり起動が遅くなります。
この現象は、現在生成中の構造物がまだ生成されていないチャンクに生成されようとしている場合に発生します。
ここの+8がないと、チャンクの隅っこから生成を始めることになるので未生成チャンクに影響を及ぼす可能性が大きくなり、発生しやすくなります。
なので、特にこだわる理由がない場合は+8しておくのが無難な気がします。

ただ、検証はしていませんがそもそもWorldGeneratorで16×16を大きく超えるような生成物を作ろうとすると避けられないかもしれないですね。

今回はこの辺で終わります。

次回は次元の追加でもやろうと思います。


Main.java
WorldGenSample.java
IWorldGenSample.java

10日目 GUIの追加

前回に引き続きTileEntityの編集をしていきたいと思います。
今回はGUIを追加します。GUIを追加するとバニラのチェストみたいに画面上でTileEntityのイベントリにアクセスできます。

前回までに作った2つのクラス、BlockTomatoとTileEntityTomatoに加えて今回はさらに3つのクラスを追加します。

・BlockTomatoContainer
これは文字通り、コンテナーとなるようにスロットを追加していきます。Containerクラスを継承します。
・guiBlockTomatoContainer
ここでは、BlockTomatoContainerに対応する画像を登録したり、その壁画に関する詳細を設定したりします。
・GUIHandler
ここでguiを登録してTileEntityなどと結びつけます。

では、まずはBlockTomatoContainerから作成していきます。

BlockTomatoContainer

package test4mod;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.SlotItemHandler;

public class BlockTomatoContainer extends Container{

	
	//-----------------------------PART 1---------------------------------------
	public BlockTomatoContainer(IInventory playerInv, final TileEntityTomato te) 
	{
		IItemHandler inventory = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.NORTH);

		for(int i=0; i<3;i++)
		{
			for(int j=0; j<3; j++)
			{
				addSlotToContainer(new SlotItemHandler(inventory, i*3+j, 62+i*18, 17+j*18)
				{
					@Override
					public void onSlotChanged() {
						te.markDirty();
					}
				});
			}
		}

		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 9; j++) {
				addSlotToContainer(new Slot(playerInv,  i * 9+j + 9, 8 + j * 18, 84 + i * 18));
			}
		}

		for (int k = 0; k < 9; k++) {
			addSlotToContainer(new Slot(playerInv, k, 8 + k * 18, 142));
		}
	}

	//--------------------------------PART 2-----------------------------------
	
	@Override
	public boolean canInteractWith(EntityPlayer playerIn) {

		return !playerIn.isSpectator();
	}


	@Override
	public ItemStack transferStackInSlot(EntityPlayer player, int index)
	{
		ItemStack itemstack = ItemStack.EMPTY;
		Slot slot = inventorySlots.get(index);

		if (slot != null && slot.getHasStack()) {
			ItemStack itemstack1 = slot.getStack();
			itemstack = itemstack1.copy();

			int containerSlots = inventorySlots.size() - player.inventory.mainInventory.size();

			if (index < containerSlots) {
				if (!this.mergeItemStack(itemstack1, containerSlots, inventorySlots.size(), true)) {
					return ItemStack.EMPTY;
				}
			} else if (!this.mergeItemStack(itemstack1, 0, containerSlots, false)) {
				return ItemStack.EMPTY;
			}

			if (itemstack1.getCount() == 0) {
				slot.putStack(ItemStack.EMPTY);
			} else {
				slot.onSlotChanged();
			}

			if (itemstack1.getCount() == itemstack.getCount()) {
				return ItemStack.EMPTY;
			}

			slot.onTake(player, itemstack1);
		}

		return itemstack;
	}
}

まずPART1についてですが、この部分がこのクラスのコンストラクタで、この中でイベントリの設定をします。3つのfor分はそれぞれタイルエンティティのスロット、プレイヤーの見えないスロット、プレイヤーの下に見えているスロットを表しています。SlotHandlerを使った場合はpublic void onSlotChanged()をオーバーライドしておく必要があるみたいです。それぞれ、Slotの第2引数はスロットの番号、第3,4引数が画像のピクセルのx座標とy座標ですね。

f:id:json_fileman:20190111004626p:plain

今回はディスペンサーの画像をそのまま使っているので、細かい値は自分で設定していませんが、自作の画像を使う場合にはスロット位置の左上か中央の座標を指定するようにすればいいのではないでしょうか。
また、プレイヤーのイベントリのスロット番号は下に表示されているアイテムの左端が0のようです。

続いて、PART 2の最初の
public boolean canInteractWith(EntityPlayer playerIn)
はプレイヤーがこのコンテナを使えるかどうかで、基本的には !playerIn.isSpectator()またはtrueでいいんじゃないかと思います。
次のメソッドは大事なんですけど理解はしていません。ただこれを入れておかないとShift+クリックとか右クリックしたときにクラッシュとかフリーズを引き起こしてしまいます。
逆にこれを入れておくことで、Shift+クリックでスタックを全部移動できたり、右クリックで半分だけ拾えたり、バニラのチェストと同じ操作ができるようになります。



次にguiBlockTomatoContainerを作って行きます。

package test4mod;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.inventory.IInventory;
import net.minecraft.util.ResourceLocation;

public class guiBlockTomatoContainer extends GuiContainer{

	public guiBlockTomatoContainer(IInventory playerinv, TileEntityTomato te) {
		super(new BlockTomatoContainer(playerinv, te));

		this.xSize = 176;
		this.ySize = 166;

	}

	@Override
	protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
		GlStateManager.color(1, 1, 1,1);
		this.mc.getTextureManager().bindTexture(new ResourceLocation("testmod","textures/gui/tomatocontainer.png"));
		this.drawTexturedModalRect(this.guiLeft, this.guiTop, 0, 0, this.xSize,this.ySize);
	}
}

ここは、xSizeとySizeにそれぞれ画像のサイズに対応した値をいれて、下のResourceLocationに画像のPathを書いておけば大丈夫だと思っています。
guiのテクスチャは
assets.testmod.textures.gui
に入れています。


次はGUIHandlerです。

package test4mod;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.IGuiHandler;

public class GuiHandler implements IGuiHandler{
	
	public static final int BlockTomatoContainer = 1;

	@Override
	public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
		if(ID == BlockTomatoContainer) {
			return new BlockTomatoContainer(player.inventory,(TileEntityTomato) world.getTileEntity(new BlockPos(x,y,z)));
		}
		return null;
	}

	@Override
	public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
		if(ID == BlockTomatoContainer){
			return new guiBlockTomatoContainer(player.inventory,(TileEntityTomato) world.getTileEntity(new BlockPos(x,y,z)));
		}
		return null;
	}
}


ここはGUIのIDを決めて、サーバーおよびクライアントでそれぞれif分またはswitch分でケース処理して、対応したguiを返すようにしておけば大丈夫でしょう。
サーバーにはコンテナーを、クライアントにはGUIを返すようです。


これで、新しいファイルは終わったのですが、これに応じてTileEntityおよびBlockクラスを少し変更します。

TileEntityTomato.java

public class TileEntityTomato extends TileEntity{
	private ItemStackHandler inventory = new ItemStackHandler(9);

//省略

}

ItemStackHandlerをコンテナーで設定したイベントリの数に対応させておきます。
ここの数がずれていると、フリーズしたりしました。


続いて、右クリックでGUIが開くように変更します。

BlockTomato.java

public class BlockTomato extends BlockContainer{

//省略

	@Override
	public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
		if (!world.isRemote) {
			TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
			IItemHandler itemHandler = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side);
			if (!player.isSneaking()) {
				player.openGui(testmod.instance, GuiHandler.BlockTomatoContainer, world, pos.getX(), pos.getY(), pos.getZ());
			} else {
				ItemStack stack = itemHandler.getStackInSlot(0);
				if (!stack.isEmpty()) {
					System.out.println(stack.getItem().getRegistryName()+" "+stack.getCount());
				} else {
					System.out.println("Empty");
				}
			}
		}
		return true;
	}


	@Override
	public void breakBlock(World world, BlockPos pos, IBlockState state) {
	TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
	IItemHandler handler =te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
	for(int slot = 0; slot < handler.getSlots();slot++) 
	{
		ItemStack stack = handler.getStackInSlot(slot);
		InventoryHelper.spawnItemStack(world, pos.getX(), pos.getY(),pos.getZ(), stack);
	}
		super.breakBlock(world, pos, state);
	}

//省略
}

openGuiでGUIを開きます。
スニークのときの処理は変えていないので0番のスロットの中身をコンソールに表示できます。

最後にメインのクラスで、

NetworkRegistry.INSTANCE.registerGuiHandler(this, new GuiHandler());

で登録をします。


これで起動してみます。

f:id:json_fileman:20190111005427p:plain


f:id:json_fileman:20190111005439p:plain


ちゃんと機能しました!
最後にファイルをアップしておきます。

次回は、ディメンションの追加か、WorldGeneratorによる自然生成をしようと思います。


testmod.java
BlockTomato.java
TileEntityTomato.java
guiBlockTomatoContainer.java
BlockTomatoContainer.java
GuiHandler.java

9日目 イベントリの追加

前回作ったint型のカウンターしか持たないTileEntityにイベントリを追加していこうと思います。
なので、今回はメインのクラスは触らず、BlockTomatoとTileEntityTomatoを書き換えます。

まず、TileEntityクラスを書き換えていきます。

TileEntityTomato.java

package test4mod;

import javax.annotation.Nullable;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.ItemStackHandler;

public class TileEntityTomato extends TileEntity{
	private ItemStackHandler inventory = new ItemStackHandler(1);

	@Override
	public NBTTagCompound writeToNBT(NBTTagCompound compound) {
		compound.setTag("inventory", inventory.serializeNBT());
		return super.writeToNBT(compound);
	}
	
	@Override
	public void readFromNBT(NBTTagCompound compound) {
		inventory.deserializeNBT(compound.getCompoundTag("inventory"));
		super.readFromNBT(compound);
	}
	
	@Override
	public boolean hasCapability(Capability<?> capability, @Nullable EnumFacing facing) {
		return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY || super.hasCapability(capability, facing);
	}
	
	@SuppressWarnings("unchecked")
	@Nullable
	@Override
	public <T> T getCapability(Capability<T> capability, @Nullable EnumFacing facing) {
		return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY ? (T)inventory : super.getCapability(capability, facing);
	}
}


1行目の
private ItemStackHandler inventory = new ItemStackHandler(1);
は引数にイベントリの数を入力します。今回は1つしかイベントリを持たないので1を入力。

後の行は正直あまり理解できていないのですが、前回int型の変数を保管するためにsetIntegerやgetIntegerを使ったのと同じようにイベントリを補完するために必要なコードくらいの認識です。


続いて、Blockクラスを書き換えていきます。

BlockTomato.java_part1

@Override
public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) {
	if (!world.isRemote) {
		TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
		IItemHandler itemHandler = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side);
		if (!player.isSneaking()) {
			if (player.getHeldItemMainhand().isEmpty()) {
				player.setHeldItem(hand, itemHandler.extractItem(0, 64, false));
			} else {
				player.setHeldItem(hand, itemHandler.insertItem(0, player.getHeldItemMainhand(), false));
			}
			te.markDirty();
		} else {
			ItemStack stack = itemHandler.getStackInSlot(0);
			if (!stack.isEmpty()) {
				System.out.println(stack.getItem().getRegistryName()+" "+stack.getCount());
			} else {
				System.out.println("Empty");
			}
		}
	}
	return true;
}

まず、右クリックしたときの処理を変更します。
サーバーかどうかを判断し、サーバーならTileEntityを生成します。
その後、非スニーク状態で、かつ、何も手にもっていない場合は引き出し、何か持っている場合は預ける処理をします。
スニーク状態で右クリックすると、中に何か入っている場合にはそのアイテム名と数を、空の場合はEmptyとそれぞれコンソール上に出力します。


BlockTomato.java_part2

@Override
public void breakBlock(World world, BlockPos pos, IBlockState state) {
	TileEntityTomato te = (TileEntityTomato) world.getTileEntity(pos);
	IItemHandler itemHandler = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, EnumFacing.NORTH);
	ItemStack stack = itemHandler.getStackInSlot(0);
	if (!stack.isEmpty()) {
		EntityItem item = new EntityItem(world, pos.getX(), pos.getY(), pos.getZ(), stack);
		world.spawnEntity(item);
	}
	super.breakBlock(world, pos, state);
}

次に、BreakBlockの処理を書き換えてブロックが壊されたときに中のアイテムをアイテムエンティティとしてドロップさせます。


これで実行してみました。

f:id:json_fileman:20190109013956g:plain


このときのログがこちらです。

f:id:json_fileman:20190109014035p:plain


ちゃんと出力されていますね!

次回はこのイベントリをguiを使って操作できるようにしたいと思います。

今回のファイルを載せておきます。
testmod.java
BlockTomato.java
TileEntityTomato.java