//=============================================================================
// RPG Maker MZ - ADV Extention
//
// Copyright 2024 Panzer-IV. All rights reserved.
// This source code or any portion thereof must not be
// reproduced or used without licensed in any manner whatsoever.
//
// ----------------------------------------------------------------------------
// Version 0.0.7 2024/11/30 追加要望改修
// Version 0.0.6 2024/11/14 不具合を修正
// Version 0.0.5 2024/03/11 ウィンドウ背景を画像指定できるように
// Version 0.0.4 2023/04/22 メッセージ表示で表示中だけ立ち絵プリセットを呼べるように
// Version 0.0.3 2023/04/21 各コマンドでアクターでの指定が出来るように
// Version 0.0.2 2023/04/20 立ち絵とキャラが被った際に立ち絵を半透過するように
// Version 0.0.1 2023/04/03 初版作成
//=============================================================================

/*:
 * @target MZ
 * @plugindesc ADVフォーマットプラグイン
 * @author 四号戦車
 * @base PluginCommonBase
 * @base DebugSwitch
 * @base AsyncLoadImage
 * @base TextResource
 * @base InputExtension
 * @base SmartAnimation
 * @orderAfter PluginCommonBase
 * @orderAfter DebugSwitch
 * @orderAfter AsyncLoadImage
 * @orderAfter TextResource
 * @orderAfter InputExtension
 * @orderAfter SmartAnimation
 *
 * @param messageLength
 * @text バックログに保管するメッセージ長
 * @desc 何行分保持するか（それ以上のメッセージは破棄します）
 * @default 100
 * @type number
 * @min 1
 *
 * @param autoWaitVariableId
 * @text オートウェイトの変数ID
 * @desc オートモードが有効の場合にメッセージを表示しておくフレーム数が指定できます
 *       0－100の範囲で指定可能です（それ以上/以下の範囲は上限/下限になります）
 *       変数が指定されない場合、50フレーム待機します。
 * @type variable
 * @default 0
 *
 * @param blankName
 * @text 名前欄未指定時の表記
 * @desc 名前欄に指定をしない場合のデフォルト
 *       表記を指定できます。
 * @type string
 * @default
 *
 * @param customMessageWindow
 * @text メッセージウィンドウ用カスタムUI指定
 * @desc メッセージウィンドウに専用のUIを指定する場合に使用します。
 *       未指定の場合デフォルトのウィンドウを使用します。
 * @type struct<MessageWindowParams>
 * @default
 *
 * @param characters
 * @text キャラクター立絵
 * @desc キャラクターの画像セット。以下の順番で描画されます。
 *       [後ろ側１]-[後ろ側２]-[身体]-[服装]-[眉]-[目]-[口]-[髪]-[オプション１]-[オプション２]
 * @type struct<CharacterImageSet>[]
 * @default []
 *
 * @param still
 * @text スチル
 * @desc スチルの画像セット。以下の順番で描画されます。
 *       [後ろ側１]-[後ろ側２]-[身体]-[服装]-[眉]-[目]-[口]-[髪]-[オプション１]-[オプション２]
 * @type struct<CharacterImageSet>[]
 * @default []
 *
 * @param effectTables
 * @text 演出イベント指定
 * @desc 演出で呼び出すコモンイベントを指定します。
 * @type struct<EffectEventTable>[]
 * @default []
 *
 * @param effectParam
 * @text 引き渡し変数
 * @desc 演出イベントに引き渡す変数を指定します。
 * @type variable
 * @default 0
 *
 * @param effectPicIdParam
 * @text ピクチャ番号変数
 * @desc 演出イベントに引き渡すピクチャ番号の変数を指定します。
 * @type variable
 * @default 0
 *
 * @param effectStillParam
 * @text スチル変数
 * @desc 演出イベントに引き渡すスチル情報の変数を指定します。
 * @type variable
 * @default 0
 *
 * @param effectPresetParam
 * @text スチルプリセット変数
 * @desc 演出イベントに引き渡すスチルプリセット名情報の変数を指定します。
 * @type variable
 * @default 0
 *
 * @param advMap
 * @text ADVマップ指定
 * @desc ADV画面再生時のマップを指定します
 * @type struct<MapInfo>
 * @default
 *
 * @param transparentWithPlayer
 * @text 重ね合わせ透過設定
 * @desc キャラチップと立ち絵が重なったときの
 *       透過設定のデフォルト値を設定します
 * @type boolean
 * @default false
 * @on 透過する
 * @off 透過しない
 *
 * @param sweepDefault
 * @text イベント終了時のスイープ設定
 * @desc イベント終了時に立ち絵やBGMのスイープ行うかの
 *       デフォルト動作を規定します
 * @type select
 * @default none
 * @option スイープしない
 * @value none
 * @option 画面表示のみスイープ
 * @value sweepVisual
 * @option 音声のみスイープ
 * @value sweepAudio
 * @option 全てスイープ
 * @value sweepAll
 *
 * @command showStill
 * @text スチル表示
 * @desc スチルを表示します
 *
 * @arg name
 * @text 対象スチル
 * @desc 表示対象のスチル名を指定します。プラグイン管理で登録した名前を指定して下さい。
 * @type string
 * @default
 *
 * @command hideStill
 * @text スチル非表示
 * @desc スチルを非表示にします
 *
 * @command setStillParts
 * @text スチル部分表示一括変更
 * @desc スチルの部位ごとの名前を指定して表示を変更します。
 *       不正な指定は無視されます。
 *
 * @arg name
 * @text 対象スチル名
 * @desc 表示対象のスチルを指定します。プラグイン管理で登録した名前を指定して下さい。
 * @type string
 * @default
 *
 * @arg parts
 * @text 部位指定
 * @desc 変更する部位を指定します。
 * @type struct<CharacterPart>[]
 * @default []
 *
 * @command showCharacter
 * @text キャラクター表示
 * @desc キャラクターを表示します
 *
 * @arg actor
 * @text 対象キャラ
 * @desc 表示対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ名
 * @desc 表示対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg align
 * @text 位置
 * @desc キャラクターの配置をどこにするかを指定できます
 * @default right
 * @type select
 * @option 左
 * @value left
 * @option 中央
 * @value center
 * @option 右
 * @value right
 *
 * @arg offsetX
 * @text 横位置オフセット
 * @desc 表示位置のオフセットです。
 *       微調整にご利用下さい。
 * @type number
 * @default 0
 *
 * @arg offsetY
 * @text 縦位置オフセット
 * @desc 表示位置のオフセットです。
 *       微調整にご利用下さい。
 * @type number
 * @default 0
 *
 * @command hideCharacter
 * @text キャラクター非表示
 * @desc キャラクターを非表示にします
 *
 * @arg actor
 * @text 対象キャラ
 * @desc 表示対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ名
 * @desc 表示対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @command hideAllCharacter
 * @text 全キャラクター非表示
 * @desc 全てのキャラクターを非表示にします
 *
 * @command setCharacterPartIndex
 * @text キャラ部分表示変更（後方互換用）
 * @desc キャラクターの部位ごとの番号を指定して表示を変更します。
 *       不正な指定は無視されます。
 *
 * @arg actor
 * @text 対象キャラ
 * @desc 表示対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ名
 * @desc 表示対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg part
 * @text 部位
 * @desc 変更する対象の部位を選択します
 * @default body
 * @type select
 * @option 後ろ側１
 * @value back1
 * @option 後ろ側２
 * @value back2
 * @option 体
 * @value body
 * @option 服装
 * @value cloth
 * @option 眉
 * @value eyeblow
 * @option 目
 * @value eye
 * @option 口
 * @value mouth
 * @option 髪
 * @value hair
 * @option オプション１
 * @value option1
 * @option オプション２
 * @value option2
 * @option オプション３
 * @value option3
 * @option オプション４
 * @value option4
 * @option オプション５
 * @value option5
 * @option オプション６
 * @value option6
 * @option オプション７
 * @value option7
 * @option オプション８
 * @value option8
 *
 * @arg index
 * @text 部位のインデックス
 * @desc 指定する部位のインデックスを指定します。
 * @type number
 * @default 0
 * @min 0
 *
 * @arg visible
 * @text 表示設定
 * @desc 非表示に設定すると指定の部位を非表示に出来ます
 * @type boolean
 * @default true
 * @on 表示
 * @off 非表示
 *
 * @command setCharacterParts
 * @text キャラ部分表示一括変更
 * @desc キャラクターの部位ごとの名前を指定して表示を変更します。
 *       不正な指定は無視されます。
 *
 * @arg actor
 * @text 対象キャラ
 * @desc 表示対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ名
 * @desc 表示対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg parts
 * @text 部位指定
 * @desc 変更する部位を指定します。
 * @type struct<CharacterPart>[]
 * @default []
 *
 * @command resetCharacter
 * @text キャラ表示状態リセット
 * @desc キャラクターの表示状態をリセットします。
 *       口パク等のアニメーションも停止します。
 *
 * @arg actor
 * @text キャラ
 * @desc 表示対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ
 * @desc 表示対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command callPreset
 * @text キャラプリセット呼び出し
 * @desc キャラに登録済みのプリセットを呼び出します。
 *       口パク等のアニメーションは一時停止します。
 *
 * @arg actor
 * @text キャラ
 * @desc 表示対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ
 * @desc 表示対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg presetName
 * @text プリセットの登録名
 * @type string
 * @default
 *
 * @command callStillPreset
 * @text スチルプリセット呼び出し
 * @desc スチルに登録済みのプリセットを呼び出します。
 *
 * @arg name
 * @text 対象スチル
 * @desc 表示対象のスチル名を指定します。プラグイン管理で登録した名前を指定して下さい。
 * @type string
 * @default
 *
 * @arg presetName
 * @text プリセットの登録名
 * @type string
 * @default
 *
 * @command message
 * @text 文章の表示
 * @desc イベントコマンド[文章]の代わりに使用できます。
 *       音声再生等を同時に指定できます。
 *
 * @arg actor
 * @text キャラ
 * @desc プラグイン管理で設定したキャラ立ち絵のアクターを指定してください。
 *       指定するとネームボックスに名前表示して連動動作します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text キャラ名
 * @desc プラグイン管理で設定したキャラ立ち絵の名前を指定してください
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg altName
 * @text 表示名
 * @desc ネームボックスの表示名を別の物に変更する場合こちらを指定して下さい。
 * @type string
 * @default
 *
 * @arg label
 * @text リソースラベル
 * @desc 表示する文章のリソースラベルを指定します。
 *       音声がある場合音声指定を組み合わせます。
 * @type string
 * @default
 *
 * @arg useVoice
 * @text 音声あり
 * @desc ラベルに紐付いた音声を再生します
 *       リソースラベル指定の場合のみ有効
 * @type boolean
 * @default true
 *
 * @arg text
 * @text 文章
 * @desc 表示する文章を指定します。
 *       ラベル指定の場合ラベルが優先されます。
 * @type multiline_string
 *
 * @arg voice
 * @text 音声
 * @desc ボイスなどSEを指定することで同時鳴音します。
 *       バックログにも記録されます。
 * @type file
 * @dir audio/me
 * @default
 *
 * @arg preset
 * @text 立ち絵プリセット
 * @desc 立ち絵のプリセットをメッセージこのメッセージ表示中だけ変更します。
 *       表情の一時変更などでご活用下さい。
 * @type string
 * @default
 *
 * @arg animation
 * @text アニメーション名
 * @desc メッセージ表示中のみ有効なアニメーションを指定します。
 *       口パクなどで使用して下さい。
 * @type string
 * @default
 *
 * @arg autoActivation
 * @text キャラ自動有効化
 * @desc キャラの有効化を自動で行います。
 *       手動制御したい場合のみOFFして下さい
 * @type boolean
 * @default true
 *
 * @arg showName
 * @text 名前欄表示
 * @desc 名前欄を消したい際はOFFにして下さい
 * @type boolean
 * @default true
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command activateCharacter
 * @text キャラの有効/無効化
 * @desc メッセージの内容で自動的に有効化される
 *       キャラを強制的に有効/無効化します
 *
 * @arg actor
 * @text 対象キャラ
 * @desc 有効/無効化対象のキャラを指定します。
 * @type actor
 * @default 0
 *
 * @arg name
 * @text 対象キャラ名
 * @desc 有効/無効化対象のキャラ名を指定します。プラグイン管理で登録した名前を指定して下さい。
 *       アクター以外のキャラが対象です。
 * @type string
 * @default
 *
 * @arg active
 * @text キャラの有効化指定
 * @type boolean
 * @default true
 * @on 有効
 * @off 無効
 *
 * @command runEffect
 * @text ADVエフェクトの実行
 * @desc ADVで使用される画面・音エフェクトを
 *       実行します
 *
 * @arg effect
 * @text エフェクト種別
 * @type select
 * @default none
 * @option 未指定
 * @value none
 * @option 戦闘
 * @value battle
 * @option 過去回想
 * @value flashback
 * @option 絶頂
 * @value acme
 * @option 射精
 * @value ejuclation
 * @option ピストン音
 * @value piston
 * @option 鞭打ち
 * @value fladge
 * @option 時間経過
 * @value time_elapse
 * @option 黒フェードアウト
 * @value blackout
 * @option 黒フェードイン
 * @value blackin
 * @option 白フェードアウト
 * @value whiteout
 * @option 白フェードイン
 * @value whitein
 * @option くちゅくちゅ
 * @value swish
 * @option 潮吹き
 * @value squirt
 * @option 倒れる
 * @value down
 * @option ちょろちょろ
 * @value trickle
 * @option 打撃（弱）
 * @value strike
 * @option 打撃（強）
 * @value power_strike
 * @option あわあわ
 * @value confuse
 * @option フラッシュ
 * @value flash
 * @option 爆発
 * @value explosion
 * @option シェイク
 * @value shake
 * @option 銃撃
 * @value gunshot
 * @option ガラスが割れる音
 * @value glass_break
 * @option 被ダメージ
 * @value damage
 * @option ジャラジャラ
 * @value chain
 * @option サイレン
 * @value siren
 * @option 激突
 * @value crash
 * @option ぼごぉ
 * @value thud
 * @option 車が移動する
 * @value vroom
 * @option ブレーキ
 * @value car_brake
 * @option 衣擦れ
 * @value undressing
 * @option ページめくり
 * @value page_riffle
 * @option ポタポタ（水音）
 * @value waterdrop
 * @option 斬撃
 * @value slash
 * @option メラメラ
 * @value flareup
 * @option キラーン
 * @value shine
 * @option ギチギチ
 * @value creakily
 * @option ズシーン
 * @value plunk
 * @option クエスチョン
 * @value question
 * @option タッタッタ
 * @value jog
 * @option 呆れ
 * @value speechless
 * @option キラキラ
 * @value twinkle
 * @option ・・・
 * @value silence
 * @option クリア
 * @value reset
 *
 * @arg stillName
 * @text 切換スチル名指定
 * @desc 演出中に切り替えるスチル名の指定
 * @type string
 * @default
 *
 * @arg preset
 * @text 切換スチルプリセット指定
 * @desc 演出中に切り替えるスチルプリセットの指定
 * @type string
 * @default
 *
 * @arg picture
 * @text 切換ピクチャ指定
 * @desc 演出中に切り替えるピクチャの指定
 * @type file
 * @dir img/pictures
 * @default
 *
 * @arg pictureId
 * @text ピクチャ番号
 * @desc 演出中に切り替えるピクチャの番号指定
 * @type number
 * @min 1
 * @default 1
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command showEffectPicture
 * @text エフェクト指定画像の表示
 * @desc エフェクト指定で指定した画像を表示します
 *
 * @command fade
 * @text フェード操作
 * @desc フェードイン・フェードアウトを行います
 *
 * @arg type
 * @text フェードタイプ
 * @desc フェードイン・フェードアウトの指定
 * @type select
 * @default out
 * @option フェードアウト
 * @value out
 * @option フェードイン
 * @value in
 *
 * @arg color
 * @text RGB色
 * @desc フェードする色をGGBで指定して下さい
 * @type string
 * @default #000000
 *
 * @arg opacity
 * @text 透明度
 * @desc 最大表示時の透明度を指定して下さい
 * @type number
 * @default 255
 * @min 0
 * @max 255
 *
 * @arg frame
 * @text フェードフレーム
 * @desc フェードするフレーム数
 * @type number
 * @default 0
 * @min 0
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command clearFade
 * @text フェードクリア
 * @desc 画面フェードを強制的にクリアします
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command waitClick
 * @text ADVのクリック待ち
 * @desc メッセージを表示せずにクリック待ちをします
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command advEntry
 * @text ADV開始
 * @desc 専用ADVに移動します
 *
 * @arg map
 * @text 個別指定遷移先
 * @desc 個別に遷移させたい
 *      　マップがある場合指定して下さい
 * @type struct<MapInfo>
 * @default
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command advLeave
 * @text ADV終了
 * @desc ADVを終了して別マップに移動します
 *
 * @arg comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 * @command transparentOnPlayer
 * @text 重ね合わせ透過設定
 * @desc キャラチップと立ち絵が重なったときの
 *       透過設定を設定します
 *
 * @arg transparent
 * @text 透過設定
 * @desc ONにすると透過するようにします
 * @type boolean
 * @default false
 * @on 透過する
 * @off 透過させない
 *
 * @requiredAssets img/system/message_command_active
 * @requiredAssets img/system/message_command_inactive
 *
 * @help
 *
 * ADVフォーマットプラグイン ver 0.0.7
 *
 * ツクールのメッセージウィンドウにADVに必要な機能を提供するプラグイン
 * 以下の機能が統合されています。
 *
 * ・オート・スキップ
 * ・メッセージウィンドウ変更
 * ・バックログ
 * ・立ち絵表示
 * ・音声再生
 * ・画面・音エフェクト
 */

