【C#, Minecraft】Blazorアプリ で チェストの中身(アイテム)を取得する

Minecraft 1.13 以降に追加された data コマンドを用いると、チェストの中身を取得することができます。今回は /data get block <チェストの座標> Items というコマンドを用いて、チェスト内のアイテムを取得してみます。

目標

Blazor アプリで任意の座標に設置されたチェストの中身(アイテム)を取得して、表として表示する!

実行環境

  • Windows 10
  • .NET Core 5.0.100
  • Visual Studio 2019 Version 16.8.2
  • Minecraft 1.16.3 (バニラ版, RCON許可済みサーバ)
  • CoreRCON 5.0.0 (NuGet)

1. Blazor アプリの作成

プロジェクト作成と基本的な書き方はこちらを参照してください。CoreRCONの導入方法もここに書いています。

blog.takunology.jp

まずはチェストアイテムを表示するためのページを作成します。ソリューションエクスプローラ―の Pages フォルダで右クリックして 追加 から 新しい Razor コンポーネント を追加してください。名前は自由に決めてください。自分は ChestItemsView.razor としました。できたら中身をこのように書いてください。はてなブログの都合上、ソースコードは区別していますが、同じファイル内に続けて記述してください。

@page "/ChestItems"

<h1>ChestItems</h1>
<p>チェストアイテムの中身を取得します。</p>

<div class="mt-3">
    <button type="button" class="btn btn-outline-primary" @onclick="GetItem">アイテムの取得</button>
</div>

<p>
    <div class="border" style="padding:10px;">
        @ItemData
    </div>
</p>
@code{
    private string ItemData { get; set; }

    static IPAddress ipaddress = IPAddress.Parse("127.0.0.1");
    static ushort port = 25575;
    static string password = "minecraft";
    static RCON rcon = new RCON(ipaddress, port, password);

    private string command = $"/data get block <チェストの座標> Items";

    private async Task GetItem()
    {
        await rcon.ConnectAsync();

        var result = await rcon.SendCommandAsync(command);

        ItemData = result;
    }
}

チェストの座標は皆さんの環境に合わせて変更してください。

2. メニューの追加と実行

次はこのページにアクセスするためのメニューを追記します。ソリューションエクスプローラ―内の Shared フォルダに入っている NavMenu.razor を開いてください。開くと色々書いてありますが、次のように追記してください。

<li class="nav-item px-3">
    <NavLink class="nav-link" href="/ChestItems" Match="NavLinkMatch.All">
        <span class="oi oi-box" aria-hidden="true"></span> ChestItems
    </NavLink>
</li>

f:id:takunology:20201214183847j:plain

出来たら実行してみましょう。Minecraftサーバを起動後、アイテムを入れたチェストを用意し、アプリから「アイテムの取得」をクリックしてみてください。

f:id:takunology:20201214183906p:plain

スゴイ文字列で表示されると思います。これをマインクラフト上でも同じように実行してみて、同じデータが取得できていればOKです。

3. 不要な文字の除去

このままでは文字が大量で分かりにくいので、正規表現や分割などを使って扱いやすいような形にします。 上記の結果を見てみると、Json のようにみえますが、残念ながら Json ではなく JavaScript のオブジェクトです。これをデシリアライズすれば解決なのですが、さらに残念なことにライブラリがない(見つからなかった)ので、自力で何とかします。

まずはアイテムデータを管理するためのクラス ChestItems.csData ディレクトリに作成し、次のように記述します。

public class ChestItems
{
    public string ItemID { get; set; }

    public string ItemCount { get; set; }

    public string ItemSlot { get; set; }
}

上から順にアイテム名(ID)、アイテムの数量、アイテムスロット(何番目にあるか)を保持しておくためのプロパティです。

次は Data ディレクトリに ChestItemsDataService.cs を作成し、この中で文字データの整理をしていきます。また、整理したデータを配列に格納し、表に出力するためのメソッドも作ります。少し複雑になりますが、仕方ありません。

public class ChestItemsDataService
{
    public ChestItems[] Itemlist { get; set; }

    public void Extraction(string data)
    {
        data = data.Substring(data.IndexOf("[")); // [ 以降のデータを得る
        data = Regex.Replace(data, @"[\[{\]\s]", ""); // [, ], { 記号を削除
        string[] split_data = data.Split("},"); // }, 記号で分割

        Itemlist = new ChestItems[split_data.Length];

        for (int i = 0; i < split_data.Length; i++)
        {
            Itemlist[i] = new ChestItems(); 

            string[] str = split_data[i].Split(",");

            for (int j = 0; j < str.Length; j++)
            {
                if (str[j].Contains("Slot:"))
                {
                    Itemlist[i].ItemSlot = Regex.Replace(str[j], "[^0-9]", "");
                }
                else if (str[j].Contains("id:"))
                {
                    str[j] = Regex.Replace(str[j], @"[^a-zA-Z:]", "");
                    Itemlist[i].ItemID = str[j].Substring(str[j].IndexOf("minecraft"));
                }
                else if (str[j].Contains("Count:"))
                {
                    Itemlist[i].ItemCount = Regex.Replace(str[j], "[^0-9]", "");
                }
            }
        }
    }

