【Minecraft】UHCを作ろう!#2

前回の続きとなるので前回を見てから読むことを推奨する。

前回はプラグインの基盤を作成したところで終わった。

今回は基本的なイベントリスナーを実装し、ワールドの自動生成を行うクラスを作成する。

イベントリスナーの実装

必要となるイベントを先に考えておこう。

・BlockBreak BlockPlace

・PlayerDeath

・PlayerJoin PlayerQuit

・EntityDamageByEntity

 

BlockBreak BlockPlace

ロビーでブロック設置、破壊を無効にする必要があると思うので実装する必要があります。

PlayerDeath

プレイヤーが死亡したときにヘッドをドロップする処理や、プレイヤーのキル数を加算する処理が必要なので実装します。

PlayerJoin PlayerQuit

プレイヤーがログインしたときにリセットしたり、スコアを取得する必要があるので実装します。

EntityDamageByEntity

PVPが無効の時にPVPができないようにする必要があるので実装します。

 

UHCの仕様を踏まえると、最低限以下のイベントも必要となりますね。

FoodLevelChange

体力ゲージの調整

EntityRegainHealth

資源回復の無効化

 

現時点で作成可能なイベントリスナーをさくっと作ってしまいましょう。

@EventHandler
public void onBreak(BlockBreakEvent e){
if (e.getPlayer().getWorld().getName().equals("lobby")){
e.setCancelled(true);
}
}
@EventHandler
public void onPlace(BlockPlaceEvent e){
if (e.getPlayer().getWorld().getName().equals("lobby")){
e.setCancelled(true);
}
}
@EventHandler
public void onFood(FoodLevelChangeEvent e){
e.setFoodLevel(e.getFoodLevel() - 1); // sorry no debug this code
}
@EventHandler
public void onRegain(EntityRegainHealthEvent e){
if (e.getRegainReason().equals(RegainReason.SATIATED)){
e.setCancelled(true);
}
}

 

次にワールドの自動生成用のクラスを作成します。

Bukkitでワールドを作成するには

WorldCreator というクラスを作成してそれを Bukkit#createWorld に渡す必要があります。

public World Create(String worldName){
WorldCreator wc = new WorldCreator(worldName);
// wc.seed(long) seed 値の設定などができます。
World w = Bukkit.createWorld(wc);
return w;
}

 

ワールドをアンロードするには

public void Unload(String worldName, boolean save){
Bukkit.unloadWorld(worldName, save);
}

です。とても分かりやすく簡単ですね。

 

このままでは、サーバーを再起動した際に、ワールドが読み込まれておらず手動で読み込む必要があるうえに読み込んだ際は前回作成したデータが引き継がれてしまいます(新規に作成されない)

そのため、生成する前に一度ワールドを削除する必要があります。

public void Remove(String worldName){
File f = new File(Bukkit.getWorldContainer(), worldName);
if (f.exists()){
IODelete(f);
}
}
public void IODelete(File file){
if (file.isFile()){
file.delete();
}else {
File[] files = file.listFiles();
for (File f : files){
IODelete(f);
}

file.delete();
}
}

 

「Remove();」を呼び出せば削除することができます(削除する前にワールドをアンロードする必要がありますよ!)

ここまでのコードを一つのクラスにまとめれば使いやすく便利なクラスの誕生です。

UHC以外でも使い道はありますね。

 

【Minecraft】UHCを作ろう!#1

始める前に

以下のことが最低限できるものとする

・サーバー構築、プラグイン導入

・Javaの基本的な構文

・EclipseなどのIDEでプラグインを作成できる

 

UHC とは 「Ultra Hardcore Champion」の略である。

ゲーム説明

大手サーバーでは最大70人。装備を揃えて戦うゲーム。

満腹ゲージによる自動再生はなく、金リンゴを食べる、敵を倒したときに落とす頭を使う、回復系ポーションの使用などを使用しないと一切回復が行えないのが、このゲーム最大の特徴である。