/*~struct~MapInfo:
 *
 * @param mapId
 * @text マップID
 * @desc マップIDを指定して下さい
 * @type number
 * @min 0
 * @default 0
 *
 * @param x
 * @text 座標横位置
 * @desc 遷移先マップのX座標です。
 * @type number
 * @min 1
 * @default 1
 *
 * @param y
 * @text 座標縦位置
 * @desc 遷移先マップのY座標です。
 * @type number
 * @min 1
 * @default 1
 *
 * @param comment
 * @text 備考欄
 * @desc プラグイン側では使用しません。
 *       メモ欄としてお使いください。
 * @type multiline_string
 * @default
 *
 */

/*~struct~MessageWindowParams:
 *
 * @param windowBackground
 * @text ウィンドウ背景画像
 * @desc ウィンドウの背景画像です。
 *       未指定の場合デフォルトの背景を使用します
 * @dir img/system
 * @type file
 * @default
 *
 * @param visibleFace
 * @text 顔表示
 * @desc 顔画像を表示するか否か指定します
 * @type boolean
 * @default true
 *
 * @param textPositionType
 * @text テキスト表示位置指定
 * @desc テキスト表示位置指定のタイプを指定します。
 * @type select
 * @default auto
 * @option 自動調整
 * @value auto
 * @option 絶対指定
 * @value position_absolute
 *
 * @param textX
 * @text テキスト表示位置横
 * @desc テキストの表示開始位置のX軸です。
 *       ポジションタイプがautoの場合無視されます。
 * @type number
 * @default 0
 *
 * @param textY
 * @text テキスト表示位置縦
 * @desc テキストの表示開始位置のY軸です。
 *       ポジションタイプがautoの場合無視されます。
 * @type number
 * @default 0
 *
 * @param textWidth
 * @text テキスト表示幅
 * @desc テキストの表示幅です。
 *       ポジションタイプがautoの場合無視されます。
 * @type number
 * @default 0
 *
 * @param textHeight
 * @text テキスト表示高さ
 * @desc テキストの表示高さです。
 *       ポジションタイプがautoの場合無視されます。
 * @type number
 * @default 0
 *
 * @param fontColor
 * @text メッセージテキストの表示色
 * @desc メッセージテキストの表示色をRGBAで指定します。デフォルトは白です。
 * @type string
 * @default #FFFFFFFF
 *
 * @param fontSize
 * @text メッセージテキストのフォントサイズ
 * @desc メッセージテキスト表示フォントのサイズです。
 *       未指定の場合はデフォルトのサイズが使用されます。
 * @type number
 * @default 0
 *
 * @param nameWindow
 * @text カスタム名前ウィンドウ指定
 * @desc 名前ウィンドウの表示指定をカスタムします
 *       未指定の場合デフォルトの名前ウィンドウを表示します。
 * @type struct<CharacterNameWindowParams>
 * @default
 *
 * @param autoIcon
 * @text オートボタンアイコン
 * @desc オート用のボタンアイコンの指定です。
 *       未指定の場合デフォルトのアイコンを使用します
 * @type struct<CustomIcon>
 * @default
 *
 * @param skipIcon
 * @text スキップボタンアイコン
 * @desc スキップ用のボタンアイコンの指定です。
 *       未指定の場合デフォルトのアイコンを使用します
 * @type struct<CustomIcon>
 * @default
 *
 * @param logIcon
 * @text バックログボタンアイコン
 * @desc バックログ用のボタンアイコンの指定です。
 *       未指定の場合デフォルトのアイコンを使用します
 * @type struct<CustomIcon>
 * @default
 *
 */

/*~struct~CharacterNameWindowParams:
 *
 * @param windowType
 * @text キャラ名ウィンドウのタイプ
 * @desc キャラ名ウィンドウの表示タイプを指定します。
 *       セパレート　　：メッセージウィンドウの外に名前表示用のウィンドウを表示します。
 *       ウィンドウ内部：メッセージウィンドウ内部に名前を表示します。ウィンドウ枠は描画されません。
 * @type select
 * @default window_separate
 * @option セパレート
 * @value window_separate
 * @option ウィンドウ内部
 * @value window_inner
 *
 * @param separater
 * @text 名前表示文字区切り
 * @desc 名前と本文の文字区切りです。
 *       タイプがウィンドウ内部以外の場合は無視されます。
 * @type string
 * @default ：
 *
 * @param innerPosX
 * @text ウィンドウ内部横位置
 * @desc 名前表示の配置横位置です。
 *       座標はウィンドウ内の相対座標です。（ウィンドウの左端が0になります）
 *       タイプがウィンドウ内部以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param innerPosY
 * @text ウィンドウ内部縦位置
 * @desc 名前表示の配置縦位置です。
 *       座標はウィンドウ内の相対座標です。（ウィンドウの上端が0になります）
 *       タイプがウィンドウ内部以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param innerWidth
 * @text ウィンドウ内部表示幅
 * @desc 名前の表示幅です。
 *       タイプがウィンドウ内部以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param innerHeight
 * @text ウィンドウ内部表示高さ
 * @desc 名前の表示高さです。
 *       タイプがウィンドウ内部以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param separateMerginHeight
 * @text ウィンドウ表示間隔縦幅
 * @desc 名前ウィンドウとメッセージウィンドウの表示位置間隔の高さです。
 *       タイプがセパレート以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param color
 * @text 名前の表示色
 * @desc 名前の表示色をRGBAで指定します。デフォルトは白です。
 * @type string
 * @default #FFFFFFFF
 *
 * @param size
 * @text 名前のフォントサイズ
 * @desc 名前表示フォントのサイズです。
 *       未指定の場合はデフォルトのサイズが使用されます。
 * @type number
 * @default 0
 *
 */

/*~struct~CustomIcon:
 *
 * @param icon
 * @text アイコン画像
 * @desc ボタンアイコン用の画像です。
 *       未指定の場合デフォルトのアイコンを使用します
 * @dir img/system
 * @type file
 * @default
 *
 * @param alignType
 * @text 配置パターン
 * @desc 配置のパターンです。絶対指定もしくは四隅の相対指定で指定できます。
 *       自動指定　　　　：完全にプラグインの自動配置に任せます。
 *       絶対指定　　　　：X/Yの開始点を絶対指定します。
 *       アンカー指定　　：画面四隅からの距離で指定します。
 *       水平相対指定(上)：前のインデックスのアイコンからの水平マージンで配置位置を指定します。
 *                    　　アイコンの垂直方向はは上揃えになります。
 *       水平相対指定(下)：前のインデックスのアイコンからの水平マージンで配置位置を指定します。
 *                    　　アイコンの垂直方向はは下揃えになります。
 *       垂直相対指定(左)：前のインデックスのアイコンからの垂直マージンで配置位置を指定します。
 *                    　　アイコンの水平方向はは左揃えになります。
 *       垂直相対指定(右)：前のインデックスのアイコンからの垂直マージンで配置位置を指定します。
 *                    　　アイコンの水平方向はは右揃えになります。
 *
 * @type select
 * @default align_auto
 * @option 自動指定
 * @value align_auto
 * @option 絶対指定
 * @value align_absolute
 * @option アンカー指定
 * @value align_anker
 * @option 水平相対指定（上）
 * @value align_relative_horizontal_top
 * @option 水平相対指定（下）
 * @value align_relative_horizontal_bottom
 * @option 垂直相対指定（左）
 * @value align_relative_vertical_left
 * @option 垂直相対指定（右）
 * @value align_relative_vertical_right
 *
 * @param absoluteX
 * @text 絶対指定X座標
 * @desc アイコンの配置横位置です。
 *       座標はウィンドウ内の相対座標です。（ウィンドウの左端が0になります）
 *       配置パターンが絶対指定以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param absoluteY
 * @text 絶対指定Y座標
 * @desc アイコンの配置縦位置です。
 *       座標はウィンドウ内の相対座標です。（ウィンドウの上端が0になります）
 *       配置パターンが絶対指定以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param ankerPosition
 * @text アンカー位置
 * @desc アンカーの位置です。ウィンドウの四隅のどこかを指定します。
 *       配置パターンがアンカー指定以外の場合は無視されます。
 * @type select
 * @default
 * @option 左上
 * @value left_top
 * @option 左下
 * @value left_bottom
 * @option 右上
 * @value right_top
 * @option 右下
 * @value right_bottom
 *
 * @param ankerX
 * @text アンカー横位置マージン
 * @desc アイコンの配置横位置です。
 *       アンカーの位置からのマージンになります。
 *       配置パターンがアンカー指定以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param ankerY
 * @text アンカー縦位置マージン
 * @desc アイコンの配置横位置です。
 *       アンカーの位置からのマージンになります。
 *       配置パターンがアンカー指定以外の場合は無視されます。
 * @type number
 * @default 0
 *
 * @param mergin
 * @text 相対位置マージン
 * @desc アイコン同士のマージンです。
 *       隣り合うアイコン同士の間隔指定です。
 *       配置パターンが相対指定以外の場合は無視されます。
 * @type number
 * @default 0
 *
 */

/*~struct~CharacterImageSet:
 *
 * @param actor
 * @text キャラ
 * @desc 管理対象のキャラ
 *       スチルの際は無視されます
 * @type actor
 * @default 0
 *
 * @param name
 * @text 名前
 * @desc キャラクター名を指定します。アクター以外を登録する場合はこちらで。
 *       メッセージに表示する名前と一致させてください。
 * @type string
 * @default
 *
 * @param widthOffset
 * @text 横位置オフセット
 * @desc キャラクターの縦位置オフセットです。
 * @type number
 * @default 0
 *
 * @param heightOffset
 * @text 縦位置オフセット
 * @desc キャラクターの縦位置オフセットです。
 * @type number
 * @default 0
 *
 * @param back1
 * @text 後ろ側画像１
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param back2
 * @text 後ろ側画像２
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param body
 * @text 身体画像
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param cloth
 * @text 服画像
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param eyeblow
 * @text 眉画像
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param eye
 * @text 目画像
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param mouth
 * @text 口画像
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param hair
 * @text 髪画像
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option1
 * @text オプション画像１
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option2
 * @text オプション画像２
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option3
 * @text オプション画像３
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option4
 * @text オプション画像４
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option5
 * @text オプション画像５
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option6
 * @text オプション画像６
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option7
 * @text オプション画像７
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param option8
 * @text オプション画像８
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param animations
 * @text アニメーション
 * @desc キャラ画像に上書きするアニメーションのリスト
 *       口パク等
 * @type struct<CharacterAnimation>[]
 * @default []
 *
 * @param presets
 * @text プリセット
 * @desc 部位組み合わせのプリセットです
 *       -1を指定すると非表示します。
 * @type struct<CharacterPreset>[]
 * @default []
 *
 */

/*~struct~CharacterAnimation:
 *
 * @param name
 * @text 名前
 * @desc プラグインコマンドで指定するシンボルです。
 * @type string
 * @default
 *
 * @param interval
 * @text 画像間インターバル
 * @desc 画像切換のインターバルフレーム数
 *       少なくすると早くなります
 * @type number
 * @default 0
 * @min 0
 * @max 1000
 *
 * @param repeat
 * @text 繰り返し
 * @desc 繰り返し指定
 *       OFFにすると１度だけ再生して停止します。
 * @type boolean
 * @default true
 *
 * @param frames
 * @text フレーム
 * @dir img/pictures
 * @type file[]
 * @default []
 *
 * @param hidePart
 * @text 非表示部位
 * @desc アニメーション再生中非表示にする部位
 * @default
 * @type select
 * @option 無し
 * @value
 * @option 後ろ側１
 * @value back1
 * @option 後ろ側２
 * @value back2
 * @option 体
 * @value body
 * @option 服装
 * @value cloth
 * @option 眉
 * @value eyeblow
 * @option 目
 * @value eye
 * @option 口
 * @value mouth
 * @option 髪
 * @value hair
 * @option オプション１
 * @value option1
 * @option オプション２
 * @value option2
 * @option オプション３
 * @value option3
 * @option オプション４
 * @value option4
 * @option オプション５
 * @value option5
 * @option オプション６
 * @value option6
 * @option オプション７
 * @value option7
 * @option オプション８
 * @value option8
 *
 */

/*~struct~CharacterPart:
 *
 * @param part
 * @text 部位
 * @desc 変更する対象の部位を選択します
 * @default body
 * @type select
 * @option 後ろ側１
 * @value back1
 * @option 後ろ側２
 * @value back2
 * @option 体
 * @value body
 * @option 服装
 * @value cloth
 * @option 眉
 * @value eyeblow
 * @option 目
 * @value eye
 * @option 口
 * @value mouth
 * @option 髪
 * @value hair
 * @option オプション１
 * @value option1
 * @option オプション２
 * @value option2
 * @option オプション３
 * @value option3
 * @option オプション４
 * @value option4
 * @option オプション５
 * @value option5
 * @option オプション６
 * @value option6
 * @option オプション７
 * @value option7
 * @option オプション８
 * @value option8
 *
 * @param image
 * @text 部位画像
 * @desc 指定する部位の画像を指定します。
 * @type file
 * @dir image/pictures
 * @default
 *
 * @param visible
 * @text 表示設定
 * @desc 非表示に設定すると指定の部位を非表示に出来ます
 * @type boolean
 * @default true
 * @on 表示
 * @off 非表示
 *
 */
/*~struct~CharacterPreset:
 *
 * @param name
 * @text 名前
 * @desc プラグインコマンドで指定するシンボルです。
 * @type string
 * @default
 *
 * @param parts
 * @text 表示指定
 * @desc 部位ごとに表示指定が出来ます。
 *       指定されない部分は変更されません。
 * @type struct<CharacterPresetData>[]
 * @default []
 */

