Modding > TurtleUpgrade


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

ComputerCraftにTurtleアップグレードを追加するModの製作について解説する。
対象:Minecraft Forgeを利用して独自のレシピを追加できるようなModding初級者以上。

参考資料:
執筆時のバージョン:
  • ComputerCraft 1.53 for Minecraft 1.5.2




Turtleアップグレード追加Modの概要

Turtleアップグレードとは、Digging TurtleやCrafty Turtleのように、Turtleにアイテムやブロックを装着(クラフト)することによって機能を追加することができるシステムである。

アップグレードの実装

TurtleアップグレードはITurtleUpgradeインターフェイスの実装クラスとして機能を実装し、その実装クラスをComputerCraftに登録することによってTurtleに装着できるようになる。機能追加に関するAPIも用意されているため、簡単に作ることができる。

アップグレードのタイプ

Turtleアップグレードには以下の2タイプがある。
Turtleアップグレードを製作するときは、どちらか一方のタイプを選択する必要がある。今のところ1つのTurtleには、1つのToolタイプと1つのPeripheralタイプ、または2つのPeripheralタイプを装着することができる(例:ダイヤクワ(Tool)とWireless Modem(Peripheral)でWireless Farming Turtle)。

アップグレードID

Turtleアップグレードには他のTurtleアップグレードと重複しないID番号を割り当てる必要がある。Mod製作者が割り当て可能なIDの範囲は【-1.52】では64~255、【1.53-】では64~32767である。既に配布されているModが使用しているTurtleアップグレードIDはComputerCraft Wikiで確認することができる。

基本的なTurtleアップグレード追加Modの例

ToolタイプとPeripheralタイプのTurtleアップグレードを追加する。

このサンプルは以下の4クラスからなる。
  • BasicUpgrades
  • TurtleBasicTool
  • TurtleBasicPeripheral
  • PeripheralTurtleBasicPeripheral
簡略化のためProxyシステムは利用していない。

なお、このサンプルmodの前提modは【MinecraftForge】と【ComputerCraft】である。

BasicUpgrades.java

Modのメインクラス。@ModアノテーションによりForgeModLoaderにロードされる。

package sample.upgrade;
 
import net.minecraftforge.common.Configuration;
import net.minecraftforge.common.Property;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkMod;
import dan200.turtle.api.TurtleAPI;
 
@Mod(modid="BasicUpgrades", name="BasicUpgrades", version="0.0.0", dependencies="after:CCTurtle")
@NetworkMod(clientSideRequired=true, serverSideRequired=false)
public class BasicUpgrades {
 
	public static int basicToolUpgradeID;
	public static int basicPeripheralUpgradeID;
 
	@Mod.PreInit
	public void preInit(FMLPreInitializationEvent event)
	{
		Property Prop;
		Configuration cfg = new Configuration(event.getSuggestedConfigurationFile());
		cfg.load();
 
		Prop = cfg.get("upgrade", "basicToolUpgradeID", 110);
		basicToolUpgradeID = Prop.getInt();
		Prop = cfg.get("upgrade", "basicPeripheralUpgradeID", 111);
		basicPeripheralUpgradeID = Prop.getInt();
 
		cfg.save();
	}
 
	@Mod.Init
	public void init(FMLInitializationEvent event)
	{
		TurtleAPI.registerUpgrade(new TurtleBasicTool());
		TurtleAPI.registerUpgrade(new TurtleBasicPeripheral());
	}
}

@Modアノテーションを付加したクラスがForge Mod Loaderにロードされる。ここでModの情報も登録している。特筆すべきは dependencies="after:CCTurtle" という値で、これはこのModをComputerCraftよりも後に読み込ませる効果がある。

@Mod.PreInitアノテーションを付加したメソッド preInit (@Mod.Initメソッドの前に呼び出される)でコンフィグファイルを読み込み、TurtleアップグレードIDのブロックIDを取得している。ToolアップグレードIDのデフォルト値は 110 、PeripheralアップグレードIDのデフォルト値は 111

