3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Minecraft CommandAdvent Calendar 2024

Day 3

バニラのクールダウンを自分の武器に実装したい!

Last updated at Posted at 2024-12-03
はじめに

1つで1スタックとなるツール系統を右クリックで使用したときに、エンダーパールのようなクールダウンをつける機能についてです。バージョン1.21.3時点の情報でお話しします。

結論

前置きとか要らんからさっさと仕様を見せろ、という方のために結論からです。
解説などは後ほど。

Loottableファイル

{
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "item",
          "name": "アイテム名",
          "functions": [
            {
              "function": "set_custom_data",
              "tag": "{\"カスタムデータ名\":1b}"
            },
            {
              "function": "set_components",
              "components": {
                "use_cooldown": {
                  "seconds": クールダウンに設定したい時間(単位:秒),
                  "cooldown_group": "クールダウングループ名"
                },
                "consumable": {
                  "consume_seconds": 0,
                  "sound": "intentionally_empty",
                  "has_consume_particles": false
                },
                "use_remainder": {
                  "id": "アイテム名",
                  "components": {
                    "custom_data": "{\"Used\":1b,Loottable:\"このファイルのパス\"}"
                  }
                }
              }
            }
          ]
        }
      ]
    }
  ]
}

まずこんな感じのloottableファイルを用意します。日本語の部分はご自身のデータパックに合うように変更してください。

Advancementファイル

{
  "criteria": {
    "右クリック検知進捗": {
      "trigger": "minecraft:consume_item",
      "conditions": {
        "item": {
          "predicates": {
            "minecraft:custom_data": "{\"カスタムデータ名\":1b}"
          }
        }
      }
    }
  },
  "rewards": {
    "function": "右クリック検知関数"
  }
}
{
  "criteria": {
    "アイテム所持検知進捗": {
      "trigger": "minecraft:inventory_changed",
      "conditions": {
        "items": [
          {
            "predicates": {
              "minecraft:custom_data": "{\"Used\":1b}"
            }
          }
        ]
      }
    }
  },
  "rewards": {
    "function": "アイテム所持検知関数"
  }
}

次に2つのadvancementファイルも作りましょう。

Functionファイル

#右クリック検知関数
function 使用したときに実行させたいファイル
advancement revoke @s only 右クリック検知進捗
#アイテム所持検知関数
function アイテムの復帰関数 with entity @s SelectedItem.components.minecraft:custom_data
advancement revoke @s only アイテム所持検知進捗
#アイテムの復帰関数
$loot replace entity @s weapon.mainhand loot $(Loottable)

最後に3つのfunctionファイルを作ります。使用したときに実行させたいファイルには、右クリックしたときに実行させたいコマンドを入れてください。

使い方

1.lootコマンドでアイテムを出します。
2.アイテムを右クリックします。

以上で実装は終了です。お疲れ様でした。

挨拶

どうもおはしあ。けいらぎっていいます。
最近はコマンドが便利になってきて、右クリックの検知方法が豊富になりましたね。
自分はこれまでconsumableの時間を最大にしてusing_itemで検知してました。
しかし最近になって、これにアイテムクールダウンをつけたいなぁとなりました。エンダーパールとかコーラスフルーツ使ったときのやつね。その方が視覚的にクールダウンの終わりまでがわかりやすいですから。
しかし、このクールダウンはアイテムを使い切らないと発生しません。なので「使っている途中」を検知しているこの手法では上手く実装できませんでした。

てことで新しく考えてきました。協力していただいたaatomuさんと友人(匿名希望)に心から感謝します。

解説

まず今回の仕様をざっくり解説すると、
1.アイテムを0秒で消費する
2.変化したアイテムがインベントリにあることを検知
3.手に残ったアイテムの情報からプレイヤーのアイテムを戻す
となっています。順を追って解説します。

1.アイテムを0秒で消費する

最近追加されたconsumableというコンポーネントには、アイテムをどれだけ右クリックしたら消費するかを決められます。そこで0秒にして右クリックを押した瞬間に消費することで"アイテムを消費した"という進捗を達成しましょうというお話です。

