RPGアツマールの共有セーブAPIを使う

# 概要

RPGアツマールには非同期ネットゲームを想定したAPIが存在します。
その中の共有セーブAPIを使ってみた記事です。

RPGツクールMVのアクタを共有するためにやったことをまとめます。

## 作ったもの

* 他のユーザーのアクタを仲間に入れる (←今回はこちらの解説になります)
  * https://game.nicovideo.jp/atsumaru/games/gm9462

* シューティングゲームのリプレイデータの共有
  * https://game.nicovideo.jp/atsumaru/games/gm10128

# 共有セーブデータの保存

まずは保存する側になります。
特定のキーでセーブデータを保存すると、他のユーザーがそのセーブデータを読み取ることができます。これによってキャラクタのステータスを共有したりリプレイデータの共有を行うことができます。

## 共有セーブデータの保存

```{javascript}
// APIの関数が存在するかどうかのチェック
var storageDefined = window.RPGAtsumaru && window.RPGAtsumaru.storage;
var exStorageDefined = window.RPGAtsumaru && window.RPGAtsumaru.experimental.storage;
var setItemsDefined = storageDefined && window.RPGAtsumaru.storage.setItems;

// プラグインコマンド実行時の処理を追加
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command) {
    _Game_Interpreter_pluginCommand.apply(this, arguments);
    // プラグインコマンドでSaveShareActorをしたときに実行
    if (command === "SaveShareActor") {
        if(setItemsDefined) {
            // shareActorに共有するActorの情報を作る(後述)
            var shareActor = internal.encodeSharedActor_($gameParty.leader())
            // APIの実行
            window.RPGAtsumaru.storage.setItems([{ key: "Atsumaru Shared", value: JSON.stringify(shareActor) }])
                .then(function() {
                    // 成功時の処理
                }, function(error){
                    // エラー時の処理
                })
        }
    }
}
```

プラグインコマンドで発動するように実装しました。
セーブ保存のAPIで、Keyに `Atsumaru Shared` を指定すると、保存された文字列を他のユーザーから読み取ることができるようになります。
https://atsumaru.github.io/api-references/storage

<details><summary>取得が終了するまでイベントの進行を待つには？</summary><div>

ネットワークを介した処理なので即座に終了するわけではなく、ネットワークの状況次第で時間がかかります。
その間はイベントの進行も止めておきたい場合、下記を追加します。
(他の公式プラグインと同じ方法です)

```{javascript}
    var _Game_Interpreter_updateWait = Game_Interpreter.prototype.updateWait;
    Game_Interpreter.prototype.updateWait = function() {
        var result = _Game_Interpreter_updateWait.apply(this, arguments);
        return result || Boolean(this._waitForSetItemsPlugin)
    };
```

`window.RPGAtsumaru.storage.setItems` の1行前に `this._waitForSetItemsPlugin=true` を記述して、
成功もしくはエラーのコールバックの中でfalseに設定します。
`this._waitForSetItemsPlugin` がtrueの間はイベントの進行が止まります。

</div></details>


### アクタの情報をjsonで保存する

```{javascript}
    var internal = {}

    internal.encodeSharedActor_ = function(actor) {
        var shareActor = {}
        shareActor.id        = actor._actorId
        shareActor.classId   = actor._classId
        shareActor.exp       = actor._exp
        shareActor.paramPlus = actor._paramPlus
        shareActor.equipWeapon = actor._equips[0]._itemId
        shareActor.equipsSub = []
        for(var idx=1;idx<actor._equips.length;idx++) {
            shareActor.equipsSub.push(actor._equips[idx]._itemId)
        }
        return shareActor
    }
```

保存する文字列は何を記録したいかによって変わりますが、今回は引数に渡されたアクタの情報を記録します。アクタの再現に必要な最低限の情報を連想配列に詰めて返します。外側に `window.RPGAtsumaru.storage.setItems` に渡すときに `JSON.stringify` で文字列にします。


# 共有セーブデータの読み取り

ここまででアクタ1人分の情報を保存できるようになりました。
次は他のユーザーから保存した情報を読み取る方法です。
準備として、保存した側が「ユーザー間通信の許可」を行っている必要があります。これは公式のプラグインがあるのでそれを利用します。これによって他のユーザーから共有セーブを読み取れるようになります。


## ユーザー間通信の許可

自分が保存した共有セーブを他のユーザーから読み取るために行います。

https://atsumaru.github.io/api-references/common/interplayer/
`AtsumaruInterplayerEnableExperimental.js` を導入して、
プラグインコマンドから `EnableInterplayer` を実行しておきます。

## 共有セーブデータの読み取り

```{javascript}
    // APIの関数が存在するかどうかのチェック
    var exStorageDefined = window.RPGAtsumaru && window.RPGAtsumaru.experimental.storage;
    var getSharedItemsDefined = exStorageDefined && window.RPGAtsumaru.experimental.storage.getSharedItems

    var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command) {
        _Game_Interpreter_pluginCommand.apply(this, arguments);
        // (さっきの続き)
        // プラグインコマンドでGetShareActorRecentUserをしたときに実行
        else if( command === "GetShareActorRecentUser" )
        {
            if(getSharedItemsDefined)
            {
                // 取得したセーブデータ(Json)を$gameTempで保持する
                $gameTemp._sharedActors = {}
                // APIの実行
                window.RPGAtsumaru.experimental.storage.getSharedItems(/*取得するUserIdの配列*/)
                    .then(function(result) {
                        // Keyの配列を取得する
                        var keys = Object.keys(result)
                        for(var idx=0; idx < keys.length;idx++)
                        {
                            var key = keys[idx]
                            if( Number(key) != /*自分のUserId*/) // 自身のUserIdは含めない
                            {
                                // JSON.parseで連想配列に直して保持する
                                var parsed = JSON.parse(result[key])
                                // 例) {12353:{id:15,classId:1542,...},52431:{id:12,classId:124,...}}
                                $gameTemp._sharedActors[key] = parsed
                            }
                        }
                    }, function(error){
                        // エラー時の処理
                    })
            }
        }
```