@Mod.Initアノテーションを付加したメソッド init (初期化時に呼び出される)でComputerCraftにTurtleアップグレードを登録している。TurtleアップブレードのクラスをTurtleAPI.registerUpgrade()で登録することにより、そのTurtleアップグレードがゲーム中で使用可能になる。

TurtleBasicTool.java

ITurtleUpgradeの実装クラスでToolタイプのTurtleアップグレードを実装している。このクラスで、アップグレードをTurtleに装着するためのアイテム(装着レシピはComputerCraftによる自動追加)や、装着後の外観、採掘・攻撃時の動作などを指定することができる。

このサンプルでは、簡単な採掘と攻撃ができるようになっている。

package sample.upgrade;
 
import java.util.ArrayList;
import java.util.Iterator;
 
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Facing;
import net.minecraft.util.Icon;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import dan200.computer.api.IHostedPeripheral;
import dan200.turtle.api.ITurtleAccess;
import dan200.turtle.api.ITurtleUpgrade;
import dan200.turtle.api.TurtleSide;
import dan200.turtle.api.TurtleUpgradeType;
import dan200.turtle.api.TurtleVerb;
 
public class TurtleBasicTool implements ITurtleUpgrade
{
	public ItemStack upgradeItem = new ItemStack(Item.pickaxeGold);	
 
	@Override
	public int getUpgradeID()
	{
		return BasicUpgrades.basicToolUpgradeID;
	}
 
	@Override
	public String getAdjective()
	{
		return "Tool";
	}
 
	@Override
	public TurtleUpgradeType getType()
	{
		return  TurtleUpgradeType.Tool;
	}
 
	@Override
	public ItemStack getCraftingItem()
	{
		return upgradeItem;
	}
 
	@Override
	public boolean isSecret()
	{
		return false;
	}
 
	@Override
	public IHostedPeripheral createPeripheral(ITurtleAccess turtle,
			TurtleSide side)
	{
		return null;
	}
 
	@Override
	public boolean useTool(ITurtleAccess turtle, TurtleSide side,
			TurtleVerb verb, int direction)
	{
		switch( verb )
		{
		case Dig:
			return dig(turtle, direction);
		case Attack:
			return attack(turtle, direction);
		}
		return false;
	}
 
	private boolean dig(ITurtleAccess turtle, int dir)
	{
		World world = turtle.getWorld();
		Vec3 position = turtle.getPosition();
 
		if (position == null)
		{
			return false;
		}
 
		int newX = (int)position.xCoord + Facing.offsetsXForSide[dir];
		int newY = (int)position.yCoord + Facing.offsetsYForSide[dir];
		int newZ = (int)position.zCoord + Facing.offsetsZForSide[dir];
 
		if ( (newY < 0) || (newY >= world.getHeight()) )
		{
			return false;
		}
 
		int blockID = world.getBlockId(newX, newY, newZ);
		Block block = Block.blocksList[blockID];		
 
		if ( (blockID == 0)
			|| (blockID == Block.bedrock.blockID)
			|| (block.getBlockHardness(world, newX, newY, newZ) <= -1.0F)
			)
		{
			return false;
		}
 
		int matadata = world.getBlockMetadata(newX, newY, newZ);
		ArrayList items = block.getBlockDropped(world, newX, newY, newZ, matadata, 0);
		Iterator it = items.iterator();
 
		while (it.hasNext())
		{
			ItemStack item = (ItemStack)it.next();
			if ( !turtle.storeItemStack(item) )
			{
				int[] oppositeSide = { 1, 0, 3, 2, 5, 4 };
				if ( !turtle.dropItemStack(item, oppositeSide[turtle.getFacingDir()]) )
				{
					turtle.dropItemStack(item, turtle.getFacingDir());
				}
			}
		}
 
		world.playAuxSFX(2001, newX, newY, newZ, blockID + matadata * 4096);
		world.setBlock(newX, newY, newZ, 0, 0, 3);
 
		return true;
	}
 
	private boolean attack(ITurtleAccess turtle, int dir)
	{
		return turtle.attackWithItemStack(new ItemStack(Item.swordDiamond), dir, 2.0F);
	}
 
	@Override
	public Icon getIcon(ITurtleAccess turtle, TurtleSide side) {
 
		return upgradeItem.getIconIndex();
	}
 
}

