Minecraftでリアルタイムを取得した話

書き始め：2023-11-13 19:11:47
# Minecraftでリアルタイムを取得した話
## 目次
前口上を多少入れております。本編が気になる方は[時間を取得する](#時間を取得する)まで飛ばしてお読みください。
- [Minecraftでリアルタイムを取得した話](#minecraftでリアルタイムを取得した話)
    - [目次](#目次)
    - [事前情報](#事前情報)
    - [本編](#本編)
        - [アドベントカレンダーって何](#アドベントカレンダーって何)
        - [タイマーは作れても出来ないこと](#タイマーは作れても出来ないこと)
        - [とてもヘンな海外勢の動画](#とてもヘンな海外勢の動画)
    - [時間を取得する](#時間を取得する)
        - [コマンドブロックのエラーを逆手に取る](#コマンドブロックのエラーを逆手に取る)
        - [文字列の扱い](#文字列の扱い)
        - [ゴリ押し数値変換](#ゴリ押し数値変換)
        - [コマンドの上限の問題](#コマンドの上限の問題)
        - [LootTableの機能](#loottableの機能)
    - [さいごに](#さいごに)
    - [参考資料](#参考資料)

## 事前情報
先にどんなものを作ったのか見たい人はこちら。
https://github.com/Lit-to/realtime (現実時間を取得するデータパック[^1])
[^1]:メイン言語がPythonになっているが実際はデータパック

この記事は[2023年アドベントカレンダー・MinecraftCommand](https://qiita.com/advent-calendar/2023/mccmd)に向けて寄稿するものです。対戦よろしくお願いします。本記事は
-   データパックの存在を知っている
-   コマンドブロックを触ったことがある
-   データパックを作ったことがある

くらいの前提知識を必要とする可能性があります。わからなくても結構細かめに説明しているのでどうぞ気軽にお読みください。

## 本編
### アドベントカレンダーって何
アドベントカレンダーというのは、12/1からクリスマスの12/25まで毎日一日ずつめくっていってカウントダウンするカレンダーのことです。それになぞらえてQiitaアドベントカレンダーは毎日コミュニティのみんなで1記事ずつ投稿していって、カウントダウンしつつ技術を共有しよう！というイベントです(もう19日目でわざわざ書くものでもありませんが・・)。
→http://ja.wikipedia.org/w/index.php?curid=79238

### タイマーは作れても出来ないこと
アドベントカレンダーは日めくりでカウントダウンしていくもの。ということで今回はマイクラで現在の時間をもとにカウントダウンタイマーを実装することを考えてみます。
マイクラにはスコアボードというシステムがあるので、カウントダウンタイマーみたいなのは結構簡単に作れそう。
雑にgoogleやyoutubeで検索しただけでもカウントダウンタイマーの作り方サイトはいっぱい出て来ますね。
-   https://codewars.jp/howtomake-timer-minecraft/
-   https://qiita.com/KizahashiLuca/items/9694f4f1432fd8849380

ざっくり要約すればMinecraft内の1秒が20tickで進むので、スコアボードのどこかに時間を入力し20tickに一回1減らしていけば、あとはスコアボードを何かしらの手段で表示するだけでタイマーが作れるというもの。この方法なら特定の瞬間に開始して、カウントダウンし続けることは難しくなさそうではあるけれど、一度サーバーを再起動してしまうと今が何時何分かを知る方法がないといちいち入力しなければならない・・**どうにかして現在時刻を取得出来るようにしたい**ですね。ただマイクラ内を見渡してもシステム時間(サーバーの時計)が取得可能な場所はぱっと見思いつかない・・。実際数値を直接取得する方法は筆者が探す限り見つかりませんでした。

### とてもヘンな海外勢の動画
現在時刻を取得する方法がないかと探し回っていたらこちらを作っている人がいました。
-   https://youtu.be/rTCVKCxp84A?si=jUSnPBQIaKlmINYi

とても面白い(奇抜な)方法で実装していたので、今回はこれを参考に同じようなものを作ってみたいと思います。

## 時間を取得する
### コマンドブロックのエラーを逆手に取る
コマンドブロックでスペルミスをやらかしちゃったことはありませんか。例えばaaaaというコマンドをコマンドブロックで実行してみます。当然/aaaaなんてコマンドはありません。

![エラー表示](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884810/a180a050-8f31-193d-eb65-d944b5c5cd80.png)

すると前回の実行結果欄に"[15:43:00] aaaa← [問題箇所]"と書かれていますね。本来コマンド成功時は実行結果が返されるところに失敗時は最終実行時間と失敗理由が書かれるので、**コマンドに失敗すれば実行時間を教えてくれます**。この欄はNBTデータとしてコマンドブロックに保存されるので`/data get block <座標> LastOutput`と実行すれば外から中身を確認することが出来るだけでなく、storageにしまったりアマスタにコピーしたり出来ます。

![/data getの結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884810/b9741851-1db7-d4b5-a1bd-0bd00721f64d.png)

### 文字列の扱い
しかし、取得できたとてこの方法で取得出来るものは文字列です。文字列は数値として扱えないため、足し算引き算のみならず大小比較などの計算することができません。文字列を使って出来ることはせいぜい｢文字列Aと文字列Bが全く同じものかどうかを確かめる｣、｢文字列を分割[^3]｣、｢文字数を数える｣くらい。そのうえ、ここで手に入る文字列はrawJSONと呼ばれる入れ子構造の文字列JSON形式なため少々扱いづらい・・・・[^4]。ただ表示するだけならこれでもいいけど、スコアボードに表示出来ることなどを考えると数値であってほしいですよね。
[^3]:制作時点ではこの操作できることを知らず、執筆時点でのリサーチで気づいた。したがって本記事では文字列の分割をしない縛りがある
[^4]:厳密なJSON形式の書き方:https://ai-akaishi.hatenablog.jp/entry/ar857358

### ゴリ押し数値変換
文字列から計算しやすい数値に変換します。コマンドブロックから文字列を取り出すと"[00:00:00] aaaa← [問題箇所]"となり前述のとおり｢同じ文字列かどうか｣は調べることが可能なので、取り出した文字列が"[00:00:00]..."だったら`0`時`0`分`0`秒という数値を出力する関数を作成します。印刷された紙の書類を渡されてExcelにデータ入力するようなものですね。
同じ変換をする関数を全てのパターンで行う必要があるため、"[00:00:00]..."から"[24:59:59]..."までの86400パターン想定する必要があります。脳筋すぎる。

### コマンドの上限の問題
さて、ここで86400パターンを想定するとある問題にぶつかります。86000もの数のコマンドブロックを並べるわけにはいかないので、データパックの関数機能にコマンドを並べるとしましょう。一番少なくコマンドを書いたとしても86400回のコマンドが実行されることになります[^2]。デフォルトのサーバー設定であれば65565回を超えてコマンドを繋げると途中で止まってしまいます。`/gamerule maxCommandChainLength`を大きくすれば増やせなくもないけれど・・処理の数が多すぎるというのも考えもの。
[^2]:コマンド数の数え方は諸説ありそうなので割愛(https://x.com/Cres333/status/1256008459851403264)

### LootTableの機能
これを回避するためにLootTableのCondition機能を使います。LootTableは簡単に言うと事前に特殊な装飾したアイテムをJSONファイルに記述することでコマンド1つで生み出せるようにするデータパックの機能のこと。データパックでエンチャント付きのピッケルを渡したり、名前を付けた紙を準備したいときに便利に使うことが出来ます。Condition機能とは、そのアイテムをエンティティに渡すときにどんなエンティティに渡すのかに関する条件を決めることが出来る機能です[ルートテーブル](https://minecraft.fandom.com/ja/wiki/ルートテーブル)。この機能を使ってゴリ押し数値変換をしていきます。具体的には次の手順を踏みます。

1.  LootTableに｢アイテムのタグのあるパスの内容が[00:00:00]ならアイテムタグに`hour:0,minute:0,second:0`を追加する｣という内容を記述する(86400パターンぶんを1つのファイルに記述する)。
2.  常に読み込まれているチャンクに透明なアマスタを召喚する。(`/summon armor_stand ~ ~ ~ {Tags:["r_time"]}`)
3.  アマスタにマグマクリーム(アイテムならなんでもいい)を渡す(`/item replace entity @e[type=armor_stand,tag=r_time] weapon.mainhand with magma_cream`)。
4.  アイテムのtag内にコマンドブロックのエラー文をコピーする(`/data modify entity @e[type=armor_stand,tag=r_time,limit=1] HandItems[0].tag.Time set from block 0 -64 0 LastOutput`)。
5.  Loottableを用いてアイテムタグに変更を適用する(`/loot replace entity @e[type=armor_stand,tag=r_time,limit=1] weapon.mainhand loot xx:xx`)。
6.  アマスタからデータを取り出す(`/data get entity @e[type=armor_stand,tag=r_time,limit=1] HandItems[0].tag.Now[0]`)

これでアイテムタグから時間、分、秒の情報をhour、minute、secondから数値として取り出すことが出来るようになりました。86400パターンなんて想定するLootTableの条件定義ファイルは**脳筋すぎてとても手では作れない**。ということで数値以外は同じものを書く単純作業を手では作らずPythonスクリプトに作成させました。単純作業はPythonにやらせると手書きより速い。

## さいごに
がんばれば文字列を数値に変換させることは出来ます！！元ネタの海外コマンド勢よくこれ思いついたな、というのが正直な感想です。ではまた。
執筆終了日：2023-11-30 18:05:13

## 参考資料

-   https://github.com/Lit-to/realtime (現実時間を取得するデータパック[^1])
-   http://ja.wikipedia.org/w/index.php?curid=79238 (アドベントカレンダーwiki)
-   https://codewars.jp/howtomake-timer-minecraft/ (スコアボードからのタイマーの作り方)
-   https://qiita.com/KizahashiLuca/items/9694f4f1432fd8849380 (スコアボードからのタイマーの作り方)
-   https://youtu.be/rTCVKCxp84A?si=jUSnPBQIaKlmINYi (今回のパロディ元)
-   https://ai-akaishi.hatenablog.jp/entry/ar857358 (赤石愛氏のrawJSON解説ページ)
-   https://x.com/Cres333/status/1256008459851403264 (ケイヅキ氏の関数実行数に関するツイート)
-   https://minecraft.fandom.com/ja/wiki/ルートテーブル (LootTableのwiki)