しかしこれについて知っている方はお気づきになることでしょう、
「一瞬で消費しても『シャクッ』という音が鳴る」と。
そうなんです。消費する時にどうしても食べる音が鳴ってしまう。
soundというタグで食べるときの音を決定できますが、厄介なことに、リソパで追加した音を指定することができませんでした。ついでに存在しない文字列を入れてもダメ、何も入れなくてもダメ。「なるべく静かな音を設定する」という方法くらいしかありませんでした。

が!友人がネットの海からこんなものを発掘してきてくれました。

"sound": "intentionally_empty"

これはMinecraftに存在する、完全に無音の音声データです。playsoundコマンドには出てこない、ハードコーディング(基礎コードに直接記入)されたものだそうです。今回のことで初めて知りました。これを使うことで、使用時に音が鳴ってしまう問題をクリアできます!
ついでにアイテム使用中はパーティクルが出るので、has_consume_particlesでパーティクルが出ないように設定します。これでアイテムを一瞬で消費する部分は終わりです。
(追記)タラやサケ、フグなど魚系統の環境音も音が出ないファイルだそうです。どっちを使うかはお好みで。

2.変化したアイテムがインベントリにあることを検知

アイテムのタグuse_remainderを用いると、アイテムを消費したとき手元に残るアイテムを指定できます。バニラで言うなら、ポーションを飲んだ後に空のビンが手に入る挙動ですね。今回はここに専用のタグをつけて、inventory_changedの進捗で使用後のアイテムが手に入ったことを検知します。

ここちょっと大事なポイントで、inventory_changedの進捗は、アイテムを使用してから"1tick後"に達成されます。

進捗を2つに分け、一連の処理を2つのファンクションに分けていること。ここがこの仕様の唯一の欠点であり、バグの発生地点となっています。
進捗が達成された瞬間に、手に持っているアイテムを使う前のアイテムに置き換えれば良いじゃないかという話なんですが、ここの挙動が思ったより複雑なんです。

①アイテムを消費したtick

まず、内部の情報的に「アイテムを消費した」という進捗を達成したtickでは、「アイテムはまだインベントリにある」状態になっているようです。
ここでtellrawコマンドの"nbt":"SelectedItem"で見ると、消費前のアイテムの情報が出てきます。

②消費したtickの次のtick

ここでアイテムがインベントリから「消失または変化」します。今回はuse_remainderタグで使った後に残るアイテムを設定するので、設定したアイテムに変化して手元に残ります。ここが今回の厄介なポイントです。

この「アイテムの消失または変化」が思ったより優先度が強く、前のtickで別のアイテムを手元に置き換えていたとしてもここで「消失または変化」が発生します。そのため、1tick遅らせてからアイテムを設定し直さなければ、アイテムの操作が出来ないということでした。

懸念点

進捗を2つに分け、1tick遅らせて実行しているということは、もちろんその間にアイテムを持ち変えればアイテムを増殖させることが可能です。ただtick rateで時間の進みを1/20にしてもかなり難しかったです。これが意図せず起こることはあまりないと思います。

modを入れて遊ぶときに何かの操作(描画)の拍子にクラッシュしたこと、あるんじゃないでしょうか。それが右クリックの関数の中に紛れ込んでしまった場合、マルチで参加者がクラッシュして退出し、進捗が変に達成されないなどの挙動も考えられます。このあたりの仕様は詳しくないですが、こっちは割とありそうですよね。

対策

自分が制作しているマップでは残った時のアイテムは使用出来ないように、attributeなどが0になるように設定しています。そして一定の行動タイミングでアイテムチェックを行い、残った状態のアイテムがあればそれを元に戻すという方法を取っています。残さないことは難しいので、残ったアイテムを後で処理する方針ですね。配布マップ"Saharass" 2024年内配信予定!
あ、一応アイテムチェックについても話しておきます?けどまだ途中なんで後で!