TurtleアップグレードIDはgetUpgradeIDの戻り値で指定する。このサンプルでは前述の@MODクラスBasicUpgradesのメンバー変数から取得して指定している。アップグレードのタイプはenum TurtleUpgradeTypeの列挙子でToolを指定。装着したTurtleに付く形容詞は"Tool"を指定(つまり"Tool Turtle"になる)。クラフトに必要なアイテムと装着したアップグレードの外観は金のツルハシ。Toolタイプなので無用なcreatePeripheralではnullを返す。

Toolタイプの本領はuseToolによって発揮される。このサンプルではverbの種類(DigかAttackか)で分岐させた後、それぞれ別のメンバー関数(digとattack)に処理させている。
  • dig
Turtleに採掘させる場合、採掘に必要な処理そのものを書く必要がある。
まず、useToolから得たITurtleAccess turtleによりTurtleの座標を得て、その座標と採掘する方向dirから採掘すべきブロックの座標と種類、メタデータを取得する。次に、採掘すべきブロックが採掘できるか(高度、種類)を調べる。採掘可能だった場合は採掘して得られるアイテムを取得し、スロットに入りきらなかった場合はドロップする。最後に採掘の効果音を鳴らし、採掘したブロックを消す(空気ブロックにする)。
  • attack
採掘処理が難しい一方で、攻撃は専用メソッドが用意されているため簡単である。このサンプルではダイヤソードの2倍の攻撃力14(ハート7個分)でdir方向を攻撃する。

ちなみに、それぞれのverdは単にTurtle側のturtle.dig()とturtle.attack()で呼び出されるというだけで、必ずしもTurtleが採掘や攻撃を行わなければならないという訳ではない。

TurtleBasicPeripheral.java

ITurtleUpgradeの実装クラスでPeripheralタイプのTurtleアップグレードを実装している。

package sample.upgrade;
 
import net.minecraft.block.Block;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Icon;
import dan200.computer.api.IHostedPeripheral;
import dan200.turtle.api.ITurtleAccess;
import dan200.turtle.api.ITurtleUpgrade;
import dan200.turtle.api.TurtleSide;
import dan200.turtle.api.TurtleUpgradeType;
import dan200.turtle.api.TurtleVerb;
 
public class TurtleBasicPeripheral implements ITurtleUpgrade
{
	public ItemStack upgradeItem = new ItemStack(Block.stone, 1, 0);
 
	@Override
	public int getUpgradeID() {
		return BasicUpgrades.basicPeripheralUpgradeID;
	}
 
	@Override
	public String getAdjective() {
		return "Peripheral";
	}
 
	@Override
	public TurtleUpgradeType getType() {
		return TurtleUpgradeType.Peripheral;
	}
 
	@Override
	public ItemStack getCraftingItem() {
		return upgradeItem;
	}
 
	@Override
	public boolean isSecret() {
		return false;
	}
 
	@Override
	public IHostedPeripheral createPeripheral(ITurtleAccess turtle,
			TurtleSide side) {
		return new PeripheralTurtleBasicPeripheral(turtle, side);
	}
 
	@Override
	public boolean useTool(ITurtleAccess turtle, TurtleSide side,
			TurtleVerb verb, int direction) {
		return false;
	}
 
	@Override
	public Icon getIcon(ITurtleAccess turtle, TurtleSide side) {
		return Block.stoneSingleSlab.getBlockTextureFromSide(0);
	}
 
}

TurtleアップグレードIDはgetUpgradeIDの戻り値で指定する。このサンプルでは前述の@MODクラスBasicUpgradesのメンバー変数から取得して指定している。アップグレードのタイプはenum TurtleUpgradeTypeの列挙子でPeripheralを指定。装着したTurtleに付く形容詞は"Peripheral"を指定(つまり"Peripheral Turtle"になる)。クラフトに必要なアイテムは石ブロック。装着したアップグレードの外観は石ハーフブロックの下面。

Peripheralタイプなので、createPeripheralでは周辺機器の機能を実装したIHostedPeripheralの実装クラスのインスタンスを返す。このサンプルでは、後述のPeripheralTurtleBasicPeripheralのインスタンスを返している。ToolタイプではないのでuseToolは呼び出されない。