これで引数に渡したUserIdのユーザーの共有セーブデータを `$gameTemp._sharedActors` で保持します。
例えばフレンドのリストを記録しておいてそのUserIdで配列を作れば、フレンドのセーブデータの取得ができます。
後述する「最近プレイしたユーザー」で取得すると、自分のUserIdが含まれるので除外しておく必要があります。

取得が完了するまでイベントを止めたい場合は、
「取得が終了するまでイベントの進行を待つには？」に記載したような待ちの処理を同様に入れてください。


### 最近プレイしたユーザーの取得

https://atsumaru.github.io/api-references/user
`AtsumaruGetUserInformationExperimental.js` を導入して
プラグインコマンドから `GetUserInformation` を実行しておきます。

一部改造してあります。
元の挙動だと変数( `$gameVariables` )に保存するようになっていますが `$gameTemp` に配列で保持します。
配列にしておくと後で `window.RPGAtsumaru.experimental.storage.getSharedItems` に渡しやすくなります。

```{javascript}
window.RPGAtsumaru.experimental.user.getRecentUsers()
   .then(function(recentUsers) {
      for (var i = 0; i < 100; i++) {
         var user = recentUsers[i];
         // 変数でなくgameTempに配列を保持、自身のIDは除く
         if(user && user.id != 0 && Number(user.id) != /*自分のUserId*/)
         {
            $gameTemp._recentUserIds.push(user.id)
            $gameTemp._recentUserNames.push(user.name)
         }
         //     $gameVariables.setValue(offset + i, user ? user.id : 0);
         //     $gameVariables.setValue(offset + i + 100, user ? user.name : 0);
      }
   }, function(error) {
      // エラー時の処理
   }
```

### 自分の情報の取得

https://atsumaru.github.io/api-references/user
`AtsumaruGetSelfInformationExperimental.js` を導入して
プラグインコマンドから `GetSelfInformation` を実行しておきます。

ここまでの記述で `/*自分のUserId*/` と書いてある部分がありますが、
これはこのプラグインで取得して `$gameVariables` の中で保持しています。

### $gameActorsにアクタの情報を構築

```{javascript}
    // $gameTemp._sharedActorsからgameActorへのコピー
    internal.dstSharedActorId = 3
    internal.setupSharedActor = function(userId)
    { 
        if(!$gameTemp._sharedActors) return false

        var sharedActor = $gameTemp._sharedActors[userId]
        if(!sharedActor) return false
        var dstActor = $gameActors.actor(RQ03x.dstSharedActorId)
        var srcActor = $gameActors.actor(sharedActor.id)

        // sharedActorから持ってくるもの
        dstActor._classId   = sharedActor.classId
        dstActor._exp       = Object.assign({},sharedActor.exp)
        dstActor.changeExp(dstActor.currentExp(),false)
        dstActor._paramPlus =  sharedActor.paramPlus.concat()
        // equipsのデコード
        dstActor._equips = [];
        for (var i = 0; i < dstActor.equipSlots().length; i++) 
        {
            dstActor._equips[i] = new Game_Item()
        }
        dstActor._equips[0].setObject($dataWeapons[sharedActor.equipWeapon])
        for(var idx=0; idx<sharedActor.equipsSub.length;idx++)
        {
            if(sharedActor.equipsSub[idx]>0)
             {
                dstActor._equips[idx+1].setObject($dataArmors[sharedActor.equipsSub[idx]])
            }
        }

        // IDをもとにコピーするもの
        dstActor.setName( srcActor.name() + "(助っ人)" )
        dstActor.setNickname( srcActor.nickname() ) 
        dstActor._faceName = srcActor._faceName
        dstActor._faceIndex = srcActor._faceIndex
        dstActor._characterName = srcActor._characterName
        dstActor._characterIndex = srcActor._characterIndex
        dstActor._battlerName = srcActor._battlerName
        dstActor.setProfile(srcActor._profile)

        dstActor.setTp(0)
        dstActor.recoverAll()
        dstActor.refresh()

        return true
    }
```

「アクタの情報をjsonで保存する」で作ったjsonから$gameActorsにコピーする部分です。
仲間に入れるUserIdを引数にこれを実行します。
`internal.setupSharedActor(12345)` を実行すると、UserId=12345のユーザーのアクタが、ID=3のアクタ(アクタのデータベースで3番目)の位置にコピーされます。名前は末尾に「(助っ人)」という文字が付きます。
共有セーブから持ってきたデータを保持しておく用のアクタ(今回はID=3)を決めておいてその場所にコピーしています。




# まとめ

共有セーブデータを使って `$gameActors` の情報を共有する方法をまとめました。

* 共有セーブデータの保存
   * アクタの情報を保存する
* 共有セーブデータを読み取る
   * ユーザー間通信の許可
   * 共有セーブデータの読み出し
      * 最近プレイしたユーザーの取得
      * 自分の情報の取得
   * UserIdを引数に$gameActorsにアクタの情報を構築する