/*~struct~CharacterPresetData:
 *
 * @param part
 * @text 対象の部位
 * @desc 指定する対象の部位
 * @default
 * @type select
 * @option 無し
 * @value
 * @option 後ろ側１
 * @value back1
 * @option 後ろ側２
 * @value back2
 * @option 体
 * @value body
 * @option 服装
 * @value cloth
 * @option 眉
 * @value eyeblow
 * @option 目
 * @value eye
 * @option 口
 * @value mouth
 * @option 髪
 * @value hair
 * @option オプション１
 * @value option1
 * @option オプション２
 * @value option2
 * @option オプション３
 * @value option3
 * @option オプション４
 * @value option4
 * @option オプション５
 * @value option5
 * @option オプション６
 * @value option6
 * @option オプション７
 * @value option7
 * @option オプション８
 * @value option8
 *
 * @param visible
 * @text 表示設定
 * @desc
 * @type boolean
 * @default true
 * @on 表示
 * @off 非表示
 *
 * @param index
 * @text 指定のインデックス
 * @type number
 * @default 0
 * @min 0
 */

/*~struct~EffectEventTable:
 *
 * @param type
 * @text エフェクト種別
 * @type select
 * @default none
 * @option 未指定
 * @value none
 * @option 戦闘
 * @value battle
 * @option 過去回想
 * @value flashback
 * @option 絶頂
 * @value acme
 * @option 射精
 * @value ejuclation
 * @option ピストン音
 * @value piston
 * @option 鞭打ち
 * @value fladge
 * @option 時間経過
 * @value time_elapse
 * @option 黒フェードアウト
 * @value blackout
 * @option 黒フェードイン
 * @value blackin
 * @option 白フェードアウト
 * @value whiteout
 * @option 白フェードイン
 * @value whitein
 * @option くちゅくちゅ
 * @value swish
 * @option 潮吹き
 * @value squirt
 * @option 倒れる
 * @value down
 * @option ちょろちょろ
 * @value trickle
 * @option 打撃（弱）
 * @value strike
 * @option 打撃（強）
 * @value power_strike
 * @option あわあわ
 * @value confuse
 * @option フラッシュ
 * @value flash
 * @option 爆発
 * @value explosion
 * @option シェイク
 * @value shake
 * @option 銃撃
 * @value gunshot
 * @option ガラスが割れる音
 * @value glass_break
 * @option 被ダメージ
 * @value damage
 * @option ジャラジャラ
 * @value chain
 * @option サイレン
 * @value siren
 * @option 激突
 * @value crash
 * @option ぼごぉ
 * @value thud
 * @option 車が移動する
 * @value vroom
 * @option ブレーキ
 * @value car_brake
 * @option 衣擦れ
 * @value undressing
 * @option ページめくり
 * @value page_riffle
 * @option ポタポタ（水音）
 * @value waterdrop
 * @option 斬撃
 * @value slash
 * @option メラメラ
 * @value flareup
 * @option キラーン
 * @value shine
 * @option ギチギチ
 * @value creakily
 * @option ズシーン
 * @value plunk
 * @option クエスチョン
 * @value question
 * @option タッタッタ
 * @value jog
 * @option 呆れ
 * @value speechless
 * @option キラキラ
 * @value twinkle
 * @option ・・・
 * @value silence
 * @option クリア
 * @value reset
 *
 * @param event
 * @text コモンイベント
 * @desc 対応するコモンイベント番号
 * @type common_event
 * @default 0
 *
 */