PeripheralTurtleBasicPeripheral.java

IHostedPeripheralの実装クラスでPeripheralタイプのアップグレードの周辺機器としての動作を実装している。IHostedPeripheralはIPeripheralのサブインターフェイスである。周辺機器ブロック追加Modも参照のこと。

package sample.upgrade;
 
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.Vec3;
import dan200.computer.api.IComputerAccess;
import dan200.computer.api.IHostedPeripheral;
import dan200.turtle.api.ITurtleAccess;
import dan200.turtle.api.TurtleSide;
 
public class PeripheralTurtleBasicPeripheral implements IHostedPeripheral
{
	private ITurtleAccess m_turtle;
	private IComputerAccess m_computer;
	private TurtleSide m_side;
 
	PeripheralTurtleBasicPeripheral(ITurtleAccess turtle, TurtleSide side)
	{
		m_turtle = turtle;
		m_side = side;
	}
 
	@Override
	public String getType()
	{
		return "turtlebasic";
	}
 
	@Override
	public String[] getMethodNames()
	{
		return new String[] { "test", "getPosition" };
	}
 
	@Override
	public Object[] callMethod( IComputerAccess computer, int method, Object[] arguments )
			throws Exception
	{
		switch( method )
		{
		case 0:
			// test
			if( arguments.length < 1 ) {
				throw new Exception("Expected argument");
			}
			return new Object[] { arguments[0] };
 
		case 1:
			//getPosition
			Vec3 pos = m_turtle.getPosition();
			if( arguments.length > 0
					&& arguments[0] instanceof Boolean
					&& true == (Boolean)arguments[0])
			{
				return new Object[] {
						"Position: " + (int)pos.xCoord
						+ ", " + (int)pos.yCoord
						+ ", " + (int)pos.zCoord
						};
			}
			return new Object[] {
					Integer.valueOf((int)pos.xCoord),
					Integer.valueOf((int)pos.yCoord),
					Integer.valueOf((int)pos.zCoord)
					};
		}
 
		return null;
	}
 
	@Override
	public boolean canAttachToSide(int side)
	{
		return true;
	}
 
	@Override
	public void attach( IComputerAccess computer)
	{
		m_computer = computer;
 
		System.out.printf("[TurtleBasicPeripheral] Attached to Computer #%d (side: %s)\n",
				m_computer.getID(), m_computer.getAttachmentName()
				);
	}
 
	@Override
	public void detach( IComputerAccess computer )
	{
		System.out.printf("[TurtleBasicPeripheral] Detached from Computer #%d\n",
				computer.getID()
				);
	}
 
	@Override
	public void update()
	{
 
	}
 
	@Override
	public void readFromNBT(NBTTagCompound nbttagcompound) {
 
	}
 
	@Override
	public void writeToNBT(NBTTagCompound nbttagcompound) {
 
	}
 
}

周辺機器ブロック追加Modと似たような構成になっているが、以下のような点が違う。
  • attachはTurtle起動時、detachはattach後のTurtle破壊時に呼び出される(Turtleアップグレードの特徴)
  • attach後に毎Tick呼び出されるupdateが追加されている(IHostedPeripheralの特徴)
  • NBTへデータを読み書きするメソッドが追加されている(IHostedPeripheralの特徴)

このサンプルではタートルへのアクセスインターフェイスを取得するために、引数付きのコンストラクタを追加している。これは周辺機器側からタートルへアクセスするのに必要である。また、attach時にTurtle内のComputerのアクセスインターフェイスを取得している。周辺機器ブロックと違い、アップグレード周辺機器に接続するのは装着されたTurtleだけなので、それらは単なるメンバ変数に保存している。

周辺機器のメソッドとして、周辺機器ブロック追加Modのサンプルと同様の"test"と、Turtleの座標を取得する"getPosition"を実装している。getPositionは引数にtrueを指定して呼ばれた時は座標を文字列にして返し、それ以外のときは座標を3つの数値で返す(サンプルではあるが、GPSから考えればチート性能なメソッドである)。