ゲームの流れ

・ゲーム開始。ランダムな場所にスポーンする。

・10分後 PVPが許可され、敵にダメージを与えることができる。

・残り15人になると、デスマッチまで10分になる。

・10分後 全員一定の場所にテレポート。残り一人になると試合終了。デスマッチが始まって15分経過した場合はキル数が最多のプレイヤーの勝ちとなる(最多同数のプレイヤーがいた場合はDraw)。

UHCの使用(Wikiより参照)

  • 満腹ゲージが一定以上の場合でも体力が自然回復しない。回復するには金リンゴ、ポーション、プレイヤーの頭などが必要。
  • 金リンゴの回復する量が通常の2倍(4♡+衝撃吸収2♡)
  • プレイヤーを倒すと頭(Head)をドロップする。その頭を右クリックすることでチームの全員が再生エフェクトで3♡回復できる。(Soloでは4♡)
  • 即時回復のポーションの回復量が異なる。(Ⅰ:4♥、Ⅱ:8♥)
  • 毒のスプラッシュポーションの効果時間が異なる。(Ⅰ:33秒、Ⅱ:5秒)
  • 力のポーションの効果は通常の62%に弱体化されている。
  • 移動速度上昇の効果量が変更されている。(Ⅰ:+20%、Ⅱ:+30%、Ⅲ:+45%)
  • ガストの涙を使って再生のポーションは醸造することはできない。
  • プレイヤーのHPが40(20♡)。
  • 最初の準備期間は、全てのプレイヤーに火炎耐性の効果がついている。
  • 通常より満腹度の減りがかなり少ない。
  • ワールドボーダーがある。Teamだと+-1500,Soloだと+-1000からPvPが有効になると徐々に狭くなっていく。
    • ワールドボーダーの外にいると窒息しているかのようにダメージを受ける
  • ワールドの真ん中周辺 (X.0 Z.0から直径約100m)が開けた土地になっている。
  • 海洋バイオームが存在しない(サバンナバイオームになる)。
  • 常に昼である(デスマッチ会場は夜にもなり、MOBが湧く)。
  • 砂利を壊したときに25%で火打石をドロップする(通常とは確率が違う)。
  • MOBがドロップするアイテムが多少異なる。これについては後述。
  • MOBからの経験値が通常の2倍。
  • ハサミでヒツジの毛を刈ると33%の確率で糸を1本ドロップする。
  • 子供の馬は砂糖を上げると大人に成長する
  • ネザーでは、ネザー要塞以外でもブレイズがスポーンする。(バグで通常世界のモブが沸くことがある)
  • 通常とは違う様々な特殊レシピがある。コインを使ってショップで解放できる。
  • きらめくスイカの作り方が異なる。スイカを中央に、金インゴットを上下左右に1個ずつ計4つ置いてクラフトする。 画像
  • Teamモードでは、ゲームが始まる前の待機時間に /team invite (プレイヤー名)で他のプレイヤーをチームに誘い、/team accept (プレイヤー名)で自分を招待したプレイヤーのチームに参加できる。
  • Tabキーを押すとプレイヤー一覧と、各自のHPが表示されるが、そこには金ハート分のHPが加算されている。
    • 弓、釣り竿などを敵に当てた際にHPが表示されるが、それには金ハートは加算されてないので注意)。
  • 糸を使って弓を金床で修理できる。レベルは使用しない。
  • エンチャントテーブルでは幸運のエンチャントが付与されない。
  • 鉱石の生成量が増加している。
  • xray対策のため、鉱石はプレイヤーが一定距離内に入ると表示される。
    • 鉱石が表示されているのに、破壊しても鉱石がドロップしないバグが時々起きる。

これ以上の詳細な説明は以下のWikiを参照してもらいたい。

Wiki

サーバー準備

パート1から早速作っていこうと思う。