(() => {
    'use strict';

    const fs = require('fs');

    const pluginName = "AdvExtention";
    const script = document.currentScript;
    const param  = PluginManagerEx.createParameter(script);

    /********************************************************************************************************
     * 共有基盤
     ********************************************************************************************************/
    // デバッグ判定
    const log = (text) => {
        if (DebugSwitch.isEnable()) {
            console.log(text);
        }
    };

    // キャラ名から検索
    const decideName = (actorId, targetName) => {
        if (actorId > 0) {
            return 'actor:' + actorId;
        } else if (!!targetName && targetName.length > 0) {
            const found = $dataActors.findIndex(actor => !!actor && actor.name === targetName);
            if (!!found && found > 0) {
                return 'actor:' + found;
            } else {
                return targetName;
            }
        } else {
            return '';
        }
    };

    const findCharacter = (actorId, targetName, callback) => {
        if ($gameTemp.adv) {
            const name = decideName(actorId, targetName);

            const sprites = $gameTemp.adv.getCharacterSprites();
            if (sprites.hasOwnProperty(name)) {
                const character = sprites[name];
                if (callback && typeof callback === 'function') {
                    callback(character);
                }
            }
        }
    };

    const findStill = (targetName, callback) => {
        if ($gameTemp.adv) {
            const name = targetName;

            const sprites = $gameTemp.adv.getStillSprites();
            if (sprites.hasOwnProperty(name)) {
                const still = sprites[name];
                if (callback && typeof callback === 'function') {
                    callback(still);
                }
            }
        }
    };

    const parseRgbaHexColorCode = (colorText) => {
        if (colorText.matchAll('^#[0-9A-F]{6}([0-9A-F]{2})?$')) {
            if (colorText.matchAll('^#[0-9A-F]{8}$')) {
                // RGBA構文
                const color = colorText.substring(0, 7);
                const opacity = parseInt(colorText.substring(7), 16)
                return {
                    color: color,
                    opacity: opacity,
                }
            } else {
                // RGB構文
                return {
                    color: colorText,
                    opacity: 255,
                };
            }
        }

    };

    const setupIconButton = (iconParam) => {
        if (!!iconParam) {
            const param = iconParam._parameter;

            let image = null;
            if (!!param.icon && param.icon.length > 0) {
                image = ImageManager.loadSystem(param.icon);
            }

            const typeParams = param.alignType.split('_');
            const alignType = typeParams[1];

            let absolute = null;
            let anker = null;
            let relative = null;

            switch (alignType) {
                case 'auto': {
                    // DO NOTHING
                    break;
                }
                case 'absolute': {
                    absolute = {
                        x: param.absoluteX,
                        y: param.absoluteY,
                    };
                    break;
                }
                case 'anker': {
                    anker = {
                        position: param.ankerPosition,
                        x: param.ankerX,
                        y: param.ankerY,
                    };
                    break;
                }
                case 'relative': {
                    relative = {
                        direction: typeParams[2],
                        position: typeParams[3],
                        mergin: param.mergin,
                    };
                    break;
                }
            } // switch

            return {
                iconImage: image,
                align: {
                    type: alignType,
                    absolute: absolute,
                    anker: anker,
                    relative: relative,
                },
            };
        } else {
            return null;
        }
    };

    const setupNameWindow = (nameWindowParam) => {
        if (!!nameWindowParam) {
            const param = nameWindowParam._parameter;
            const type = param.windowType.split('_')[1];
            const colorInfo = parseRgbaHexColorCode(param.color);
            const value = {
                type: type,
                font: {
                    color: colorInfo.color,
                    opacity: colorInfo.opacity,
                    size: (param.size > 0) ? param.size : $gameSystem.mainFontSize(),
                },
                inner: null,
                separate: null,
            };

            if (type === 'inner') {
                value.inner = {
                    separater: param.separater,
                    x: param.innerPosX,
                    y: param.innerPosY,
                    width: param.innerWidth,
                    height: param.innerHeight,
                };
            } else {
                value.separate = {
                    mergin: param.separateMerginHeight,
                };
            }
            return value;
        } else {
            return null;
        }
    };

    const setupWIndowParameters = (param) => {
        const base = param._parameter;
        const colorInfo = parseRgbaHexColorCode(base.fontColor);
        const parameter = {
            backgroundImage: null,
            visibleFace: base.visibleFace,
            window: {
                type: base.textPositionType,
                x: base.textX,
                y: base.textY,
                width: base.textWidth,
                height: base.textHeight,
                font: {
                    size: base.fontSize,
                    color: colorInfo.color,
                    opacity: colorInfo.opacity,
                },
            },
            nameWindow: setupNameWindow(base.nameWindow),
            autoIcon: setupIconButton(base.autoIcon),
            skipIcon: setupIconButton(base.skipIcon),
            logIcon: setupIconButton(base.logIcon),
        };

        if (!!base.windowBackground && base.windowBackground.length > 0) {
            parameter.backgroundImage = ImageManager.loadSystem(base.windowBackground);
        }
        return parameter;
    };

    const voiceExists = (voice) => {
        const fn = `${voice}${AudioManager.audioFileExt()}`;
        return PluginUtils.fileExists('audio/me', fn);
    };

    const playVoice = (voice) => {
        Logger.d(`play voice: ${voice}`);
        if (voiceExists(voice)) {
            AudioManager.playMe({
                name: voice,
                volume: 90,
                pitch: 100,
                pan: 0,
                pos: 0,
            });
        } else {
            Logger.w(`voice not found: voice = ${voice}`);
        }
    };

    //========================================================================
    // クラス定義（AdvController）
    //========================================================================
    // ADV管理用クラス
    class AdvController {
        constructor() {
            this.logQueue = new MessageQueue(param.messageLength);
            this._skip = false;
            this._auto = false;

            this._parmanentCharacter = '';

            this._skipHandler = null;
            this._autoHandler = null;
            this._skipButtonPressed = false;
            this._backLogEnable = true;
            this.clearSkipInfo();

            this._backLogListeners = [];
            this.windowParams = setupWIndowParameters(param.customMessageWindow);

            this._onWaitClick = false;

            this._characterSprites = {};
            param.characters.map(p => p._parameter).forEach(p => {
                const sprite = new Sprite_Character(p, (s) => {
                    // on bitmap loaded
                    s.forceHide();

                    const name = decideName(p.actor, p.name);
                    this._characterSprites[name] = s;
                });
            });

            const stillSpriteEntries = param.still.map(p => p._parameter).map(p => {
                const sprite = new Sprite_Still(p);
                sprite.hide();
                return [p.name, sprite];
            });
            this._stillSprites = Object.fromEntries(stillSpriteEntries);
        }

        getCharacterSprites() {
            return this._characterSprites;
        }

        getStillSprites() {
            return this._stillSprites;
        }

        setStageParmanentCharacter(name) {
            this._parmanentCharacter = decideName(0, name);
        }

        hideAllCharacters() {
            Logger.d(`hideAllCharacters: exclude = ${this._parmanentCharacter}`);
            Object.keys(this._characterSprites)
                .filter(name => {
                    Logger.d(`hideAllCharacters: name = ${name}`);
                    return name !== this._parmanentCharacter;
                })
                .forEach(name => {
                    this._characterSprites[name]?.hide();
                });

            // hideの結果キャラクターが一人だけになったら念のため有効化する
            if (this._parmanentCharacter !== '') {
                const visibleSprites = Object.values(this._characterSprites).filter(sprite => sprite.isShown());
                if (visibleSprites.length === 1) {
                    visibleSprites[0].activate(true);
                }
            }
        }

        createPage() {
            return new LogPage();
        }

        addLog(page) {
            this.logQueue.enqueue(page);
        }

        getMessageList() {
            const list = this.logQueue.asList();
            return list;
        }

        toggleSkip() {
            this.setSkip(!this._skip);
        }

        toggleAuto() {
            this.setAuto(!this._auto);
        }

        inSkip() {
            return this._skip || this._skipButtonPressed;
        }

        inAuto() {
            return this._auto;
        }

        isCustomWindow() {
            return !!this.windowParams;
        }

        getWindowParameter() {
            return this.windowParams;
        }

        setSkip(value) {
            if (this._skip !== value) {
                if (this._skipHandler) {
                    this._skipHandler(value);
                }
            }
            this._skip = value;
            if (this._skip && this._auto) {
                this.setAutoForce(false);
            }
        }

        setAuto(value) {
            if (!this._skip) {
                this.setAutoForce(value);
            }
        }

        setAutoForce(value) {
            if (this._autoHandler) {
                this._autoHandler(value);
            }
            this._auto = value;
        }

        setWaitClick(wait) {
            this._onWaitClick = wait;
        }

        onWaitClick() {
            return this._onWaitClick;
        }

        clearSkipInfo() {
            this.setSkip(false);
            this.setAuto(false);
        }

        setSkipListener(handler) {
            if (handler && typeof handler === 'function') {
                this._skipHandler = handler;
            }
        }

        setAutoListener(handler) {
            if (handler && typeof handler === 'function') {
                this._autoHandler = handler;
            }
        }

        setOverlapTransparent(value) {
            Object.values(this._characterSprites).forEach(sprite => {
                sprite.setOverlapTransparent(value);
            });
        }

        terminateEvent() {
            this.clearSkipInfo();
        }

        onAfterLoad() {
            this.clearSkipInfo();
        }

        enableBackLog(enable) {
            this._backLogEnable = enable;
        }

        isBackLogEnable() {
            return this._backLogEnable;
        }

        addBackLogListener(listener) {
            if (!!listener && typeof listener === 'function') {
                this._backLogListeners.push(listener);
            }
        }

        removeBackLogListener(listener) {
            this._backLogListeners = this._backLogListeners.filter(l => l !== listener);
        }

        processInput() {
            const skipPressed = Input.isPressed(InputClass.INPUT_CLASS_CTRL);
            if (!this._skip && skipPressed) {
                if (this._auto) {
                    this.setAutoForce(false);
                }
                if (!!this._skipHandler) {
                    this._skipHandler(true);
                }
            }

            if (this._skipButtonPressed && !skipPressed) {
                this.setSkip(false);
                if (this._skipHandler) {
                    this._skipHandler(false);
                }
            }
            this._skipButtonPressed = skipPressed;

            if (Input.isTriggered(InputClass.INPUT_CLASS_AUTO)) {
                this.toggleAuto();
                Input.clear();
                return true;
            }

            const waitingFade = SceneManager.isAdvScene()
                && !!SceneManager._scene.fadeAnimation
                && !SceneManager._scene.fadeAnimation.isComplete()
                && !SceneManager.isSceneChanging();

            const windowShown = SceneManager.isAdvScene() && SceneManager._scene?.isAdvShown();

            if (this._backLogEnable && !waitingFade) {
                // マウスホイール処理（ホイール上操作でバックログへ）
                // パッドLBでもバックログへ
                const threshold = -20;
                if (TouchInput.wheelY <= threshold || Input.isTriggered(InputClass.INPUT_CLASS_PAGE_UP)) {
                    this.backLog();
                }
            }
            return false;
        }

        backLog() {
            this._backLogListeners.forEach(listener => {
                listener();
            });
            SceneManager.push(Scene_Backlog);
        }

        showStill(name) {
            const scene = SceneManager.getCurrentScene();
            if (scene instanceof Scene_Message) {
                scene.showStill(name);
            }
        }

        hideStill() {
            const scene = SceneManager.getCurrentScene();
            if (scene instanceof Scene_Message) {
                scene.hideStill(name);
            }
        }

        showCharacter(actor, name, align, offsetX, offsetY) {
            const scene = SceneManager.getCurrentScene();
            if (scene instanceof Scene_Message) {
                const chName = decideName(actor, name);
                scene.showCharacter(chName, align, offsetX, offsetY);

                // キャラクターが一人しかいない場合念のため有効化する
                const visibleSprites = Object.values(this._characterSprites).filter(sprite => sprite.isShown());
                if (visibleSprites.length === 1) {
                    visibleSprites[0].activate(true);
                }
            } else if (DebugSwitch.isEnable()) {
                log(pluginName + '.showCharacter: Scene_Message以外からの呼び出し');
            }
        }

        hideCharacter(actor, name) {
            const scene = SceneManager.getCurrentScene();
            if (scene instanceof Scene_Message) {
                const chName = decideName(actor, name);
                scene.hideCharacter(chName);
            } else if (DebugSwitch.isEnable()) {
                log(pluginName + '.hideCharacter: Scene_Message以外からの呼び出し');
            }
        }

        callPreset(actor, name, presetName) {
            findCharacter(actor, name, character => {
                character.callPreset(presetName);
            });
        }

        callStillPreset(name, presetName) {
            findStill(name, still => {
                still.callPreset(presetName);
            });
        }

        sweep() {
            switch (param.sweepDefault) {
                case 'sweepVisual':
                    this._sweepVisual();
                    break;
                case 'sweepAudio':
                    this._sweepAudio();
                    break;
                case 'sweepAll':
                    this._sweepAudio();
                    this._sweepVisual();
                    break;
                default:
                    /* do nothing */
                    break;
            } // switch
        }

        _sweepAudio() {
            const savedBgm = $gameSystem._savedBgm;
            const currentBgm = AudioManager.saveBgm();
            AudioManager.stopMe();
            AudioManager.stopBgs();
            AudioManager.stopSe();
            if (savedBgm?.name !== currentBgm?.name) {
                AudioManager.stopBgm();
                $gameSystem.replayBgm();
            }
        }

        _sweepVisual() {
            if (SceneManager.isAdvScene()) {
                SceneManager._scene.clearFade();
            }
            $gameScreen.clear();
            $gameTemp.adv.hideAllCharacters();
            const picture = PluginUtils.getVariables(param.effectParam);
            if (picture > 0) {
                $gameVariables.setValue(param.effectParam, 0);
            }

            const pictureId = PluginUtils.getVariables(param.effectPicIdParam);
            if (pictureId > 0) {
                $gameVariables.setValue(param.effectPicIdParam, 0);
            }

            $gameVariables.setValue(param.effectStillParam, '');
            $gameVariables.setValue(param.effectPresetParam, '');
        }

        runEffect(effect, picture, pictureId, stillName, preset, interpreter) {
            Logger.d(`runEffect: ${effect}, picture = ${picture}, picture ID = ${pictureId}, still = ${stillName}`);
            if (param.effectParam > 0 && !!picture && picture !== '') {
                $gameVariables.setValue(param.effectParam, picture);
                if (param.effectPicIdParam > 0) {
                    $gameVariables.setValue(param.effectPicIdParam, pictureId);
                }
            } else {
                $gameVariables.setValue(param.effectPicIdParam, 0);
            }

            if (param.effectStillParam > 0 && !!stillName) {
                $gameVariables.setValue(param.effectStillParam, stillName);
                if (param.effectPresetParam > 0) {
                    $gameVariables.setValue(param.effectPresetParam, preset);
                }
            } else {
                $gameVariables.setValue(param.effectStillParam, '');
            }

            const effectParam = param.effectTables.find(p => {
                const pair = p._parameter;
                return pair.type === effect;
            });

            if (!!effectParam) {
                const childInterpreter = PluginUtils.waitCommonEvent(effectParam.event, interpreter);
                childInterpreter.setSweep(false);
            } else {
                Logger.d(`runEffect: effect not registered, effect = ${effect}`);
            }
        }
    }

    //========================================================================
    // コアスクリプト変更部 (DataManager)
    //========================================================================
    DataManager.reserveAdvController = null;

    //========================================================================
    // コアスクリプト変更部 (Game_Temp)
    //========================================================================
    const _Game_Temp_initialize = Game_Temp.prototype.initialize;
    Game_Temp.prototype.initialize = function() {
        _Game_Temp_initialize.apply(this, arguments);
        if (!DataManager.reserveAdvController) {
            this.adv = new AdvController();
            DataManager.reserveAdvController = this.adv;
        } else {
            this.adv = DataManager.reserveAdvController;
        }
        this.findCharacter = findCharacter;
        this.fading = null;
    };

    //========================================================================
    // コアスクリプト変更部 (Game_System)
    //========================================================================
    // プレイ中保持するデータを初期化
    const _Game_System_onAfterLoad = Game_System.prototype.onAfterLoad;
    Game_System.prototype.onAfterLoad = function() {
        _Game_System_onAfterLoad.apply(this, arguments);
        $gameTemp.adv.onAfterLoad();
    };

    //========================================================================
    // コアスクリプト変更部 (Scene_Message)
    //========================================================================
    Scene_Message.prototype.createAdvObjects = function() {
        this.fadeScreen = new Sprite(new Bitmap(Graphics.width, Graphics.height));
        this.fadeScreen.zIndex = 1000;
        this.fadeAnimation = null;

        if (!!$gameTemp.fading) {
            this.fadeScreen.bitmap.fillAll($gameTemp.fading.color);
        } else {
            this.fadeScreen.hide();
        }
        this.fadeType = 'none';
        this.addChild(this.fadeScreen);
        this._messageWindow.zIndex = 1100;
        this.sortChildren();

        const windowLayerIndex = this._windowLayer ? this.getChildIndex(this._windowLayer) : 0;
        Logger.d(`[ADV] scene setup sprites`);
        this._characterSprites = $gameTemp.adv.getCharacterSprites();
        Object.values(this._characterSprites).forEach(sprite => {
            this.addChildAt(sprite, windowLayerIndex);
        });

        this._stillSprites = $gameTemp.adv.getStillSprites();
        Object.values(this._stillSprites).forEach(sprite => {
            this.addChildAt(sprite, windowLayerIndex);
        });
    };

    Scene_Message.prototype.isAdvShown = function() {
        if (!this._messageWindow) {
            return false;
        }
        return this._messageWindow.isOpen() || this._messageWindow.isOpening()|| this._messageWindow.isClosing();
    }

    const _Scene_Message_destroy = Scene_Message.prototype.destroy;
    Scene_Message.prototype.destroy = function() {
        Object.values($gameTemp.adv.getCharacterSprites()).forEach(sprite => {
            this.removeChild(sprite);
        });
        this._characterSprites = null;

        Object.values($gameTemp.adv.getStillSprites()).forEach(sprite => {
            this.removeChild(sprite);
        });
        this._stillSprites = null;

        _Scene_Message_destroy.apply(this, arguments);
    };

    Scene_Message.prototype.showStill = function(name) {
        if (this._stillSprites.hasOwnProperty(name)) {
            const still = this._stillSprites[name];
            still.show();
        }
    }

    Scene_Message.prototype.hideStill = function() {
        Object.values(this._stillSprites).forEach(sprite => {
            sprite.hide();
        });
    }

    Scene_Message.prototype.showCharacter = function(name, align, offsetX, offsetY) {
        if (this._characterSprites.hasOwnProperty(name)) {
            const character = this._characterSprites[name];

            const width = character.width();
            const height = character.height();
            const y = (Graphics.height - height) + offsetY + character.offsetHeight();

            let x = 0;  // place on left side
            if (align == 'center') {
                // ignore offset
                x = (Graphics.width - width) / 2;
            } else if (align === 'right') {
                x = Graphics.width - width;
            }
            x += (offsetX + character.offsetWidth());

            character.refresh();
            character.move(x, y);
            character.show();
            Logger.d(`showCharacter: name = ${name}, x = ${x}, y = ${y}, width = ${Graphics.width}, height = ${Graphics.height}`);
        } else if (DebugSwitch.isEnable()) {
            log('[showCharacter] Sprite[' + name + '] is not registered');
        }
    };

    Scene_Message.prototype.hideCharacter = function(name) {
        if (this._characterSprites.hasOwnProperty(name)) {
            const character = this._characterSprites[name];
            character.hide();

            // hideの結果キャラクターが一人だけになったら念のため有効化する
            const visibleSprites = Object.values(this._characterSprites).filter(sprite => sprite.isShown());
            if (visibleSprites.length === 1) {
                visibleSprites[0].activate(true);
            }
        } else if (DebugSwitch.isEnable()) {
            log('[hideCharacter] Sprite[' + name + '] is not registered');
        }
    };

    // Scene_Messageのメッセージウィンドウを仕込んだものに差し換え
    const _Scene_Message_messageWindowRect = Scene_Message.prototype.messageWindowRect;
    Scene_Message.prototype.messageWindowRect = function() {
            const padding = (Graphics.boxWidth / 10);
            const ensureWindowHeight = 28;

            const ww = Graphics.boxWidth - padding;
            const wh = this.calcWindowHeight(4, false) + ensureWindowHeight;
            const wx = (Graphics.boxWidth - ww) / 2;
            const wy = 0;

            return new Rectangle(wx, wy, ww, wh);
    };

    const _Scene_Message_createMessageWindow = Scene_Message.prototype.createMessageWindow;
    Scene_Message.prototype.createMessageWindow = function() {
        const windowParam = $gameTemp.adv.getWindowParameter();
        const windowImage = !!windowParam ? windowParam.backgroundImage : null;

        if (!!windowImage) {
            const ww = windowImage.width;
            const wh = windowImage.height;
            const wx = (Graphics.width - ww) / 2;
            const wy = Graphics.height - wh;
            const rect = new Rectangle(wx, wy, ww, wh);

            log(`createMessageWindow: screen = ${Graphics.width} x ${Graphics.height}`);
            log(`createMessageWindow: window x = ${rect.x}, y = ${rect.y}, width = ${rect.width}, height = ${rect.height}`);

            this._messageWindow = new Window_AdvMessage(rect);
            this._messageWindow.zIndex = 100;
            this.addChild(this._messageWindow);
        } else {
            this._messageWindow = new Window_AdvMessage(this.messageWindowRect());
            this.addWindow(this._messageWindow);
        }
    };

    const _Scene_Message_prototype_createChoiceListWindow = Scene_Message.prototype.createChoiceListWindow;
    Scene_Message.prototype.createChoiceListWindow = function() {
        if (!!window.Window_CustomChoiceList) {
            this._choiceListWindow = new Window_CustomChoiceList();
            this._choiceListWindow.zIndex = 90;
            this.addChild(this._choiceListWindow);
        } else {
            _Scene_Message_prototype_createChoiceListWindow.apply(this, arguments);
        }
    };

    const _Scene_Message_prototype_update = Scene_Message.prototype.update;
    Scene_Message.prototype.update = function() {
        _Scene_Message_prototype_update.apply(this, arguments);

        if (this.fadeType !== 'none' && !!this.fadeAnimation) {
            this.fadeAnimation.setSkip($gameTemp.adv.inSkip())
            this.fadeAnimation.update();
        }
    };

    Scene_Message.prototype.clearFade = function() {
        this.fadeAnimation = null;
        this.fadeType = 'none';
        $gameTemp.fading = null;
        this.fadeScreen.hide();
    };

    Scene_Message.prototype.completeFade = function() {
        if (!!this.fadeAnimation) {
            this.fadeAnimation.completeForce();
        }
    };

    Scene_Message.prototype.fade = function(type, color, opacity, frame) {
        if (!this.fadeScreen) {
            Logger.w(`fade screen is not ready`);
            return;
        }
        this.fadeScreen.bitmap.fillAll(color);
        this.fadeType = type;
        this.fadeWaitCount = frame;

        if (!!this.fadeAnimation) {
            this.fadeAnimation.completeForce();
        }

        if (type === 'in') {
            this.fadeAnimation = new SmartAnimation('fade', this.fadeScreen, 'opacity', false, opacity, 0, () => {
                this.clearFade();
            }, EasingType.LINEAR);
        } else if (type === 'out') {
            this.fadeAnimation = new SmartAnimation('fade', this.fadeScreen, 'opacity', false, 0, opacity, () => {
                this.fadeAnimation = null;
                this.fadeType = 'none';
                $gameTemp.fading = {
                    color: color,
                    type: type,
                };
            }, EasingType.LINEAR);
        }
        this.fadeScreen.opacity = opacity;
        this.fadeScreen.show();
        this.fadeAnimation.setDuration(frame);
        this.fadeAnimation.start();
    };

    //========================================================================
    // コアスクリプト変更部 (Scene_Map)
    //========================================================================
    const _Scene_Map_createDisplayObjects = Scene_Map.prototype.createDisplayObjects;
    Scene_Map.prototype.createDisplayObjects = function() {
        _Scene_Map_createDisplayObjects.apply(this, arguments);
        this.createAdvObjects();
    };

    //========================================================================
    // コアスクリプト変更部 (Scene_Battle)
    //========================================================================
    const _Scene_Battle_createDisplayObjects = Scene_Battle.prototype.createDisplayObjects;
    Scene_Battle.prototype.createDisplayObjects = function() {
        _Scene_Battle_createDisplayObjects.apply(this, arguments);
        this.createAdvObjects();
    };

    //========================================================================
    // クラス定義 (Window_AdvMessage)
    //========================================================================
    // ADVフォーマット用ウィンドウクラス（Scene_MessageのWindow_Messageをこれに差し換える）
    class Window_AdvMessage extends Window_Message {
        static TEXT_MARGIN = 18;

        constructor(rect) {
            super(rect);

            this._page = null;
            this._voice = null;
            this._inAnimation = null;
            this._illustSnap = null;
            this._windowParam = null;
            this._isBackLogEnabledInPrevFrame = true;
        }

        static coodinateWindowSize(rect) {
            return new Rectangle(x, y, width, height);
        }

        isCustomWindow() {
            const param = $gameTemp.adv.getWindowParameter();
            const image = !!param ? param.backgroundImage : null;
            return !!image;
        }

        _createAllParts() {
            this._windowParam = $gameTemp.adv.getWindowParameter();
            super._createAllParts();
        }

        _createFrameSprite() {
            const param = this._windowParam;
            const image = !!param ? param.backgroundImage : null;
            if (!!image) {
                this._frameSprite = new Sprite(image);
                this._container.addChild(this._frameSprite);
            } else {
                super._createFrameSprite();
            }
        }

        _refreshBack() {
            if (!this.isCustomWindow()) {
                super._refreshBack();
            }
        }

        _updateArrows() {
            this._downArrowSprite.visible = false;
            this._upArrowSprite.visible = false;
        }

        _refreshFrame() {
            if (this.isCustomWindow()) {
                const wx = 0;
                const wy = 0;
                const ww = this._frameSprite.width;
                const wh = this._frameSprite.height;
                this._frameSprite.move(wx, wy, ww, wh);
            } else {
                super._refreshFrame();
            }
        }

        updatePlacement() {
            log(`updatePlacement: isCustomWindow = ${this.isCustomWindow()}`);
            if (this.isCustomWindow()) {
                const goldWindow = this._goldWindow;
                if (goldWindow) {
                    goldWindow.y = this.y > 0 ? 0 : Graphics.boxHeight - goldWindow.height;
                }
            } else {
                super.updatePlacement();
            }
        }

        drawMessageFace() {
            const param = this._windowParam;
            if (!!param && param.visibleFace === true) {
                super.drawMessageFace();
            }
        }

        flushTextState(textState) {
            if (this.isCustomWindow()) {
                const windowParam = $gameTemp.adv.getWindowParameter();
                const font = windowParam.window.font;
                this.contents.fontSize = font.size;
                this.contents.textColor = font.color;
                this.contents.paintOpacity = font.opacity;
                this.contents.outlineWidth = 2;
            }
            super.flushTextState(textState);
        }

        createContents() {
            this.buttons = {
                auto: new CommandButton('auto', "AUTO", this._windowParam.autoIcon, null),
                skip: new CommandButton('skip', "SKIP", this._windowParam.skipIcon, null),
                backlog: new CommandButton('backlog', "LOG", this._windowParam.logIcon, null),
            };
            this.buttons.backlog.addLoadListener(() => {
                Object.values(this.buttons).forEach((button, index, array) => {
                    const align = button.getParameter().align;
                    log(`createContents: align = ${JSON.stringify(align, null, 2)}`);
                    let x = 0;
                    let y = 0;
                    switch (align.type) {
                        case 'auto': {
                            const margin = 20;
                            const width = button.width;
                            const height = button.height;

                            x = this.width - margin - ((margin + width) * (index + 1));
                            y = this.height - (margin * 2) - height;
                            break;
                        }
                        case 'absolute': {
                            const param = align.absolute;
                            x = param.x;
                            y = param.y;
                            break;
                        }
                        case 'anker': {
                            const param = align.anker;
                            const pos = param.position.split('_');
                            const lr = pos[0];  // left or right
                            const tb = pos[1];  // top or bottom

                            x = (lr === 'left') ? param.x : (this.width - button.width - param.x);
                            y = (tb === 'top') ? param.y : (this.height - button.height - param.y);
                            break;
                        }
                        case 'relative': {
                            const param = align.relative;
                            const direction = param.direction;  // horizontal or vertical
                            const pos = param.position;         // top / buttom / left / right
                            const mergin = param.mergin;
                            const prev = (index > 0) ? array[index - 1] : null;
                            let prevRect = null;
                            if (!!prev) {
                                prevRect = new Rectangle(prev.x, prev.y, prev.width, prev.height);
                            } else {
                                prevRect = new Rectangle(0, 0, 0, 0);
                            }

                            x = prevRect.x;
                            y = prevRect.y;
                            if (direction === 'horizontal') {
                                x = x + prevRect.width + mergin;
                                if (pos === 'bottom') {
                                    y = prevRect.y + prevRect.height - button.height;
                                }
                            } else {
                                y = y + prevRect.height + mergin;
                                if (pos === 'right') {
                                    x = prevRect.x + prevRect.width - button.width;
                                }
                            }
                            break;
                        }
                    } // switch

                    button.hide();
                    this.addChild(button);
                    log(`coordinate buttons: x = ${x}, y = ${y}, [width = ${this.width} x height = ${this.height}]`);
                    button.move(x, y);
                });
            });

            this.buttons.auto.activate($gameTemp.adv.inAuto());

            $gameTemp.adv.setSkipListener((value) => this.buttons.skip.activate(value));
            $gameTemp.adv.setAutoListener((value) => this.buttons.auto.activate(value));
            super.createContents();
        }

        destroyContents() {
            super.destroyContents();
            if (!!this.buttons) {
                Object.values(this.buttons).forEach(button => {
                    button.activate(false);
                });
            }
        }

        fittingHeight(numLines) {
            if (this.isCustomWindow()) {
                return this.height;
            } else {
                return super.fittingHeight(numLines);
            }
        }

        setNameBoxWindow(nameBoxWindow) {
            const windowParam = $gameTemp.adv.getWindowParameter();
            const nameWindowParam = windowParam.nameWindow;
            log(`setNameBoxWindow: param = ${windowParam}, window = ${JSON.stringify(nameWindowParam, null, 2)}`);
            if (!!nameWindowParam && nameWindowParam.type === 'inner') {
                nameBoxWindow.hide();

                const innerPos = nameWindowParam.inner;
                const font = nameWindowParam.font;
                const bitmap = new Bitmap(innerPos.width, innerPos.height);
                bitmap.fontSize = font.size;
                bitmap.outlineWidth = 2;
                bitmap.drawText(innerPos.separater, innerPos.width - 18, 0, 18, innerPos.height, 'left');

                this._nameBoxSprite = new Sprite(bitmap);
                this._nameBoxSprite.move(innerPos.x, innerPos.y);
                this._nameBoxSprite.hide();
                this._nameBoxSprite._showName = true;
                this.addChild(this._nameBoxSprite);
            } else {
                nameBoxWindow.y -= 5;
                nameBoxWindow.windowWidth = function() {
                    if (this._name) {
                        const textWidth = this.textSizeEx(this._name).width;
                        const padding = this.padding + this.itemPadding() + Window_AdvMessage.TEXT_MARGIN;
                        const width = Math.ceil(textWidth) + padding * 2;
                        return Math.min(width, Graphics.boxWidth);
                    } else {
                        return 0;
                    }
                };

                const _Window_NameBox_baseTextRect = Window_NameBox.prototype.baseTextRect;
                nameBoxWindow.baseTextRect = function() {
                    const rect = _Window_NameBox_baseTextRect.apply(this, arguments);
                    rect.x = Window_AdvMessage.TEXT_MARGIN;
                    return rect;
                };
            }
            super.setNameBoxWindow(nameBoxWindow);
        }

        setChoiceListWindow(choiceListWindow) {
            if (choiceListWindow instanceof Window_CustomChoiceList) {
                // DO NOTHING
            } else {
                const _Window_ChoiceList_updatePlacement = Window_ChoiceList.prototype.updatePlacement;
                choiceListWindow.updatePlacement = function() {
                    const messageWindow = this._messageWindow;
                    if (messageWindow) {
                        const x = this.windowX();
                        this.y = this.windowY();
                        this.width = this.windowWidth();
                        this.height = this.windowHeight();

                        const rightDiff = (messageWindow.x + messageWindow.width) - (x + this.width);
                        if (rightDiff < 0) {
                            // 右にはみ出している
                            this.x = messageWindow.x + messageWindow.width - this.width;
                        } else if (this.x < messageWindow.x) {
                            // 左にはみ出している
                            this.x = messageWindow.x;
                        }
                    } else {
                        _Window_ChoiceList_updatePlacement.apply(this, arguments);
                    }
                };
            }
            super.setChoiceListWindow(choiceListWindow);
        }

        updateOpen() {
            if (this._opening) {
                this.openness += 32;
                if (this.isOpen()) {
                    this._opening = false;

                    if (!!this._nameBoxSprite && this._nameBoxSprite?._showName === true) {
                        this._nameBoxSprite.show();
                    }

                    Object.values(this.buttons).forEach(button => {
                        if (button === this.buttons.backlog) {
                            if ($gameTemp.adv.isBackLogEnable()) {
                                button.show();
                            } else {
                                // button.hide();
                            }
                        } else {
                            button.show();
                        }
                    })
                }
            }
        }

        updateClose() {
            if (this._closing) {
                if (!!this._nameBoxSprite) {
                    this._nameBoxSprite.hide();
                }

                Object.values(this.buttons).forEach(button => {
                    button.hide();
                })
            }
            super.updateClose();
        }

        cancelChoice(force) {
            if (!!this._choiceListWindow) {
                if (this._choiceListWindow instanceof Window_CustomChoiceList && this._choiceListWindow.isCancelEnabled()) {
                    if (force === false) {
                        // キャンセル可能ではない かつ 強制キャンセルでもない ので無視
                        return false;
                    }
                }
                this._choiceListWindow.callCancelHandler();
            }
            return true;
        }

        updateSpeakerName() {
            const windowParam = $gameTemp.adv.getWindowParameter();
            const nameWindowParam = windowParam.nameWindow;
            const name = $gameMessage.speakerName();
            if (!!nameWindowParam && nameWindowParam.type === 'inner') {
                const font = nameWindowParam.font;
                const bitmap = this._nameBoxSprite.bitmap;
                log(`updateSpeakerName: font = ${JSON.stringify(font, null, 2)}`);
                bitmap.clearRect(0, 0, bitmap.width - 24, bitmap.height);
                if (!!name && name.length > 0) {
                    bitmap.textColor = font.color;
                    bitmap.paintOpacity = font.opacity;
                    bitmap.fontSize = font.size;
                    bitmap.outlineWidth = 2;
                    bitmap.drawText(name, 0, 0, bitmap.width - 24, bitmap.height, 'right');
                    this._nameBoxSprite._showName = true;
                    this._nameBoxSprite.show();
                } else {
                    this._nameBoxSprite._showName = false;
                    this._nameBoxSprite.hide();
                }
            } else if (!!this._nameBoxWindow) {
                if (name && name.length > 0) {
                    this._nameBoxWindow.setName('【 ' + name + ' 】');
                } else {
                    this._nameBoxWindow.setName(name);
                }
            }
        }

        startMessage() {
            // 立ち絵プリセット指定がある場合は変える
            const illustPreset = $gameMessage.illustPreset();
            if (illustPreset) {
                findCharacter(illustPreset.actorId, illustPreset.name, (character) => {
                    this._illustSnap = {
                        snap: character.getCurrentPictureSet(),
                        character: character,
                    };
                    character.callPreset(illustPreset.presetName);
                });
            }

            const animation = $gameMessage.animation();
            if (animation) {
                findCharacter(animation.actor, animation.target, character => {
                    character.startAnimation(animation.animationName);
                })
                this._inAnimation = animation;
            }

            if (!!this._nameBoxWindow) {
                this._nameBoxWindow.y -= 5;
            }
            super.startMessage();

            this.initializeMessageAutoCount();
        }

        terminateMessage() {
            if (this._page) {
                $gameTemp.adv.addLog(this._page);
            }
            super.terminateMessage();
            if (!!this._illustSnap) {
                this._illustSnap.character.applyPictureSet(this._illustSnap.snap);
                this._illustSnap = null;
            }

            if (!!this._inAnimation) {
                findCharacter(this._inAnimation.actor, this._inAnimation.target, character => {
                    character.stopAnimation(this._inAnimation.animationName);
                    this._inAnimation = null;
                });
            }
        }

        newPage(textState) {
            // flushしたので新しいページを用意
            const newPage = $gameTemp.adv.createPage();
            newPage.setName($gameMessage.speakerName());

            const voice = $gameMessage.voice();
            if (voice && voice.length > 0) {
                // 次に再生する音声の有無にかかわらずウィンドウ切替では音をいったん止める
                AudioManager.stopMe();

                newPage.setVoice(voice);
                playVoice(voice);
            }

            textState.text.split('\n').forEach(line => {
                newPage.addLine(line);
            });
            this._page = newPage;
            super.newPage(textState);

            const windowParam = $gameTemp.adv.getWindowParameter();
            const windowAlign = windowParam.window;
            if (this.isCustomWindow() && windowAlign.type === 'position_absolute') {
                // 毎回縦位置をWindow_Messageに0にされるのを再修正
                textState.y = windowAlign.y;
            }
        }

        messageAuto() {
            return $gameTemp.adv.inAuto();
        }

        messageSkip() {
            return $gameTemp.adv.inSkip();
        }

        createTextState(text, x, y, width) {
            const textState = super.createTextState(text, x, y, width);
            const windowParam = $gameTemp.adv.getWindowParameter();
            const windowAlign = windowParam.window;
            log(`createTextState: align = ${JSON.stringify(windowAlign, null, 2)}`);
            if (this.isCustomWindow() && windowAlign.type === 'position_absolute') {
                textState.x = windowAlign.x;
                textState.y = windowAlign.y;
                textState.width = windowAlign.width;
                // TODO: フォントサイズ変更時はheightの対応が必要
            }
            return textState;
        }

        newLineX(textState) {
            // TODO: RTL判定
            const windowParam = $gameTemp.adv.getWindowParameter();
            const windowAlign = windowParam.window;
            if (this.isCustomWindow() && windowAlign.type === 'position_absolute') {
                // const faceExists = $gameMessage.faceName() !== "" && windowParam.visibleFace;
                // const faceWidth = ImageManager.faceWidth;
                // const spacing = 20;
                return windowAlign.x;
            } else {
                const x = super.newLineX(textState);
                return x + Window_AdvMessage.TEXT_MARGIN;
            }
        }

        update() {
            if (this._isBackLogOpenning) {
                return;
            }

            super.update();
            if (!this.isOpening() && !this.isClosing()) {
                const backLogEnabled = $gameTemp.adv.isBackLogEnable();
                if (this._isBackLogEnabledInPrevFrame !== backLogEnabled) {
                    this._isBackLogEnabledInPrevFrame = backLogEnabled;
                }

                if (!this.isClosed() && this.isTriggeredHidden() && this.isEnableInChoice()) {
                    if (!this.isHidden()) {
                        Logger.d(`adv: hide all window`);
                        this.hideAllWindow();
                    } else {
                        Logger.d(`adv: show all window`);
                        this.showAllWindow();
                    }
                } else if (this.isHidden() && this.isTriggered()) {
                    Logger.d(`adv: show all window`);
                    this.showAllWindow();
                    Input.update();
                }

                if (this.isHidden() && this.visible) {
                    Logger.d(`adv: hide all window hidden`);
                    this.hideAllWindow();
                }
            }
        }

        updateInput() {
            if (this.messageSkip()) {
                if (Input.isPressed(InputClass.INPUT_CLASS_OK) || TouchInput.isClicked()) {
                    $gameTemp.adv.setSkip(false);
                    return true;
                }
            }

            if (this.isHidden()) {
                return true;
            }
            if (this.messageAuto() && this._messageAutoCount > 0 && this.visible) {
                this._messageAutoCount--;
            }
            // this.buttons.backlog.processTouch();
            if (this.buttons.backlog.isPressed()) {
                $gameTemp.adv.backLog();
                this._isBackLogOpenning = true;
                return true;
            }

            if (this.buttons.auto.isClicked()) {
                $gameTemp.adv.toggleAuto();
                return false;
            }

            if (this.buttons.skip.isClicked()) {
                $gameTemp.adv.toggleSkip();
                return false;
            }

            if ($gameTemp.adv.inAuto() || $gameTemp.adv.inSkip() || this.isTriggered()) {
                $gameTemp.adv.setWaitClick(false);
            }

            return super.updateInput();
        }

        updateWait() {
            this.updateSkipAuto();
            if (this.messageSkip()) {
                this._waitCount = 0;
            }
            return super.updateWait();
        }

        isTriggered() {
            if ($gameTemp.adv.processInput()) {
                return false;
            }

            if (this.messageAuto() && this._messageAutoCount <= 0) {
                // SE再生中はオートトリガを待機
                if (!this._page || !this._page.getVoice() || !AudioManager.isVoicePlaying(this._page.getVoice())) {
                    this.initializeMessageAutoCount();
                    return true;
                }
            }
            return Input.isRepeated("ok") || TouchInput.isRepeated() || this.messageSkip();
        }

        isEnableInChoice() {
            return !$gameMessage.isChoice();
        }

        hideAllWindow() {
            this.hide();
            this.subWindows().forEach(function(subWindow) {
                this.hideSubWindow(subWindow);
            }.bind(this));

            this._originalPictureOpacities = {};
            this.linkPictures(0, param.linkPictureNumbers);
            this.linkPictures(255, param.linkShowPictureNumbers);
            this._hideByMessageWindowHidden = true;
        }

        subWindows() {
            const subWindows = [this._goldWindow,
                this._choiceListWindow, this._numberInputWindow,
                this._eventItemWindow
            ];

            if (this._nameBoxWindow instanceof Window_NameBox) {
                subWindows.push(this._nameBoxWindow);
            }
            return subWindows;
        }

        showAllWindow() {
            this.show();
            this.subWindows().forEach(function(subWindow) {
                this.showSubWindow(subWindow);
            }.bind(this));

            this.linkPictures(null, param.linkShowPictureNumbers);
            this.linkPictures(null, param.linkPictureNumbers);
            this._hideByMessageWindowHidden = false;
        }

        isHidden() {
            return this._hideByMessageWindowHidden;
        }

        linkPictures(opacity, pictureNumbers) {
            if (!pictureNumbers || $gameSwitches.value(param.disableLinkSwitchId)) {
                return;
            }
            pictureNumbers.forEach(function(pictureId) {
                this.linkPicture(opacity, pictureId);
            }, this);
        }

        linkPicture(opacity, pictureId) {
            var picture = $gameScreen.picture(pictureId);
            if (!picture) {
                return;
            }
            if (opacity === null) {
                if (!this._originalPictureOpacities.hasOwnProperty(pictureId)) {
                    return;
                }
                opacity = this._originalPictureOpacities[pictureId];
            } else {
                this._originalPictureOpacities[pictureId] = picture.opacity();
            }
            picture.linkWithMessageWindow(opacity);
        }

        hideSubWindow(subWindow) {
            subWindow.prevVisible = subWindow.visible;
            subWindow.hide();
        }

        showSubWindow(subWindow) {
            if (subWindow.prevVisible) subWindow.show();
            subWindow.prevVisible = undefined;
        }

        // 古いYEP_MessageCore.jsでは、ネーム表示ウィンドウはsubWindowsに含まれる
        nameWindowIsSubWindow() {
            return this.subWindows().filter(function(subWindow) {
                return subWindow === this._nameWindow;
            }, this).length > 0;
        }

        disableWindowHidden() {
            return $gameParty.inBattle();
        }

        isTriggeredHidden() {
            if (this.disableWindowHidden()) {
                return false;
            }
            return TouchInput.isCancelled() || Input.isTriggered(InputClass.INPUT_CLASS_CANCEL);
        }

        updateSkipAuto() {
            if (this.isClosed()) return;
            if (this.isAnySubWindowActive()) {
                $gameTemp.adv.clearSkipInfo();
            }
        }

        initializeMessageAutoCount() {
            var textSize = 0;
            if (this._textState) {
                var index = this._textState.index;
                var text  = this._textState.text;
                while (text[index] && !(text[index] === '\x1b' && text[index + 1] === '!')) {
                    index++;
                }
                // use in param
                textSize = index - this._textState.index;
            }

            const autoSpeed = ConfigManager.autoSpeed ?? 2;
            const baseWait = Math.max((textSize * 5) - (autoSpeed * 20), 25);
            if (param.autoWaitVariableId > 0) {
                const variableValue = $gameVariables.value(param.autoWaitVariableId);
                this._messageAutoCount = variableValue;
            } else {
                const wait = 20 + (200 - (autoSpeed * 50));
                this._messageAutoCount = wait;
                // this._messageAutoCount = 50;
            }
            Logger.d(`text speed = ${this._messageAutoCount}`);
        }

        startPause() {
            super.startPause();
            if (this.messageSkip()) {
                this.startWait(2);
            }
        }
    }

    /********************************************************************************************************
     * バックログ
     ********************************************************************************************************/

    //=============================================================================
    // Game_Message
    //  音声(SE)とアニメーション情報をバックログに記録できるようにする
    //=============================================================================
    const _Game_Message_clear = Game_Message.prototype.clear;
    Game_Message.prototype.clear = function() {
        _Game_Message_clear.apply(this, arguments);
        this._voice = null;
        this._resevedIllustPreset = null;
        this._animation = null;
    };

    Game_Message.prototype.setVoice = function(value) {
        Logger.d(`set voice: ${value}`);
        this._voice = value;
    };

    Game_Message.prototype.voice = function() {
        return this._voice;
    };

    Game_Message.prototype.setPreset = function(actorId, name, preset) {
        this._resevedIllustPreset = {
            actorId: actorId,
            name: name,
            presetName: preset,
        };
    };

    Game_Message.prototype.illustPreset = function() {
        return this._resevedIllustPreset;
    };

    Game_Message.prototype.setAnimation = function(value) {
        this._animation = value;
    };

    Game_Message.prototype.animation = function() {
        return this._animation;
    };

    // メッセージキュー
    class MessageQueue {
        constructor(size) {
            this._list = [];
            this._maxSize = 0;
            if (Number.isFinite(size)) {
                this._maxSize = size;
            }
        }

        enqueue(message) {
            let dropped = "";
            if (this._list.length === this._maxSize) {
                dropped = this._list.shift();
            }
            this._list.push(message);
            return dropped;
        }

        size() {
            return this._list.length;
        }

        dequeue() {
            if (this._list.length > 0) {
                return this._list.shift();
            } else {
                return null;
            }
        }

        asList() {
            return this._list;
        }
    }

    class LogPage {
        constructor() {
            this.lines = [];
            this._name = null;
            this._voice = null;
        }

        addLine(text) {
            if (text.indexOf('\n') >= 0) {
                const texts = text.split('\n');
                texts.forEach(t => this.lines.push(t));
            } else {
                this.lines.push(text)
            }
        }

        setName(value) {
            if (value && value.length > 0) {
                this._name = value;
            } else {
                this._name = null;
            }
        }

        setVoice(value) {
            if (value && value.length > 0) {
                this._voice = value;
            } else {
                this._voice = null;
            }
        }

        getVoice() {
            return this._voice;
        }

        buildLines() {
            if (this.lines.length > 0) {
                return this.lines.map((line, index) => {
                    const name = (index === 0) ? this._name : null;
                    const voice = (index === 0) ? this._voice : null;
                    return {
                        name: name,
                        text: line,
                        voice: voice,
                    };
                });
            } else if (this._name) {
                // 名前のみ
                return [ {
                    name: this._name,
                    text: null,
                    voice: null,
                }, ];
            }
        }

        getLineLength() {
            return this.lines.length;
        }
    }

    // バックログ用シーンクラス
    class Scene_Backlog extends Scene_MenuBase {
        constructor() {
            super();
        }

        create() {
            super.create();

            const WINDOW_TOP = 50;

            const rect = new Rectangle(0, WINDOW_TOP, Graphics.boxWidth, Graphics.boxHeight - WINDOW_TOP);
            rect.pad(-10);
            const window = new Window_BackLog(rect);
            this.addWindow(window);
        }

        needsCancelButton() {
            return false;
        }
    }

    const BACKLOG_NAME_WIDTH = 250;
    const BACKLOG_VOICE_ICON_WIDTH = 48;
    const BACKLOG_LINE_MARGIN = 5;

    // バックログ用ウィンドウ
    class Window_BackLog extends Window_Selectable {
        constructor(rect) {
            super(rect);
            this._items = $gameTemp.adv.getMessageList().flatMap(page => {
                const lines = page.buildLines();
                return lines;
            });
            this.cursorVisible = false;
            this.replyIcons = [];

            const top = this.maxItems() - this.maxVisibleItems();
            const topRow =  top < 0 ? 0 : top + 2;
            this.forceSelect(topRow);
            this.setTopRow(topRow);
            this.activate();
            this.refresh();
        }

        maxItems() {
            return this._items.length;
        }

        setCursorRect(x, y, width, height) {
            // nothing to do
        }

        drawItemBackground(index) {
            // nothing to do
        }

        itemRectWithPadding(index) {
            const rect = this.itemRect(index);
            const padding = 2;
            rect.x += padding;
            rect.width -= padding * 2;
            return rect;
        }

        paint() {
            this.replyIcons = [];
            super.paint();
        }

        drawItem(index) {
            const item = this._items[index];
            const rect = this.itemLineRect(index);

            if (!!item.name && item.name !== '') {
                this.drawText(item.name + '：', rect.x, rect.y, BACKLOG_NAME_WIDTH, 'right');
            }

            const textX = rect.x + BACKLOG_NAME_WIDTH + BACKLOG_LINE_MARGIN;
            const textWidth = rect.width - textX - BACKLOG_VOICE_ICON_WIDTH - (BACKLOG_LINE_MARGIN * 2);
            this.drawText(item.text, textX, rect.y, textWidth, 'left');

            if (item.voice && voiceExists(item.voice)) {
                const voiceX = textX + textWidth + BACKLOG_LINE_MARGIN;
                const itemRect = this.itemRect(index);
                const rect = new Rectangle(voiceX, itemRect.y, BACKLOG_VOICE_ICON_WIDTH, itemRect.height);
                this.paintReplayIcon(rect, false);

                this.replyIcons.push({
                    voice: item.voice,
                    index: index,
                    rect: rect,
                    pressed: false,
                });
            }
        }

        paintReplayIcon(rect, hovered) {
            this.contents.clearRect(rect.x, rect.y, rect.width, rect.height);
            const iconBitmap = new Bitmap(rect.width, rect.height);

            const padding = 2;
            const innerWidth = iconBitmap.width - (padding * 2);
            const innerHeight = iconBitmap.height - (padding * 2);

            if (hovered) {
                const color = '#D07A3D';
                iconBitmap.paintOpacity = PluginUtils.percent(255, 80);
                iconBitmap.fillRect(padding, padding, innerWidth, innerHeight, color);
                iconBitmap.paintOpacity = 255;
            }
            iconBitmap.strokeRect(padding, padding, innerWidth, innerHeight, iconBitmap.textColor);
            iconBitmap.fontSize = 26;
            iconBitmap.fontBold = true;
            iconBitmap.drawText('♪', 0, 0, iconBitmap.width, iconBitmap.height, 'center');

            this.contents.blt(iconBitmap, 0, 0, iconBitmap.width, iconBitmap.height, rect.x, rect.y);
        }

        smoothScrollDown(n) {
            // 下端で更に下げ操作した場合は画面抜ける
            if (this._scrollY === this.maxScrollY()) {
                SoundManager.playCancel();
                SceneManager.pop();
            } else {
                super.smoothScrollDown(n);
            }
        }

        processCursorMove() {
            if (this.isCursorMovable()) {
                const lastIndex = this.index();
                if (Input.isRepeated("down")) {
                    this.smoothScrollDown(2);
                }

                if (Input.isRepeated("up")) {
                    this.smoothScrollUp(2);
                }

                if (!this.isHandled("pagedown") && Input.isTriggered("pagedown")) {
                    this.cursorPagedown();
                }
                if (!this.isHandled("pageup") && Input.isTriggered("pageup")) {
                    this.cursorPageup();
                }
            }
        }

        update() {
            super.update();

            const touchPos = new Point(TouchInput.x, TouchInput.y);
            this.replyIcons.forEach(iconData => {
                const localPos = this._contentsSprite.worldTransform.applyInverse(touchPos);
                const rect = iconData.rect;
                if (rect.contains(localPos.x, localPos.y)) {
                    this.paintReplayIcon(rect, true);

                    if (TouchInput.isClicked()) {
                        AudioManager.stopMe();
                        playVoice(iconData.voice);
                    }
                } else {
                    this.paintReplayIcon(rect, false);
                }
            });


            if (TouchInput.isCancelled() || Input.isTriggered('cancel')) {
                SoundManager.playCancel();
                SceneManager.pop();
            }
        }
    }

    /********************************************************************************************************
     * ADVフォーマット（イベントクリア）
     ********************************************************************************************************/
    const APPENDIX_WAIT_MODE_FADE = 'adv_fade';
    const APPENDIX_WAIT_MODE_CLICK = 'adv_click';

    const _Game_Interpreter_prototype_setup = Game_Interpreter.prototype.setup;
    Game_Interpreter.prototype.setup = function(list, eventId) {
        if (this._depth === 0) {
            this._startBgm = AudioManager.saveBgm();
        }
        this._needSweep = true;
        _Game_Interpreter_prototype_setup.apply(this, arguments);
    };

    const _Game_Interpreter_prototype_updateWaitMode = Game_Interpreter.prototype.updateWaitMode;
    Game_Interpreter.prototype.updateWaitMode = function() {
        let waiting = false;
        switch (this._waitMode) {
            case APPENDIX_WAIT_MODE_FADE:
                if (SceneManager._scene.prototype === Game_Map) {
                    waiting = !!SceneManager._scene.fadeAnimation && SceneManager._scene.fadeAnimation.inRunning();
                }
                break;
            case APPENDIX_WAIT_MODE_CLICK:
                waiting = $gameTemp.adv.onWaitClick();
                break;
        } // switch

        if (waiting === false) {
            return _Game_Interpreter_prototype_updateWaitMode.apply(this, arguments);
        } else {
            return true;
        }
    };

    const _Game_Interpreter_prototype_setWaitMode = Game_Interpreter.prototype.setWaitMode;
    Game_Interpreter.prototype.setWaitMode = function(waitMode) {
        if (waitMode !== '' && this._waitMode !== waitMode && this._waitMode === APPENDIX_WAIT_MODE_FADE) {
            if (SceneManager._scene.constructor === Scene_Message) {
                SceneManager._scene.completeFade();
            }
        }
        _Game_Interpreter_prototype_setWaitMode.apply(this, arguments);
    };

    //=============================================================================
    // Game_Interpreter
    //  マップイベント終了時のスイープ抑止設定
    //  マップイベント終了時にメッセージスキップフラグを初期化します。
    //  マップイベント終了時に音声とピクチャ表示を終了します
    //=============================================================================
    const _Game_Interpreter_terminate      = Game_Interpreter.prototype.terminate;
    Game_Interpreter.prototype.terminate = function() {
        _Game_Interpreter_terminate.apply(this, arguments);
        if (this.isNeedClearSkip()) {
            $gameTemp.adv.terminateEvent();
        }

        if (this._needSweep === true) {
            $gameTemp.adv.sweep();
        }
    };

    Game_Interpreter.prototype.setSweep = function(sweep) {
        this._needSweep = sweep;
    };

    Game_Interpreter.prototype.isNeedClearSkip = function() {
        return ($gameMap.isMapInterpreterOf(this) || !$gameMap.isEventRunning()) && this._depth === 0;
    };

    /********************************************************************************************************
     * ADVフォーマット（オート・スキップ・ウィンドウ非表示）
     ********************************************************************************************************/

    //=============================================================================
    // Game_Picture
    //  メッセージウィンドウの表示可否と連動します。
    //=============================================================================
    Game_Picture.prototype.linkWithMessageWindow = function(opacity) {
        this._opacity       = opacity;
        this._targetOpacity = opacity;
    };

    //=============================================================================
    // Game_Message
    //  メッセージスキップ情報を保持します。
    //=============================================================================
    const _Game_Message_initialize      = Game_Message.prototype.initialize;
    Game_Message.prototype.initialize = function() {
        _Game_Message_initialize.apply(this, arguments);
        $gameTemp.adv.clearSkipInfo();
    };

    //=============================================================================
    // Game_Map
    //  指定されたインタプリタがマップイベントかどうかを返します。
    //=============================================================================
    Game_Map.prototype.isMapInterpreterOf = function(interpreter) {
        return this._interpreter === interpreter;
    };

    Game_Map.prototype.isMapInterpreterOf = function(interpreter) {
        return this._interpreter === interpreter;
    };

    //=============================================================================
    // AudioManager
    //  ボイスSE再生中にオートモードでの更新を待つために再生中判定を追加
    //=============================================================================
    AudioManager.isVoicePlaying = function(name) {
        if (name && name.length > 0) {
            return this._meBuffer?.name === name && this._meBuffer?.isPlaying();
        }
        return false;
    }

    //=============================================================================
    // Window_ChoiceList、Window_NumberInput、Window_EventItem
    //  非表示の間は更新を停止します。
    //=============================================================================
    var _Window_ChoiceList_update      = Window_ChoiceList.prototype.update;
    Window_ChoiceList.prototype.update = function() {
        if (!this.visible) return;
        _Window_ChoiceList_update.apply(this, arguments);
    };

    var _Window_NumberInput_update      = Window_NumberInput.prototype.update;
    Window_NumberInput.prototype.update = function() {
        if (!this.visible) return;
        _Window_NumberInput_update.apply(this, arguments);
    };

    var _Window_EventItem_update      = Window_EventItem.prototype.update;
    Window_EventItem.prototype.update = function() {
        if (!this.visible) return;
        _Window_EventItem_update.apply(this, arguments);
    };

    const setupDefaultIcon = (name, callback) => {
        const promises = [
            ImageManager.loadSystemAsync('message_command_active'),
            ImageManager.loadSystemAsync('message_command_inactive'),
        ];

        Promise.all(promises).then(icons => {
            const active = icons[0];
            const width = active.width;
            const height = active.height;

            const drews = icons.map(icon => {
                const bitmap = new Bitmap(width, height);
                bitmap.blt(icon, 0, 0, width, height, 0, 0);

                bitmap.fontBold = true;
                bitmap.fontSize = 16;
                bitmap.drawText(name, 0, 0, width, height, 'center');

                return new Sprite(bitmap);
            });

            if (!!callback) {
                callback(drews, width, height);
            }
        });
    };

    const setCustomIcon = (parameter, callback) => {
        const baseImage = parameter.iconImage;

        const active = new Sprite(baseImage);
        active.tint = 0xFFFFFF;
        active.opacity = 255;

        const inactive = new Sprite(baseImage);
        inactive.tint = 0xFFFFFF;
        inactive.opacity = 153;   // 60%

        const width = baseImage.width;
        const height = baseImage.height;

        if (!!callback) {
            callback([
                active,
                inactive,
            ], width, height);
        }
    };

    class CommandButton extends Sprite_Clickable {
        constructor(symbol, name, parameter, handler) {
            super();

            this.name = name;
            this.symbol = symbol;
            this.parameter = parameter;
            this.handler = handler;
            this._state = null;
            this._loadListeners = [];
            this._isReady = false;

            this._isClicked = false;

            const loaded = (icons, width, height) => {
                this.activeIcon = icons[0];
                this.inactiveIcon = icons[1];

                this.width = width;
                this.height = height;

                this.reflashState(false);

                this._loadListeners.forEach(listener => listener());
                this._isReady = true;
            };
            loaded.bind(this);

            if (!!parameter.iconImage) {
                // アイコン指定あり
                setCustomIcon(parameter, loaded);
            } else {
                // アイコン指定無し（デフォルトを使用）
                setupDefaultIcon(name, loaded);
            }
        }

        getSymbol() {
            return this.symbol;
        }

        getParameter() {
            return this.parameter;
        }

        addLoadListener(onLoad) {
            if (!!onLoad && typeof onLoad === 'function') {
                if (this._isReady === true) {
                    onLoad();
                } else {
                    this._loadListeners.push(onLoad);
                }
            }
        }

        isActive() {
            return this._state;
        }

        activate(value) {
            this.reflashState(value);
        }

        onClick() {
            if (!!this.handler && typeof this.handler === 'function') {
                this.handler(this, this.symbol, !this.isActive());
            }
            this._isClicked = true;
        }

        isClicked() {
            return this._isClicked;
        }

        processTouch() {
            this._isClicked = false;
            super.processTouch();
        }

        reflashState(active) {
            if (this._state !== active) {
                this._state = active;

                const icon = active ? this.activeIcon : this.inactiveIcon;
                const old = !active ? this.activeIcon : this.inactiveIcon;

                if (this.children.length > 0) {
                    this.removeChild(old);
                }
                this.addChild(icon);
            }
        }

        destroy() {
            this.removeChildren(0, this.children.length);
            this.activeIcon.destroy();
            this.inactiveIcon.destroy();
            super.destroy();
        }
    }

    /********************************************************************************************************
     * 立ち絵
     ********************************************************************************************************/

    /**
     * 立ち絵スプライトクラス
     */
    class Sprite_Character extends Sprite_Clickable {
        static ACTIVE_Y_OFFSET = 30;
        static THLOTTLE_ACTIVE_ANIMATION = 3;
        static THLOTTLE_VISIBLE_ANIMATION = 15;
        static OVERLAP_ALPHA = 128;

        constructor(structure, onLoad) {
            super();
            this._data = this.loadPicture(structure);
            this._baseY = 0;
            this._active = true;
            this._inActivateAnimation = false;
            this._inShowAnimation = false;
            this._inHideAnimation = false;
            this._shownCallback = null;
            this._hiddenCallback = null;

            this._offsetWidth = structure.widthOffset;
            this._offsetHeight = structure.heightOffset;
            this.zIndex = 50;

            this._inOverlapAnimation = false;
            this._outOverlapAnimation = false;
            this._isOverlap = false;

            this._currentAnimation = [];
            this._checkOverlap = param.transparentWithPlayer;

            if (this._data.bitmaps.body.length > 0) {
                const baseImage = this._data.bitmaps.body[0];
                baseImage.addLoadListener(() => {
                    const width = baseImage.width;
                    const height = baseImage.height;
                    this.bitmap = new Bitmap(width, height);
                    this.refresh();

                    if (onLoad && typeof onLoad === 'function') {
                        onLoad(this);
                    }
                })
            } else {
                // no base image
                if (DebugSwitch.isEnable()) {
                    alert(pluginName + ': 身体画像が設定されてない name[' + p.name + ']');
                }
                if (onLoad && typeof onLoad === 'function') {
                    onLoad(this);
                }
            }
        }

        loadPicture(structure) {
            let animations = {};
            structure.animations.map(p => p._parameter).forEach(p => {
                animations[p.name] = {
                    name: p.name,
                    interval: p.interval,
                    repeat: p.repeat,
                    hidePart: p.hidePart,
                    frames: p.frames.map(path => ImageManager.loadPicture(path)),
                };
            });

            let presets = {};
            structure.presets.map(p => p._parameter).forEach(p => {
                presets[p.name] = p;
            });

            return {
                name: structure.name,
                bitmaps: {
                    back1: structure.back1.map(path => ImageManager.loadPicture(path)),
                    back2: structure.back2.map(path => ImageManager.loadPicture(path)),
                    body: structure.body.map(path => ImageManager.loadPicture(path)),
                    hair: structure.hair.map(path => ImageManager.loadPicture(path)),
                    cloth: structure.cloth.map(path => ImageManager.loadPicture(path)),
                    eyeblow: structure.eyeblow.map(path => ImageManager.loadPicture(path)),
                    eye: structure.eye.map(path => ImageManager.loadPicture(path)),
                    mouth: structure.mouth.map(path => ImageManager.loadPicture(path)),
                    option1: structure.option1.map(path => ImageManager.loadPicture(path)),
                    option2: structure.option2.map(path => ImageManager.loadPicture(path)),
                    option3: structure.option3.map(path => ImageManager.loadPicture(path)),
                    option4: structure.option4?.map(path => ImageManager.loadPicture(path)),
                    option5: structure.option5?.map(path => ImageManager.loadPicture(path)),
                    option6: structure.option6?.map(path => ImageManager.loadPicture(path)),
                    option7: structure.option7?.map(path => ImageManager.loadPicture(path)),
                    option8: structure.option8?.map(path => ImageManager.loadPicture(path)),
                },
                iamgePath: {
                    back1: structure.back1,
                    back2: structure.back2,
                    body: structure.body,
                    hair: structure.hair,
                    cloth: structure.cloth,
                    eyeblow: structure.eyeblow,
                    eye: structure.eye,
                    mouth: structure.mouth,
                    option1: structure.option1,
                    option2: structure.option2,
                    option3: structure.option3,
                    option4: structure.option4,
                    option5: structure.option5,
                    option6: structure.option6,
                    option7: structure.option7,
                    option8: structure.option8,
                },
                index: {
                    back1: 0,
                    back2: 0,
                    body: 0,
                    hair: 0,
                    cloth: 0,
                    eyeblow: 0,
                    eye: 0,
                    mouth: 0,
                    option1: 0,
                    option2: 0,
                    option3: 0,
                    option4: 0,
                    option5: 0,
                    option6: 0,
                    option7: 0,
                    option8: 0,
                },
                animations: animations,
                presets: presets,
            };
        }

        name() {
            return this._data.name;
        }

        width() {
            return this.bitmap.width;
        }

        height() {
            return this.bitmap.height;
        }

        offsetWidth() {
            return this._offsetWidth;
        }

        offsetHeight() {
            return this._offsetHeight;
        }

        setPartValue(partData) {
            const key = partData.part;
            const image = partData.image;
            const visible = partData.visible;

            if (!this._data.iamgePath.hasOwnProperty(key)) {
                Logger.d(`setPartValue: key not found, key = ${key}`);
                return;
            }

            // find index
            let index = this._data.iamgePath[key].findIndex(path => path === image);
            if (index < 0) {
                index = this._data.bitmaps[key].push(ImageManager.loadPicture(image)) - 1;
                if (!visible) {
                    index = -1;
                }
                this._data.index[key] = index;
                this.refresh();
            } else {
                // found on set
                this.setIndex(key, index, visible);
            }
        }

        setIndex(key, index, visible) {
            if (this._data.index.hasOwnProperty(key)) {
                const old = this._data.index[key];
                if (!visible) {
                    index = -1;
                } else if (index >= this._data.bitmaps[key].length) {
                    index = this._data.bitmaps[key].length - 1;
                }

                this._data.index[key] = index;
                if (old !== index) {
                    this.refresh();
                }
            }
        }

        getIndex(key) {
            if (this._data.index.hasOwnProperty(key)) {
                return this._data.index[key];
            } else {
                return -1;
            }
        }

        keys() {
            return Object.keys(this._data.bitmaps);
        }

        startAnimation(name) {
            this.stopAnimation(name);
            if (this._data.animations.hasOwnProperty(name)) {
                const data = this._data.animations[name];
                this._currentAnimation.push({
                    data: data,
                    frame: 0,
                    intervalCount: 0,
                });
            }
        }

        stopAnimation(name) {
            if (this._data.animations.hasOwnProperty(name)) {
                this._currentAnimation = this._currentAnimation.filter(animation => {
                    return animation.data.name !== name;
                });
                this.refresh();
            }
        }

        getCurrentPictureSet() {
            // build deep copy of current picture set
            return JSON.parse(JSON.stringify(this._data.index));
        }

        applyPictureSet(pictureSet) {
            Object.keys(pictureSet).forEach(key => {
                this._data.index[key] = pictureSet[key];
            });
            this.refresh();
        }

        callPreset(presetName) {
            if (this._data.presets.hasOwnProperty(presetName)) {
                const preset = this._data.presets[presetName];
                preset.parts.map(p => p._parameter).forEach(p => {
                    if (p.visible) {
                        this._data.index[p.part] = (p.index - 1);
                    } else {
                        this._data.index[p.part] = -1;
                    }
                });
                this.refresh();
            } else if (DebugSwitch.isEnable()) {
                log(pluginName + ': 不明なプリセット [' + presetName + ']');
            }
        }

        reset() {
            this.keys().forEach(key => {
                this._data.index[key] = 0;
            });
            this._currentAnimation = [];
            this.refresh(true);
        }

        getPatternCount(part) {
            if (this._data.bitmaps.hasOwnProperty(part)) {
                return this._data.bitmaps.length;
            }
            return 0;
        }

        activate(active) {
            if (this._active !== active) {
                this._active = active;
                this._inActivateAnimation = true;
            }
        }

        move(x, y) {
            this._baseY = y;
            let coodinatedY = this._baseY;
            if (this._active) {
                coodinatedY -= Sprite_Character.ACTIVE_Y_OFFSET;
            }
            super.move(x, coodinatedY);
        }

        show(callback = null) {
            if (!this.visible) {
                if (this._inHideAnimation) {
                    this._inHideAnimation = false;
                } else {
                    this.opacity = 0;
                }
                super.show();
                this._inShowAnimation = true;
                this._shownCallback = callback;
            }
        }

        hide(callback = null) {
            if (this.visible) {
                this._inOverlapAnimation = false;
                this._outOverlapAnimation = false;
                this._inHideAnimation = true;
                this._hiddenCallback = callback;
            }
        }

        // visibleでの判定だと非表示アニメーション中の判定がバグるのでこちらを使う
        isShown() {
            return this.visible && !this._inHideAnimation;
        }

        // コード内での呼び出し用（即座に非表示）
        forceHide() {
            super.hide();
            this._inShowAnimation = false;
            this._inHideAnimation = false;
            this._inOverlapAnimation = false;
            this._outOverlapAnimation = false;
            this.opacity = 0;
        }

        refresh(updateAnimation = false) {
            this.bitmap.clear();

            const updateAnimationFrame = (animation) => {
                const bitmap = animation.data.frames[animation.frame];
                this.bitmap.blt(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0);

                if (updateAnimation) {
                    if (animation.intervalCount < animation.data.interval) {
                        // intervalがあるのでその分更新
                        animation.intervalCount++;
                    } else {
                        animation.intervalCount = 0;
                        if (animation.frame < animation.data.frames.length) {
                            animation.frame++;
                        }

                        if (animation.frame === animation.data.frames.length) {
                            // animationの終端に到達
                            animation.frame = 0;

                            if (!animation.data.repeat) {
                                // リピートではない場合完了したので削除リストに載せる
                                removeAnimationNames.push(animation.data.name);
                            }
                        }
                    }
                }
            };

            this.keys().forEach(key => {
                const targetAnimations = this._currentAnimation.filter(animation => {
                    return animation.data.hidePart && animation.data.hidePart === key;
                });

                if (targetAnimations.length === 0) {
                    // Animation無し
                    const index = this._data.index[key];
                    if (index >= 0 && !!this._data.bitmaps[key] && this._data.bitmaps[key].length > 0) {
                        const bitmap = this._data.bitmaps[key][index];
                        this.bitmap.blt(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0);
                    }
                } else {
                    let removeAnimationNames = [];
                    targetAnimations.forEach(animation => {
                        updateAnimationFrame(animation);
                    });

                    // 削除リストのアニメーションを再生中リストから削除
                    this._currentAnimation = this._currentAnimation.filter(animation => {
                        return !removeAnimationNames.includes(animation.data.name);
                    });
                }
            });

            // 部位指定の無いアニメーションはここで再生
            this._currentAnimation.filter(animation => !animation.data.hidePart).forEach(animation => {
                updateAnimationFrame(animation);
            });

            this.setBlendColor([ 0, 0, 0, this._active ? 0 : 128 ]);
        }

        update() {
            super.update();

            if (!this.updateVisibleAnimation()) {
                if (!this.updateActiveAnimation()) {
                    this.updateOverlapAnimation();
                }
            }

            if (this._currentAnimation.length > 0) {
                this.refresh(true);
            }
        }

        setOverlapTransparent(value) {
            this._checkOverlap = (value === true);
        }

        checkPlayerCollided() {
            const contains = this.getBounds().contains($gamePlayer.screenX(), $gamePlayer.screenY());
            if (!contains) {
                return false;
            }

            const global = new PIXI.Point($gamePlayer.screenX(), $gamePlayer.screenY());
            const local = this.toLocal(global);
            const pixel = this.bitmap.getAlphaPixel(local.x, local.y);
            // return pixel > 0; // for check pixel transpalent
            return true;
        }

        updateOverlapAnimation() {
            const currentOverlap = this._isOverlap;
            const updated = this.checkPlayerCollided() && this._checkOverlap;
            if (currentOverlap !== updated) {
                this._inOverlapAnimation = updated;
                this._outOverlapAnimation = !updated;
                this._isOverlap = updated;
            }

            const currentOpacity = this.opacity;
            if (this._inOverlapAnimation) {
                if (currentOpacity > Sprite_Character.OVERLAP_ALPHA) {
                    this.opacity = currentOpacity - Sprite_Character.THLOTTLE_VISIBLE_ANIMATION;
                    return true;
                } else if (currentOpacity <= Sprite_Character.OVERLAP_ALPHA) {
                    this.opacity = Sprite_Character.OVERLAP_ALPHA;
                    this._inOverlapAnimation = false;
                }
            } else if (this._outOverlapAnimation) {
                if (currentOpacity < 255) {
                    this.opacity = currentOpacity + Sprite_Character.THLOTTLE_VISIBLE_ANIMATION;
                    return true;
                } else if (currentOpacity >= 255) {
                    this.opacity = 255;
                    this._outOverlapAnimation = false;
                }
            }
            return false;
        }

        updateActiveAnimation() {
            if (this._inActivateAnimation) {
                if (this._active) {
                    const targetY = (this._baseY - Sprite_Character.ACTIVE_Y_OFFSET);
                    if (this.y > targetY) {
                        super.move(this.x, this.y - Sprite_Character.THLOTTLE_ACTIVE_ANIMATION);
                        return true;
                    } else if (this.y <= targetY) {
                        super.move(this.x, targetY);
                        this.refresh();
                        this._inActivateAnimation = false;
                    }
                } else {
                    if (this.y < this._baseY) {
                        super.move(this.x, this.y + Sprite_Character.THLOTTLE_ACTIVE_ANIMATION);
                        return true;
                    } else if (this.y >= this._baseY) {
                        super.move(this.x, this._baseY);
                        this.refresh();
                        this._inActivateAnimation = false;
                    }
                }
            }
            return false;
        }

        updateVisibleAnimation() {
            const currentOpacity = this.opacity;
            if (this._inShowAnimation) {
                if (currentOpacity < 255) {
                    this.opacity = currentOpacity + Sprite_Character.THLOTTLE_VISIBLE_ANIMATION;
                    return true;
                } else if (currentOpacity >= 255) {
                    this.opacity = 255;
                    this._inShowAnimation = false;
                    if (!!this._shownCallback) {
                        this._shownCallback();
                        this._shownCallback = null;
                    }
                }
            } else if (this._inHideAnimation) {
                if (currentOpacity > 0) {
                    this.opacity = currentOpacity - Sprite_Character.THLOTTLE_VISIBLE_ANIMATION;
                    return true;
                } else if (currentOpacity <= 0) {
                    this.opacity = 0;
                    super.hide();
                    this._inHideAnimation = false;
                    if (!!this._hiddenCallback) {
                        this._hiddenCallback();
                        this._hiddenCallback = null;
                    }
                }
            }
            return false;
        }
    }

    /**
     * スチルスプライトクラス
     */
    class Sprite_Still extends Sprite {
        constructor(structure) {
            super(new Bitmap($dataSystem.advanced.screenWidth, $dataSystem.advanced.screenHeight));

            this._data = this.loadPicture(structure);
            this.zIndex = 60;

            this.refresh();
        }

        loadPicture(structure) {
            const pathes = {
                back1: structure.back1,
                back2: structure.back2,
                body: structure.body,
                cloth: structure.cloth,
                eyeblow: structure.eyeblow,
                eye: structure.eye,
                mouth: structure.mouth,
                hair: structure.hair,
                option1: structure.option1,
                option2: structure.option2,
                option3: structure.option3,
                option4: structure.option4,
                option5: structure.option5,
                option6: structure.option6,
                option7: structure.option7,
                option8: structure.option8,
            };

            const bitmapEntries = Object.keys(pathes).filter(key => !!pathes[key]).map(key => {
                return [key, pathes[key].map(path => ImageManager.loadPicture(path))];
            });
            const bitmaps = Object.fromEntries(bitmapEntries);

            const index = Object.fromEntries(Object.keys(pathes).map(key => {
                return [key, 0];
            }));

            const presets = Object.fromEntries(structure.presets
                    .map(p => p._parameter)
                    .map(p => {
                        return [p.name, p];
                    }));

            return {
                name: structure.name,
                bitmaps: bitmaps,
                iamgePath: pathes,
                index: index,
                presets: presets,
            };
        }

        name() {
            return this._data.name;
        }

        width() {
            return Graphics.width;
        }

        height() {
            return Graphics.height;
        }

        keys() {
            return Object.keys(this._data.bitmaps);
        }

        show() {
            this.refresh();
            super.show();
        }

        callPreset(presetName) {
            if (this._data.presets.hasOwnProperty(presetName)) {
                const preset = this._data.presets[presetName];
                preset.parts.map(p => p._parameter).forEach(p => {
                    if (p.visible) {
                        this._data.index[p.part] = (p.index - 1);
                    } else {
                        this._data.index[p.part] = -1;
                    }
                });
                this.refresh();
            } else if (DebugSwitch.isEnable()) {
                log(pluginName + ': 不明なプリセット [' + presetName + ']');
            }
        }

        setPartValue(partData) {
            const key = partData.part;
            const image = partData.image;
            const visible = partData.visible;

            if (!this._data.iamgePath.hasOwnProperty(key)) {
                Logger.d(`setPartValue: key not found, key = ${key}`);
                return;
            }

            // find index
            let index = this._data.iamgePath[key].findIndex(path => path === image);
            if (index < 0) {
                index = this._data.bitmaps[key].push(ImageManager.loadPicture(image)) - 1;
                if (!visible) {
                    index = -1;
                }
                this._data.index[key] = index;
                this.refresh();
            } else {
                // found on set
                this.setIndex(key, index, visible);
            }
        }

        setIndex(key, index, visible) {
            if (this._data.index.hasOwnProperty(key)) {
                const old = this._data.index[key];
                if (!visible) {
                    index = -1;
                } else if (index >= this._data.bitmaps[key].length) {
                    index = this._data.bitmaps[key].length - 1;
                }

                this._data.index[key] = index;
                if (old !== index) {
                    this.refresh();
                }
            }
        }

        callPreset(presetName) {
            if (this._data.presets.hasOwnProperty(presetName)) {
                const preset = this._data.presets[presetName];
                preset.parts.map(p => p._parameter).forEach(p => {
                    if (p.visible) {
                        this._data.index[p.part] = (p.index - 1);
                    } else {
                        this._data.index[p.part] = -1;
                    }
                });
                this.refresh();
            } else if (DebugSwitch.isEnable()) {
                log(pluginName + ': 不明なプリセット [' + presetName + ']');
            }
        }

        reset() {
            this.keys().forEach(key => {
                this._data.index[key] = 0;
            });
            this.refresh();
        }

        refresh() {
            this.bitmap.clear();

            this.keys().forEach(key => {
                const index = this._data.index[key];
                if (index >= 0 && this._data.bitmaps[key].length > 0) {
                    const bitmap = this._data.bitmaps[key][index];
                    this.bitmap.blt(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0);
                }
            });
        }
    }

    /********************************************************************************************************
     * プラグインコマンド呼び出しがウェイトされない問題の対応
     ********************************************************************************************************/
    const _Game_Interpreter_prototype_command357 = Game_Interpreter.prototype.command357;
    Game_Interpreter.prototype.command357 = function(params) {
        const pluginName = Utils.extractFileName(params[0]);
        const result = PluginManager.callCommand(this, pluginName, params[1], params[3]);
        return result !== 'fault';
    };

    /********************************************************************************************************
     * プラグインコマンド
     ********************************************************************************************************/
    SceneManager.getCurrentScene = function() {
        return this._scene;
    };

    SceneManager.isAdvScene = function() {
        return !!this._scene && this._scene instanceof Scene_Message;
    };

    PluginManagerEx.registerCommand(script, 'showStill', args => {
        $gameTemp.adv.showStill(args.name);
    });

    PluginManagerEx.registerCommand(script, 'hideStill', args => {
        $gameTemp.adv.hideStill();
    });

    PluginManagerEx.registerCommand(script, 'setStillParts', args => {
        findStill(args.name, (still) => {
            args.parts.map(p => p._parameter).forEach(p => {
                still.setPartValue(p);
            });
        });
    });

    PluginManagerEx.registerCommand(script, 'showCharacter', args => {
        $gameTemp.adv.showCharacter(args.actor, args.name, args.align, args.offsetX, args.offsetY);
    });

    PluginManagerEx.registerCommand(script, 'hideCharacter', args => {
        $gameTemp.adv.hideCharacter(args.actor, args.name);
    });

    PluginManagerEx.registerCommand(script, 'hideAllCharacter', args => {
        // $gameTemp.adv.hideAllCharacters();
        $gameTemp.adv.hideCharacter(args.actor, args.name);
        const sprites = $gameTemp.adv.getCharacterSprites();
        Object.values(sprites).forEach(sprite => sprite.hide());
    });

    PluginManagerEx.registerCommand(script, 'setCharacterPartIndex', args => {
        findCharacter(args.actor, args.name, character => {
            character.setIndex(args.part, args.index - 1, args.visible);
        });
    });

    PluginManagerEx.registerCommand(script, 'setCharacterParts', args => {
        findCharacter(args.actor, args.name, character => {
            args.parts.map(p => p._parameter).forEach(p => {
                character.setPartValue(p);
            });
        });
    });

    PluginManagerEx.registerCommand(script, 'resetCharacter', args => {
        findCharacter(args.actor, args.name, character => {
            character.reset();
        });
    });

    PluginManagerEx.registerCommand(script, 'callPreset', args => {
        $gameTemp.adv.callPreset(args.actor, args.name, args.presetName);
    });

    PluginManagerEx.registerCommand(script, 'callStillPreset', args => {
        $gameTemp.adv.callStillPreset(args.name, args.presetName);
    });

    PluginManagerEx.registerCommand(script, 'message', function(args) {
        if ($gameMessage.isBusy()) {
            if (DebugSwitch.isEnable()) {
                log(pluginName + ': message in busy');
            }
            return 'fault';
        }

        const name = decideName(args.actor, args.name);
        if (args.altName && args.altName.length > 0) {
            $gameMessage.setSpeakerName(args.altName);
        } else if (!!name.match(/actor:\d+/)) {
            const id = name.split(':')[1];
            $gameMessage.setSpeakerName($gameActors.actor(id).name());
        } else if (!name || name.length === 0) {
            if (!!param.blankName && param.blankName !== '' && args.showName !== 'false') {
                $gameMessage.setSpeakerName(param.blankName);
            } else {
                $gameMessage.setSpeakerName(null);
            }
        } else {
            $gameMessage.setSpeakerName(name);
        }


        if (name && name.length > 0 && args.preset && args.preset.length > 0) {
            // キャラ指定とプリセット指定がある場合プリセットを一時変更する
            $gameMessage.setPreset(args.actor, args.name, args.preset);
        }

        if (name && name.length > 0 && args.animation && args.animation.length > 0) {
            // キャラ指定とアニメーション指定が両方ある時だけ設定
            $gameMessage.setAnimation({
                actor: args.actor,
                target: name,
                animationName: args.animation,
            });
        }

        /*
         * キャラクターを有効にする条件
         *
         * ・キャラクターの自動有効化設定がON
         * ・キャラクターが複数表示されていない
         * ・ネームボックスに指定がない
         * ・ネームボックスの指定がキャラクター登録名と一致
         */
        if (args.autoActivation) {
            const sprites = $gameTemp.adv.getCharacterSprites();
            const visibleSprites = Object.values(sprites).filter(sprite => sprite.isShown());
            const visibleCount = visibleSprites.length;
            if (visibleCount > 0) {
                if (visibleCount === 1) {
                    // １人しかキャラ表示されてない
                    visibleSprites[0].activate(true);
                } else if (!name || name.length === 0) {
                    // 対象キャラ指定が無いので全員有効
                    visibleSprites.forEach(sprite => sprite.activate(true));
                } else {
                    let chName = name;
                    if (!!name.match(/actor:\d+/)) {
                        const id = name.split(':')[1];
                        chName = $gameActors.actor(id).name();
                    }
                    visibleSprites.forEach(sprite => {
                        const spriteName = sprite.name();
                        Logger.d(`autoActivation: name = ${chName}, sprite = ${spriteName}, match = ${spriteName === chName}`);
                        sprite.activate(spriteName === chName);
                    });
                }
            }
        }

        // 文章表示の設定
        if (!!args.label && args.label !== '') {
            Logger.d(`set message: label = ${args.label}, use voice = ${args.useVoice}`);
            $gameMessage.add(TextResource.getText(args.label));
            if (args.useVoice) {
                $gameMessage.setVoice('voice/' + args.label);
            }
        } else {
            $gameMessage.add(args.text);
            $gameMessage.setVoice(args.voice);
        }

        // 次が選択肢等の場合nextEventCodeで判定して表示する必要があるが、
        // 引数があるとその分引数表示用のイベントコード657が連続するのでその分飛ばす
        while (this.nextEventCode() === 657) {
            this._index++;
        }

        switch (this.nextEventCode()) {
            case 102: // Show Choices
                this._index++;
                this.setupChoices(this.currentCommand().parameters);
                break;
            case 103: // Input Number
                this._index++;
                this.setupNumInput(this.currentCommand().parameters);
                break;
            case 104: // Select Item
                this._index++;
                this.setupItemChoice(this.currentCommand().parameters);
                break;
        }
        this.setWaitMode("message");
        return true;
    });

    PluginManagerEx.registerCommand(script, 'activateCharacter', args => {
        findCharacter(args.actor, args.name, character => {
            character.activate(args.active);
        });
    });

    PluginManagerEx.registerCommand(script, 'transparentOnPlayer', args => {
        $gameTemp.adv.setOverlapTransparent(args.transparent);
    });

    PluginManagerEx.registerCommand(script, 'runEffect', args => {
        $gameTemp.adv.runEffect(
                args.effect,
                args.picture,
                args.pictureId,
                args.stillName,
                args.preset,
                args.interpreter);
    });

    PluginManagerEx.registerCommand(script, 'showEffectPicture', args => {
        const picture = PluginUtils.getVariables(param.effectParam);
        const pictureId = PluginUtils.getVariables(param.effectPicIdParam);

        const name = PluginUtils.getVariables(param.effectStillParam);
        const presetName = PluginUtils.getVariables(param.effectPresetParam);
        if (!!name && name !== '' && name !== 0) {
            console.log(`showEffectPicture: still = ${name}, preset = ${presetName}`);

            findStill(name, still => {
                still.callPreset(presetName);
            });
        } else if (!!picture && picture !== '' && pictureId !== 0) {
            console.log(`showEffectPicture: ${picture}, id = ${pictureId}`);
            $gameScreen.showPicture(
                pictureId,
                picture,
                0,
                0, 0,
                100, 100,
                255, 0);
        }
    });

    PluginManagerEx.registerCommand(script, 'fade', args => {
        if (SceneManager.isAdvScene()) {
            SceneManager._scene.fade(args.type, args.color, args.opacity, args.frame);
            args.interpreter.setWaitMode(APPENDIX_WAIT_MODE_FADE);
        }
    });

    PluginManagerEx.registerCommand(script, 'clearFade', args => {
        if (SceneManager.isAdvScene()) {
            SceneManager._scene.clearFade();
        }
    });

    PluginManagerEx.registerCommand(script, 'waitClick', args => {
        if (SceneManager.isAdvScene()) {
            $gameTemp.adv.setWaitClick(true);
            args.interpreter.setWaitMode(APPENDIX_WAIT_MODE_CLICK);
        }
    });

    PluginManagerEx.registerCommand(script, 'advEntry', args => {
        $gamePlayer.setTransparent(true);
        $gamePlayer.setMovable(false);

        if (SceneManager._scene.constructor !== Scene_Map) {
            SceneManager.push(Scene_Map);
        }
        const map = (!!args.map) ? args.map : param.advMap._parameter;

        if ($gameMap.mapId() !== map.mapId) {
            PluginUtils.transferMap(map.mapId, map.x, map.y, 2, args.interpreter);
            args.interpreter.setWaitMode("transfer");
        }
    });

    PluginManagerEx.registerCommand(script, 'advLeave', args => {
        $gameTemp.adv.sweep();

        const stackLength = SceneManager._stack.length;
        if (stackLength > 0 && !$gamePlayer.isTransferring()) {
          const prevScene = SceneManager._stack[stackLength - 1];
          if (!!prevScene && prevScene !== Scene_Map) {
            SceneManager.pop();
          }
        }
    });
})();