3.手に残ったアイテムの情報からプレイヤーのアイテムを戻す

1tick後にアイテムを戻すコマンドを実行するので、検知するアイテムは使用後のものとなります。
そこで使用後に残るアイテムを設定するuse_remainderで、loottableのパスを指定したcustom_dataが入ったアイテムを残します。このアイテムのデータを見てマクロ関数を実行することで、手にアイテムを戻しています。この部分はaatomuさんから教えていただきました。以上で全体の処理が完了します。

クールダウングループについて

use_cooldownというタグを用いて、アイテムを使用したときのクールダウンを設定しているわけですが、ここのcooldown_groupという項目でクールダウンを共有するアイテムを指定できます。逆に言えば、これをそれぞれ指定すれば別のクールダウンが実装できるわけですね。

思わぬ副産物

アイテムを0秒で消費しているため、使用しても足が遅くなりません!
using_itemを使っていた頃は、どうやってもアイテム使用中に足が遅くなっていましたからね。作っているときは全く意図していませんでしたが、嬉しい誤算でした。

余談:アイテムチェック

「持っているアイテムを1つ1つチェックし、指定したデータがあればそのアイテムをピンポイントで編集する」という機能が、マクロ関数を用いて可能となりました。海外の方を参考にしたものですが、これを見つけて来てくれたのも友人です。すごいですね。

概要

#:item_check
#インベントリデータをStorageに保存
data modify storage stuff: Inventory set from entity @s Inventory
#最後のデータからチェック
execute if data storage stuff: Inventory[-1] run function :each_item
#:each_item
#最後のアイテムに検知したいアイテムがあったら関数で編集する
execute if data storage stuff: Inventory[-1].components.minecraft:custom_data.detect run function :update with storage stuff: Inventory[-1]
#リストの最後のデータを消す
data remove storage stuff: Inventory[-1]
#まだデータが残っていれば最初に戻ってもう一度
execute if data storage stuff: Inventory[-1] run function :each_item with storage stuff: Inventory[-1]
#:update
#検知したアイテムのデータから元のアイテムに戻す
$loot replace entity @s container.$(Slot) loot $(Loottable)

いきなり簡易的になってしまいましたが、こんな感じのファイルを3つ作ります。これも順に解説します。

1.インベントリデータをStorageのリストに保存

data storageでStorageにインベントリの情報を保存します。このときリストにはアイテムの名前の他に、「それがあったインベントリの位置slot」や、個数、カスタム情報componentsなども入ってくれるんですって。便利~

2. 最初 最後のデータからチェック

このデータの中身は一つの長い列のようになっています。データの位置をInventory[-1]と指定し、ifコマンドを実行します。これが後で効いてくるんですよ。
(追記) リストの最後のデータを見る方が軽いそうです。情報ありがとうございます!

3. 1番目 最後のアイテムに検知したいアイテムがあったら関数で編集する

execute ifで、アイテムに検知したいデータがあることを確認したらマクロ関数を組み立て、インベントリの位置のところでloot replaceを実行します。

5.リストの 最初 最後のデータを消す

リストになっているデータの一番最初を消すと、全体が一つ前にズレます。一つ次のデータは先頭のデータになるわけですね。
最後から見るため、ここの操作で全体をズラす必要が無くなりました。

6.まだデータが残っていれば最初に戻ってもう一度

ここでまたInventory[-1]を指定するんです。新しいデータが最後になっているので!
綺麗なプロセスですよね。ここ好きポイント。
これによってリスト内のデータが全部なくなるまで繰り返されます。

以上が概要となります。結構いろいろなものに応用が効くと思います。

おわり

てことで今回はクールダウンをツール系アイテムでも使いたい!というお話でした。
質問や改善点などあればお気軽にどうぞ!できれば優しく、フランクな感じで。
初心者かつ昨日思いついた機構なので荒があるかもしれません。ぜひこれを基に改良され、システムが広まり、コマンドの世界がより良くなることを願っています。(他力本願)
それでは、ご覧いただきありがとうございました。
おつなも!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?