ScriptAPIでアイテムへのデータの保存を試してみる



 例えば、マイクラでボスを作る時。出した攻撃を数字としてスコアボードに保存して同じ攻撃を繰り返させないことでパターン化を防ぐことができます。でもわざわざ数字にするとか面倒じゃないですか？
 
　ScriptAPIの``World.setDynamicProperty()`` を使えばスコアボードコマンドのように**ワールド自体**に数値や**文字**を**保存**できてしまいます。
　もちろん、ワールド自体だけではなく、エンティティやアイテムにまで色々なところに保存することができるのです。今回はその中の**アイテムへの保存**を試してみたいと思います。ScriptAPI自体の説明は一切しませんのでご了承ください。
 
　前振りで話した「ワールド自体」保存にしたい方は[こちら](https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/world?view=minecraft-bedrock-stable#setdynamicproperty)の公式ドキュメントとにらめっこでもしてきてください(畜生)
## 保存できる型
他の(Worldとかentityだとか)と同じように、``string``,``boolean``,``number``,``Vector3`` のみ保存ができます。

Vector3は座標です。こういう痒い所へ手が届いてくれる感じなのは有り難いですね。


## アイテム内のデータの読み取り
　``ItemStack().getDynamicProperty("保存先のID")``を使用して保存ができます。

``` diff_Javascript


const player = target;
//ここで対象となるプレイヤーを取得。
//(例)chatSend.subscribe((event) => {const player = event.sender}

 const player_content = player.getComponent("inventory").container;
 const getItem =  player_content.getItem(player.selectedSlotIndex);
 //ここで持っているアイテムを取得する。ItemStack クラスを返す。

 const deta = getItem.getDynamicProperty("Item_Storage");
 player.sendMessage(deta)
 //データを取得してプレイヤーへ送信。
```

# 保存のやり方

## 保存した"はず"なのに取得ができない。


データを保存するためには、``setDynamicProperty()``を使えば良い、はずですが...。

``` diff_Javascript
const slot = player.selectedSlotIndex
//プレイヤーが選択しているスロット番号を取得。
const getItem =  player_content.getItem(slot);
//データの読み取りのコードと同じようにしている。
getItem.setDynamicProperty(`Item_Storage`,`保存したい内容`);
//「Item_Storage」という場所に「保存したい内容」という文字列を保存する。


const Haveitem = player_content.getItem(slot);
const deta = Haveitem.getDynamicProperty("Item_Storage");
//保存したものを読み取って確認する。
player.sendMessage(deta)
//プレイヤーに送信。

```
これを実行すると保存した内容を、チャット欄に出してくれるはずなのですが下記のようなエラーが出てしまいます。anonymousだから何だよ(投げやり)


<details><summary>エラー内容</summary>

```
      [Scripting][error]-
      TypeError: Native variant type conversion failed.
      Function argument [0] expected type:
      string | RawMessage | string | RawMessage[]
      at <anonymous> (Item.js:11)
```
>  チャット欄を送信するところでエラーでたよ。
 sendMessage は"文字列"か "RawMessage(tellrawのやつ)"しか受け付けないよ。
 変数"deta"の中身は \<anonymous>みたいだね、これ直してね。

的なことを言ってる。

</details>


## 原因
 ItemStack はアイテムそのものではなくアイテムのデータを返していると思われます(知らんけど)(多分)(恐らく)

つまりは **手に持っているアイテム**と``.getItem(player.selectedSlotIndex);`` 等で**取得したアイテム**は別物と考えればいいわけで。

  そう考えると、``getItem.setDynamicProperty(`Item_Storage`,`保存したい内容`);``だとアイテムからデータを読み取れるはずがありません。保存されていない(データがない)のですから。

  今の状況をファイル管理で例えるとすれば、ファイルをコピーしてきた後に、コピー後のファイルを書き換えてコピー元のファイルが書き換わっていないと唸っているようなものということ...、でしょうか。

## 解決

手に持っているアイテムと取得したアイテムが別物なのなら、それに置き換えてしまえば良いですよね。

``` diff_Javascript
const slot = player.selectedSlotIndex
//プレイヤーが選択しているスロット番号を取得。
const getItem =  player_content.getItem(slot);
//データの読み取りのコードと同じようにしている。
getItem.setDynamicProperty(`Item_Storage`,`保存したい内容`);
//「Item_Storage」という場所に「保存したい内容」という文字列を保存する。

+ player.getComponent("inventory").container.setItem(slot,getItem)
+ //setDynamicProperty された後のアイテムに置き換える




 const Haveitem = player_content.getItem(slot);
 const deta = Haveitem.getDynamicProperty("Item_Storage");
 player.sendMessage(deta)
 //プレイヤーに送信。



```
### 結果:

``` 
 保存したい内容 
```

良かった、ちゃんとチャット欄に保存した内容が出てきました。原因がわかれば簡単な話でしたね。原因分かるまでに三日かかりましたけど。



下にコード全体を置いておきます。コピーすればすぐに使えます。
<details><summary>コード全体</summary>

```Item.js
import { world } from '@minecraft/server';


world.afterEvents.chatSend.subscribe((event) => {

   let player = event.sender;
   const slot = player.selectedSlotIndex
   const player_content = player.getComponent("inventory").container;

   //読み取り
        if(event.message.toLowerCase().startsWith("!its_read")){
      
           // event.cancel = true
           //afterEvents だから使えない。こうしないと setItem() が使えないので直せない。
          
            //ここで対象となるプレイヤーを取得。

            const player_content = player.getComponent("inventory").container;
            const getItem =  player_content.getItem(player.selectedSlotIndex);
            //ここで持っているアイテムを取得する。ItemStack クラスを返す。

            const deta = getItem.getDynamicProperty("Item_Storage");
            player.sendMessage(`読み取り:"${deta}" というデータが保存されています。`)
            //データを取得してプレイヤーへ送信。
            
        }
        
//保存(書き込み)
        if(event.message.toLowerCase().startsWith("!its_write")){
            const getItem =  player_content.getItem(slot);
            //データの読み取りのコードと同じようにしている。
            getItem.setDynamicProperty(`Item_Storage`,`保存したい内容`);
            //「Item_Storage」という場所に「保存したい内容」という文字列を保存する。
            
            player.getComponent("inventory").container.setItem(slot,getItem)
            //setDynamicProperty された後のアイテムに置き換える
            
            
            
            
            
             const deta = getItem.getDynamicProperty("Item_Storage");
             player.sendMessage(`"` +deta +`"というデータが保存されています。` )
             //プレイヤーに送信。
            
        }
        })
       

```

</details>


:::note alert
 警告：`` 1.18.0-beta`` 時点では スタックできるアイテムだと 保存ができないようです。
:::





# まとめ
 - 読み取りの時は ``getDynamicProperty("保存先のID")``
- 保存の時は``setDynamicProperty("保存先のID","保存したい値")``を使う

- アイテムへのデータの保存をするときは、保存の処理を使った後に対象のアイテムを上書きする
- データを保存するアイテムが剣などのスタックできないアイテムである必要がある


余談となりますが、保存するときだけではなく[名前](https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/itemstack?view=minecraft-bedrock-stable#nametag)を変えたり、アイテムに[説明文](https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/itemstack?view=minecraft-bedrock-stable#setlore)をつけたりする時にもちゃんと上書きしないと適応されないようです。Itemstack自体がそのような感じみたいですね。

# 参考


https://minecraft-mcworld.com/72544/

https://minecraft-mcworld.com/77187/