だがそのためには、初めにサーバーを用意しなければならない。

今回はSPIGOTの 1.8.8を使用する。

プラグイン開発はSPIGOT(Bukkit)ベースなのでBukkit派生であればなんでもかまわない(例えばPaper)

 

さて、サーバー(API)の準備ができたところで、プラグインを作っていこう。

EclipseなどのIDEを立ち上げてプロジェクトを作成して、ビルドパスにAPIを通そう。

次にメインとなるクラスを作成しよう。

今回は「Game」というクラスを作成した。

中身は何もない。

この「Game」というクラスがプラグインの基準というか、中心となる。なるべく完結に綺麗にコードをまとめて見やすくする努力をしよう。

onEnable と onDisable を作成する。

「static Game Instance;」を作成して、onEnable ないに 「Instance = this;」 

onDisable ないに 「Instance = null」を追記。

onDisable は必須ではないが、Javaの性質上 static で定義した変数は必ずメモリを解放してあげてください。

プログラムが終了するまでメモリを解放されず、確保したままになってしまいます。

次のパートではイベントリスナーを実装します。

【Bukkit】【Spigot】めっちゃ便利!インベントリをファイルに保存して活用する方法【プラグイン開発】

簡単にインベントリをファイルに保存する方法です。

拡張インベントリを作るときに便利だと思います!

組み込み型なので自作プラグインに埋め込んでください!

 

※お借りしたコード

https://jyn.jp/ – CustomConfig 

 

1.完成例

完成して実際に動かしたものの動画があります!

 

2.制作環境について

IDE Eclipse 

Java java7

Spigot 1.14.4

 

3.保存形式

YAMLにて保存しました

 

4.コード

とってもシンプルです。

※プラグインの無効化時にクリーン処理を挟んでください。 InventorySQL.clean();

※プレイヤーが退出したときにクリーン処理を挟んでください。 InventorySQL.clean(Player#getUniqueId());

※インベントリをそのまま保存しておくとメモリリークが心配なので毎回作成していますが、必要に応じて変更してください。

※ファイル名、フォルダを追加する場合は必要に応じて引数を変更してください。


import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;

import CustomConfig;

public class InventorySQL{
private static HashMap<UUID, InventorySQL> datas = new HashMap<>();
public static InventorySQL getData(UUID key) {
if (!datas.containsKey(key)) {
return new InventorySQL(key);
}
return datas.get(key);
}

public static void clean() {
for (UUID key : datas.keySet()) {
clean(key);
}
}
public static void clean(UUID key) {
if (datas.containsKey(key)) {
datas.get(key).save();
datas.remove(key);
}
}

private UUID key;
private ItemStack[] contents;
public InventorySQL(UUID key) {
this.key = key;

reload();


datas.put(key, this);
}

public void reload() {
contents = new ItemStack[9 * 6];
try {
CustomConfig CC = new CustomConfig(key.toString() + ".inv.yml");
FileConfiguration config = CC.getConfig();

if (config.contains("inventory")) {
List<?> list = config.getList("inventory");
int max = list.size();
for (int cur = 0; cur < max; cur++) {
Object obj = list.get(cur);
if (obj instanceof ItemStack) {
contents[cur] = (ItemStack) obj;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

public void open(Player p) {
Inventory inv = Bukkit.createInventory(null, 9 * 6, "§eBackPack");
inv.setContents(contents);
p.openInventory(inv);
}

public void closeInventory(Player p, Inventory inv) {
contents = inv.getContents();
}

public void save() {
CustomConfig CC = new CustomConfig(key.toString() + ".inv.yml");
FileConfiguration config = CC.getConfig();

/**
* Save as list type
*/
List<ItemStack> list = new ArrayList<>(Arrays.asList(contents));

config.set("inventory", list);
CC.saveConfig();
}
}

 

今回はやりませんでしたが、Blob形式に変換できればSQLにも保存できると思います。