    public Task<ChestItems[]> AsyncExtraction()
    {
        return Task.FromResult(Enumerable.Range(0, Itemlist.Length).Select(index => new ChestItems
        {
            ItemCount = Itemlist[index].ItemCount,
            ItemID = Itemlist[index].ItemID,
            ItemSlot = Itemlist[index].ItemSlot
        }).ToArray());
    }
}

これで不要な文字の除去ができました。あとはこのデータを View (ChestItemsView.razor) で受け取って表示するだけです。

4. 表にして出力する

このまま foreach で回して表示しても見づらいだけなので、表にしてみます。ソースコードは分かれていますが、続けて書いてください。ChestItemsView.razor をこのように書き換えます。

@page "/ChestItems"
@using Data
@inject ChestItemsDataService ItemData

<h1>ChestItems</h1>
<p>チェストの中身を取得します。</p>

<div class="mt-3">
    <button type="button" class="btn btn-outline-primary" @onclick="GetItem">アイテムの取得</button>
</div>

@if (chestFound == false)
{
    <div class="alert alert-danger mt-3" role="alert">
        チェストが見つかりませんでした。<br/>
        チェストの座標が正しいか、または Minecraft サーバの状態を確かめてください。
    </div>
}

@if (items == null)
{
    <div class="mt-3">
        <table class="table">
            <thead>
                <tr>
                    <th>スロット</th>
                    <th>アイテム名</th>
                    <th>数量</th>
                </tr>
            </thead>
        </table>
    </div>
}
else
{
    <div class="mt-3">
        <table class="table">
            <thead>
                <tr>
                    <th>スロット</th>
                    <th>アイテム名</th>
                    <th>数量</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var item in items)
                {
                    <tr>
                        <td>@item.ItemSlot</td>
                        <td>@item.ItemID</td>
                        <td>@item.ItemCount</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
}
@code{
    static IPAddress ipaddress = IPAddress.Parse("127.0.0.1");
    static ushort port = 25575;
    static string password = "minecraft";
    static RCON rcon = new RCON(ipaddress, port, password);

    private string command = $"/data get block <ブロックの座標> Items";

    private ChestItems[] items;
    private bool chestFound = true;

    private async Task GetItem()
    {
        await rcon.ConnectAsync();
        var result = await rcon.SendCommandAsync(command);

        if (result.Contains("not"))
        {
            chestFound = false;
            return;
        }
        else
            chestFound = true;

        ItemData.Extraction(result);
        items = await ItemData.AsyncExtraction();
    }
}

これで View 側の記述もできました。これで完成に見えますが、まだ動きません。@inject で導入したクラスは何もしないと「サービス」として認識しません。なので、次はサービスの登録を行います。

5. サービスの登録

ソリューションエクスプローラ―の中に Startup.cs というファイルがあるので開いてください。中身を見てみると、ConfigureServices というメソッドが記述されています。この中に、先ほど作った ChestItemsDataService を登録します。

//なんかいっぱいかいてある

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    services.AddSingleton<ChestItemsDataService>();
}

//なんかいっぱいかいてある

これで準備は整いました!あとは実行してみましょう。

6. 実行例

まずは最初のページです。アイテムを取得していない状態では items の要素は null になっているので、表示されていません。

f:id:takunology:20201216034303p:plain

正しくチェストの座標を指定し、「アイテムの取得」をクリックしてみると表示されます。ちなみに、チェストが空の場合は何も表示されません。

f:id:takunology:20201216034325p:plain

チェストの座標が正しくない、Minecraft サーバに接続できない等々でアイテムが取得できない場合は chestFound フラグ が false になるので、警告が表示されます。

f:id:takunology:20201216034441p:plain

あとは実際に動かしている様子を見ると分かりやすいかもしれません。


【Minecraft】Blazorアプリからチェストのアイテムを取得する

感想とか

今回は結構苦戦しました。Blazorって極めたらスゴイとは思いますが、こんな変な使い方をしている人はあまりいないと思います(笑)。本当に趣味で書いているので、このコーディングは微妙かもしれません。もっといい書き方があるはずですが、そこは時間を掛けながら模索していきたいですね。

Blazor 使えると楽しいですよ~

宣伝

Minecraft でプログラミング (C#) してみませんか?
Minecraft with Code では、MinecraftC# 言語などを組み合わせた面白いコンテンツを製作中です。整地作業や鉱石探しに苦労している人は、ぜひ自動化してみましょう!

www.mcwithcode.com

ソースコード

今回作ったソースコードです。参考にどうぞ。

github.com

参考サイト

参考にしたサイトです。ありがとうございます。

keiduki.hatenablog.com

docs.microsoft.com

docs.microsoft.com