マインクラフトでトロッコの自動運行をする

# 概要

CIBPluginは、Minecraftサーバ(SpigotMCサーバー)上でトロッコの自動運行を行うプラグインです。
本記事ではCIBPluginの開発背景と技術的概要を説明し、現在の問題点とその改善案について説明する。
添付資料として、プラグインによるトロッコ走行時のYouTube動画とGitHub上のコードを提示する。

# プラグインの開発背景

スタッフをしていたMinecraftサーバー内でトロッコ鉄道網を整備した人がいたが、ほとんどのプレイヤーがアイテムのトロッコを持ち歩くのが面倒で利用頻度が低かった。
トロッコの配布を駅で行うなどしてみたが補充の手間がかかり、また線路上にエンティティ化したトロッコが放置されるなどの問題が発生した。
またMinecraftのトロッコ鉄道にはつきものだが、加速用レールの効率的な配置を考えるのも手数がかかっていた。


# 主な解決方法

<dl>
<dt>トロッコの生成：</dt>
<dd>自動で線路上に配置する駅設備を用意する。</dd>

<dt>トロッコの自動運行：</dt>
<dd>線路上に配置されたトロッコが自動的に発車するようにする。</dd>
<dd>加速用レールがなくても運行するようにする。</dd>
<dd>次のトロッコ用の駅設備では一時停車をするようにする。</dd>
</dl>


# トロッコの自動配置

専用看板の右クリックでトロッコを配置させる仕組みとしています。



```抜粋1：net.cibmc.spigot.cib.EL_CIBSign.java
// ブロックへの右クリック操作だったか調べる
if(!(event.getAction().equals(Action.RIGHT_CLICK_BLOCK))) return;

// 右クリックされたのが看板か判定する
BlockState bs = event.getClickedBlock().getState();
if (!(bs instanceof Sign)) return;
Sign s = (Sign)bs;

// 0行目にプラグイン用看板の判別用テキストがあるか調べる
if(!s.getLine(0).equals(CIBCommon.CIB_SIGN_STRING)) return;

// すでに何らかの乗り物に乗っていれば二重押しとみなす
if(event.getPlayer().getVehicle() != null) return;
```

判定用テキストは[CIB_MC]とし、0行目にこのテキストがある看板を設置できるのは権限を持っているプレイヤーのみとしています。(記述は別箇所)
~~いつだったか失念したのですが、看板の種類が木材ごとに追加されたときに判定のswitch-case文が長くなりました。~~
`instanceof` を使って、看板かを判定しています。



```抜粋2：net.cibmc.spigot.cib.EL_CIBSign.java
// トロッコ生成
Location locRail = event.getClickedBlock().getLocation().add(0.5,-1,0.5);
Minecart entMC = (Minecart)event.getPlayer().getWorld().spawnEntity(locRail, EntityType.MINECART);

// 判定用メタデータ設定等
entMC.setMetadata(CIBCommon.CIB_METADATA_TYPE, new FixedMetadataValue(this.plugin, true));
entMC.setSilent(true);

// 生成したトロッコにプレイヤーを乗せる
Player pl = event.getPlayer();
entMC.addPassenger(pl);

// 運行用データオブジェクト生成、管理対象として登録
MinecartInfo mci = new MinecartInfo(entMC, event.getClickedBlock().getLocation());
mci.setSpeed(MinecartSpeed.SPEED_RIDING);
synchronized(plugin.minecartBox){
	plugin.minecartBox.put(pl.getUniqueId().toString(), mci);
}
```

先程、線路上にトロッコを配置するとだけ書きましたがついでにプレイヤーを乗せてしまいます。
運行用データは生成したあとにHashMapにputして、スケジューラで順次操作を行います。



# トロッコの自動運行

自動運行については、`net.cibmc.spigot.cib.SyncAMC`がその役目を担っています。
こちらは定期実行タスクになっていて何をしているかというと、状況に合わせてトロッコのエンティティに対して`setMaxSpeed`を行っています。
直線では最大速度で運行しますが、そのままだとカーブや坂でレールから外れるので減速するようにしています。

全部抜粋すると長くなるので、状況を示す各スピードの値ごとの処理をいくつか掲載します。

```乗車時処理抜粋：net.cibmc.spigot.cib.SyncAMC.java
//効果音が演奏中でないか確認
if(!plugin.musicBox.containsKey(mci)){
	// 演奏待ちHashMapに追加
	plugin.musicBox.put(mci, plugin.mbn.shallowCopyFactory());
	
	// イベントを発生させたのがプレイヤーか確認
	if(ent instanceof Player){
		Player pl = (Player)ent;
		Sign s = (Sign) mci.startLoc.getBlock().getState();
		// 降車専用液設備でないか確認
		if(s.getLine(1).equalsIgnoreCase(CIBCommon.STR_TEREMINAL)){
			pl.sendMessage(ChatColor.GREEN + "[CIB] " + ChatColor.WHITE + "こちらは降車専用です。ご乗車になれません。");
			mc.eject(); // 降車専用なので乗客を下ろす
			break;
		}else{
			pl.sendMessage(ChatColor.GREEN + "[CIB] " + ChatColor.WHITE + "まもなく発車いたします。");
		}
	}
	// 速度(ステートに相当)を変更
	mci.setSpeed(MinecartSpeed.SPEED_WAIT);
}
```

```発車待ち処理抜粋：net.cibmc.spigot.cib.SyncAMC.java
//効果音が演奏中でないか確認
if(!plugin.musicBox.containsKey(mci)){
	Sign s = (Sign) mci.startLoc.getBlock().getState();

	// 看板の記述に応じて発車、または強制降車
	if(s.getLine(1).equalsIgnoreCase(CIBCommon.STR_NORTH)){
		mc.setVelocity(CIBCommon.VEC_NORTH);
	}else if(s.getLine(1).equalsIgnoreCase(CIBCommon.STR_SOUTH)){
		mc.setVelocity(CIBCommon.VEC_SOUTH);
	}else if(s.getLine(1).equalsIgnoreCase(CIBCommon.STR_EAST)){
		mc.setVelocity(CIBCommon.VEC_EAST);
	}else if(s.getLine(1).equalsIgnoreCase(CIBCommon.STR_WEST)){
		mc.setVelocity(CIBCommon.VEC_WEST);
	}else{
		if(ent instanceof Player){
			Player pl = (Player)ent;
			pl.sendMessage(ChatColor.GREEN + "[CIB] " + ChatColor.WHITE + "こちらはターミナルです。このトロッコは回送となります。");
		}//End if
		mc.eject();
		break;
	}//End if

	// 速度変更
	mci.setSpeed(MinecartSpeed.SPEED_START);

	// BossBarとして速度計を表示
	mci.setVisibleBossBarForPassenger(true);
}
```

```速度段階上げ抜粋：net.cibmc.spigot.cib.SyncAMC.java
// 効果音を再生中でないか確認
if(!plugin.musicBox.containsKey(mci)){
	// 次の効果音を再生
	plugin.musicBox.put(mci, MBNForStarting.SPEED_NA.getMBN());

	// スピード変更
	mci.setSpeed(MinecartSpeed.SPEED_NA);
}
```
感のいい方はお気づきかと思いますが、効果音再生が終わるのをタイマー代わりに使う実装になってます。
よい開発者の方は真似しないでね☆


# 追加の便利機能

前掲のコードにも一部記述がありましたが、下記2点の機能があります。

- 発車メロディー、車内アナウンス(チャットテキスト)などを流す。
- 速度計を表示する。(BossBar利用)


# 既知の問題点と解決案

<dl>
<dt>既に駅設備に他のトロッコが停車中だと、後続のトロッコが通過してしまう。</dt> 
<dd>次駅設備との距離を計測して、前回より距離が遠くなれば停まるようにする手法を思案中。</dd>

<dt>車内アナウンスの多言語化</dt>
<dd>アナウンスデータのオブジェクト化(？)</dd>
</dl>

# Plugin JARファイル の配布

配布は[公式サイト](https://rainyvillage.jp/cibplugin/)で行っています。

# 参考動画(YouTube)

https://www.youtube.com/watch?v=mZ3MO0arn-o

※昔の動画ですが、機能としては当時から変わっていません

# プラグインソースコード(GitHub)

https://github.com/CIB-MC/CIBPlugin/tree/main/src/main/java/net/cibmc/spigot/cib


# 最後に

楽しいトロッコライフを！