//=============================================================================
// MPP_MessageEX.js
//=============================================================================
// Copyright (c) 2016 - 2022 Mokusei Penguin
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
//=============================================================================

/*:
 * @target MV MZ
 * @plugindesc Extend the text display function and add display effects.
 * @author Mokusei Penguin
 * @url 
 *
 * @help [version 4.4.0]
 * This plugin is for RPG Maker MV and MZ.
 * 
 * ▼ [Display text] control characters
 *   \SP[n]        # Text speed(characters per second / 0:Instant display)
 *   \AT[n]        # Change animation type to n(※1)
 *   \SET[n]       # Convert to the set string set(※2)
 *   \CO[S]        # Display character string S as one character
 *   \RB[S,R]      # Display character string S with ruby R(※3)
 *   \MX[n]        # Shift the next letter X coordinate by n pixels
 *   \MY[n]        # Shift the next letter Y coordinate by n pixels
 *   \PX[n]        # Change the X coordinate of the next character to n
 *   \PY[n]        # Change the Y coordinate of the next character to n
 *   \SW[n]        # Turn on switch n
 *   \SN[n]        # Replace with the name of skill ID n
 *   \SIN[n]       # Replace with icon and name of skill ID n
 *   \IN[n]        # Replace with the name of item ID n
 *   \IIN[n]       # Replace with the icon and name of item ID n
 *   \WN[n]        # Replace with the name of weapon ID n
 *   \WIN[n]       # Replace with icon and name of weapon ID n
 *   \AN[n]        # Replace with the name of armor ID n
 *   \AIN[n]       # Replace with icon and name of armor ID n
 *   \WE           # Wait until the event production is over(※4)
 *   \C[r,g,b]     # Specify the font color in RGB
 *   \FS[n]        # Change font size to n / Default:MV=28,MZ=26
 *   \FB           # Invert font bold flag
 *   \FI           # Invert font italic flag
 *   \OP[n]        # Character opacity(0～255) / Default:255
 *   \OC[n]        # Change the color of the character edge to n / 0:Default
 *   \OC[r,g,b]    # Specify the border color of characters in RGB
 *   \OC[r,g,b,a]  # Specify the border color of characters with RGBA(※5)
 *   \OW[n]        # Change the thickness of the character edge / Default:4
 *   \RC[n]        # Changed ruby color to n / 0:Default
 *   \RC[r,g,b]    # Specify ruby color in RGB
 *   \RC[r,g,b,a]  # Specify ruby color with RGBA(※5)
 *   \DF           # Return the text display setting to the default value(※6)
 *   \SV           # Memorize the current text display settings(※6)
 *   \LD           # Calling the settings memorized by \SV(※6)
 *
 *  - The following applies if it is included in the text
 *   \A            # Prohibition of skipping with enter key or shift key
 *   \ES           # Temporarily enable skipping event production(※7)
 *   \FO[n]        # Temporarily change the fade-out type(※8)
 *  
 *  - All control characters can be in either case
 * 
 * ▼ Control character details
 *  ※1: \AT[n] (Change animation type to n)
 *   - The default animation type is as follows.
 *       0: Default
 *       1: Letters emerge while sliding to the right
 *       2: Characters are displayed while spreading horizontally
 *       3: Characters are displayed while expanding
 *       4: Display characters from the left side(Text speed 6 recommended)
 *  
 *  ※2: \SET[n] (Convert to the set string set)
 *   - Converts to the character string specified by the plug-in parameter
 *     [Text Set].
 *   - Control characters can also be set.
 *  
 *  ※3: \RB[S,R] (Display character string S with ruby R)
 *   - Character strings with ruby characters are displayed together,
 *     not individually.
 * 
 *  ※4: \WE (Wait until the event production is over)
 *   - Wait until the production of the event set in the plug-in parameter
 *     [Wait Effects] is completed.
 *   - Even if you skip sentences, weight will be applied.
 * 
 *  ※5: \OC[r,g,b,a] (Specify the border color of characters with RGBA)
 *   - Specify the alpha value (a) from 0.0 to 1.0.
 * 
 *  ※6: \DF, \SV, \LD (Initialize / save / recall text display settings)
 *   - The following information is applicable.
 *       - Text speed
 *       - Anime type
 *       - Font color
 *       - Font size
 *       - Font bold flag
 *       - Font italic flag
 *       - Text opacity
 *       - Text outline color
 *       - Text outline width
 *       - Ruby color
 * 
 * 
 *  ※7: \ES (Temporarily enable skipping event production)
 *   - See the plugin command SetEffectSkip for details.
 * 
 *  ※8: \FO[n] (Temporarily change the fade-out type)
 *   - See the plugin command SetMesFadeOut for details.
 * 
 * ▼ Plugin command details
 *  - In MV, the variable N is referred to by writing v [N] in the item for
 *    inputting a numerical value.
 *  - In MZ, in the item to enter a numerical value, select the text and
 *    write v [N] to refer to the variable N.
 *    
 *  〇 MV / MZ
 *  
 *  〇 SetMesRow row  / messageRow
 *       row : Number of lines in the message window
 *   - Change the number of lines displayed in the message window.
 *    
 *  〇 SetMesFadeOut type  / messageFadeOut
 *       type : Fade out type
 *   - Change the fade-out type.
 *   - The types to specify are as follows.
 *       0: Default
 *       1: Gradually disappear (fast)
 *       2: Gradually disappear (slow)
 *       3: Scroll up
 *       4: Add text below (Only when Op3 is introduced)
 * 
 *  〇 SetEffectSkip bool  / effectSkip
 *       bool : Enabled with true, disabled with false
 *   - When you skip a sentence, you can change whether to skip the production.
 *   - The default setting is invalid.
 *   - The effect to skip can be set in the plug-in parameter [Skip Effects].
 *   - The control character \ES is temporary, but it is always applied after
 *     the command is executed.
 * 
 * ================================
 * Mail : wood_penguin＠yahoo.co.jp (＠ is half-width)
 * Blog : http://woodpenguin.blog.fc2.com/
 * License : MIT license
 * 
 *  @command messageRow
 *      @desc 
 *      @arg row
 *          @desc 
 *          @type number
 *              @min 1
 *              @max 99
 *          @default 4
 * 
 *  @command messageFadeOut
 *      @desc 
 *      @arg type
 *          @desc 
 *          @type select
 *              @option 0:Default
 *                  @value 0
 *              @option 1:Gradually disappear (fast)
 *                  @value 1
 *              @option 2:Gradually disappear (slow)
 *                  @value 2
 *              @option 3:Scroll up
 *                  @value 3
 *              @option 4:Op3| Add text below
 *                  @value 4
 *          @default 0
 * 
 *  @command effectSkip
 *      @desc 
 *      @arg boolean
 *          @desc 
 *          @type boolean
 *          @default true
 * 
 * 
 * @----------------
 * 
 *  @param Text Set
 *      @desc An array of sets of strings to call with \SET
 * (From the top, \SET[1], \SET[2] ...)
 *      @type string[]
 *      @default []
 *
 *  @param Wait Effects
 *      @desc \WE Directing to wait for the end when executing
 *      @type struct<WaitEffects>
 *      @default {"Scroll Map":"true","Set Movement Route":"true","Show Animation":"true","Show Balloon Icon":"true","Move Picture":"true","Tint Picture":"true","Tint Screen":"true","Flash Screen":"true","Shake Screen":"false","Set Weather Effect":"false","Fadeout BGM":"false","Fadeout BGS":"false","Play ME":"false"}
 *
 *  @param Skip Effects
 *      @desc A production that skips sentences at the same time when skipping sentences
 *      @type struct<SkipEffects>
 *      @default {"Scroll Map":"true","Set Movement Route":"true","Move Picture":"true","Tint Picture":"true","Tint Screen":"false","Flash Screen":"false","Shake Screen":"false","Set Weather Effect":"false"}
 *
 *  @param Skip Effects Timing
 *      @desc 
 *      @type select
 *          @option skip
 *          @option end of text
 *      @default skip
 *      @parent Skip Effects
 *
 *  @param Ruby Font Size
 *      @desc 
 *      @type number
 *          @min 4
 *          @max 99
 *      @default 14
 *
 *  @param Ruby Oy
 *      @desc 
 *      @type number
 *          @min -9999
 *          @max 9999
 *      @default 0
 * 
 *  @param Ruby Outline Width
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 99
 *      @default 2
 *
 *  @param Always Leave Ruby Height
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Default
 *      @desc 
 *      @type struct<Default>
 *      @default {"Anime Type":"1","Message Row":"4","FadeOut Type":"0","Text Speed":"60","Outline Color":"0,0,0,0.5","Outline Width":"4","Ruby Color":"255,255,255"}
 *
 */

/*~struct~WaitEffects:
 *  @param Scroll Map
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Set Movement Route
 *      @desc Excludes [Repeat operation]
 *      @type boolean
 *      @default true
 *
 *  @param Show Animation
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Show Balloon Icon
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Move Picture
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Picture
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Screen
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Flash Screen
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Shake Screen
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Set Weather Effect
 *      @desc 
 *      @type boolean
 *      @default false
 * 
 *  @param Fadeout BGM
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Fadeout BGS
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Play ME
 *      @desc 
 *      @type boolean
 *      @default false
 *
 */

/*~struct~SkipEffects:
 *  @param Scroll Map
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Set Movement Route
 *      @desc Excludes [Repeat operation]
 *      @type boolean
 *      @default true
 *
 *  @param Move Picture
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Picture
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Screen
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Flash Screen
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Shake Screen
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Set Weather Effect
 *      @desc 
 *      @type boolean
 *      @default false
 * 
 */

/*~struct~Default:
 *  @param Anime Type
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 999
 *      @default 1
 *
 *  @param Message Row
 *      @desc 
 *      @type number
 *          @min 1
 *          @max 99
 *      @default 4
 *
 *  @param FadeOut Type
 *      @desc 
 *      @type select
 *          @option 0:Default
 *              @value 0
 *          @option 1:Gradually disappear (fast)
 *              @value 1
 *          @option 2:Gradually disappear (slow)
 *              @value 2
 *          @option 3:Scroll up
 *              @value 3
 *          @option 4:Op3| Add text below
 *              @value 4
 *      @default 0
 *
 *  @param Text Speed
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 999
 *      @default 60
 *
 *  @param Outline Color
 *      @desc Specified by RGBA / Alpha value is specified from 0 - 1.0
 *      @default 0,0,0,0.5
 *
 *  @param Outline Width
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 99
 *      @default 4
 *
 *  @param Ruby Color
 *      @desc Specified by RGB
 *      @default 255,255,255
 *
 */

/*:ja
 * @target MV MZ
 * @plugindesc 文章表示の機能を拡張したり表示の演出を追加します。
 * @author 木星ペンギン
 * @url 
 *
 * @help [version 4.4.0]
 * このプラグインはRPGツクールMVおよびMZ用です。
 * 
 * ▼ [文章の表示]の制御文字
 *   \SP[n]        # 文章の表示速度(秒間描写文字数n / 0:瞬間表示)
 *   \AT[n]        # アニメーションタイプをn番に変更(※1)
 *   \SET[n]       # 設定した文字列に変換(※2)
 *   \CO[S]        # 文字列Sを１文字として表示
 *   \RB[S,R]      # 文字列SにルビRを付けて表示(※3)
 *   \MX[n]        # 次に表示する文字のX座標をnピクセルずらす
 *   \MY[n]        # 次に表示する文字のY座標をnピクセルずらす
 *   \PX[n]        # 次に表示する文字のX座標をnに変更
 *   \PY[n]        # 次に表示する文字のY座標をnに変更
 *   \SW[n]        # スイッチn番をONにする
 *   \SN[n]        # スキルID n 番の名前に置き換える
 *   \SIN[n]       # スキルID n 番のアイコンと名前に置き換える
 *   \IN[n]        # アイテムID n 番の名前に置き換える
 *   \IIN[n]       # アイテムID n 番のアイコンと名前に置き換える
 *   \WN[n]        # 武器ID n 番の名前に置き換える
 *   \WIN[n]       # 武器ID n 番のアイコンと名前に置き換える
 *   \AN[n]        # 防具ID n 番の名前に置き換える
 *   \AIN[n]       # 防具ID n 番のアイコンと名前に置き換える
 *   \WE           # イベントの演出が終了するまでウェイト(※4)
 *   \C[r,g,b]     # 文字色をRGBで指定
 *   \FS[n]        # 文字サイズをnに変更 / デフォルト値:MV=28,MZ=26
 *   \FB           # 文字の太字フラグを反転
 *   \FI           # 文字の斜体フラグを反転
 *   \OP[n]        # 文字の不透明度(0～255) / デフォルト値:255
 *   \OC[n]        # 文字の縁の色をn番に変更 / 0:デフォルト(黒)
 *   \OC[r,g,b]    # 文字の縁の色をRGBで指定
 *   \OC[r,g,b,a]  # 文字の縁の色をRGBAで指定(※5)
 *   \OW[n]        # 文字の縁の太さを変更 / デフォルト値:4
 *   \RC[n]        # ルビの色をn番に変更 / 0:デフォルト
 *   \RC[r,g,b]    # ルビの色をRGBで指定
 *   \RC[r,g,b,a]  # ルビの色をRGBAで指定(※5)
 *   \DF           # 文章表示の設定をデフォルト値に戻す(※6)
 *   \SV           # 現在の文章表示の設定を記憶(※6)
 *   \LD           # \SVで記憶した設定の呼び出し(※6)
 *
 *  - 以下は文章内に含まれていた場合に適用
 *   \A            # 決定キーやシフトキーによるスキップの禁止
 *   \ES           # イベントの演出のスキップを一時的に有効にする(※7)
 *   \FO[n]        # フェードアウトタイプを一時的に変更する(※8)
 *  
 *  - すべての制御文字は大文字小文字どちらでも可能
 * 
 * ▼ 制御文字詳細
 *  ※1: \AT[n] (アニメーションタイプをn番に変更)
 *   - アニメーションタイプのデフォルトは以下のようになります。
 *       0: デフォルト
 *       1: 文字が右にスライドしながら浮かび上がる
 *       2: 文字が横に広がりながら表示される
 *       3: 文字が拡大しながら表示される
 *       4: 文字を左側から表示する(表示速度6推奨)
 *  
 *  ※2: \SET[n] (設定した文字列に変換)
 *   - プラグインパラメータ[Text Set]で指定した文字列に変換します。
 *   - 制御文字も設定可能です。
 *  
 *  ※3: \RB[S,R] (文字列SにルビRを付けて表示)
 *   - ルビを振った文字列は一文字ずつではなくまとめて表示されます。
 * 
 *  ※4: \WE (演出が終了するまでウェイト)
 *   - プラグインパラメータ[Wait Effects]で設定したイベントの演出が終了するまで
 *     ウェイトを行います。
 *   - 文章のスキップを行ってもウェイトがかかります。
 * 
 *  ※5: \OC[r,g,b,a] (文字の縁の色をRGBAで指定)
 *   - アルファ値(a)は0.0～1.0で指定してください。
 * 
 *  ※6: \DF, \SV, \LD (文章表示の設定を初期化/保存/呼び出し)
 *   - 以下の情報が対象となります。
 *       - 文章の表示速度
 *       - アニメーションタイプ
 *       - 文字色
 *       - 文字サイズ
 *       - 文字の太字フラグ
 *       - 文字の斜体フラグ
 *       - 文字の不透明度
 *       - 文字の縁の色
 *       - 文字の縁の太さ
 *       - ルビの色
 * 
 *  ※7: \ES (イベントの演出のスキップを一時的に有効にする)
 *   - 詳細はプラグインコマンドの SetEffectSkip を参照。
 * 
 *  ※8: \FO[n] (フェードアウトタイプを一時的に変更する)
 *   - 詳細はプラグインコマンドの SetMesFadeOut を参照。
 * 
 * ▼ プラグインコマンド詳細
 *  - MVでは数値を入力する項目で v[N] と記述することで変数N番を参照します。
 *  - MZでは数値を入力する項目で、テキストを選択して v[N] と記述することで
 *    変数N番を参照します。
 *    
 *  〇 MV / MZ
 *  
 *  〇 SetMesRow row  / メッセージ行数設定
 *       row : メッセージウィンドウの行数
 *   - メッセージウィンドウの表示行数をn行に変更します。
 *    
 *  〇 SetMesFadeOut type  / メッセージフェードアウト
 *       type : フェードアウトのタイプ
 *   - フェードアウトのタイプを変更します。
 *   - 指定するタイプは以下の通りです。
 *       0: デフォルト
 *       1: 徐々に消える(早め)
 *       2: 徐々に消える(遅め)
 *       3: 上にスクロール
 *       4: 下に文章を追加(Op3 導入時のみ)
 * 
 *  〇 SetEffectSkip bool  / 演出スキップ
 *       bool : trueで有効, falseで無効
 *   - 文章のスキップを行った際、演出のスキップをするかどうかを変更できます。
 *   - 初期設定は無効です。
 *   - スキップする演出はプラグインパラメータ[Skip Effects]にて設定できます。
 *   - 制御文字 \ES は一時的なものですが、こちらはコマンド実行後、常に適用されます。
 * 
 * ================================
 * Mail : wood_penguin＠yahoo.co.jp (＠は半角)
 * Blog : http://woodpenguin.blog.fc2.com/
 * License : MIT license
 * 
 *  @command messageRow
 *      @text メッセージ行数設定
 *      @desc 
 *      @arg row
 *          @desc 
 *          @type number
 *              @min 1
 *              @max 99
 *          @default 4
 * 
 *  @command messageFadeOut
 *      @text メッセージフェードアウト
 *      @desc 
 *      @arg type
 *          @desc 
 *          @type select
 *              @option 0:デフォルト(瞬時に消える)
 *                  @value 0
 *              @option 1:徐々に消える(早め)
 *                  @value 1
 *              @option 2:徐々に消える(遅め)
 *                  @value 2
 *              @option 3:上にスクロール
 *                  @value 3
 *              @option 4:Op3| 下に文章を追加
 *                  @value 4
 *          @default 0
 * 
 *  @command effectSkip
 *      @text 演出スキップ
 *      @desc 
 *      @arg boolean
 *          @desc 
 *          @type boolean
 *          @default true
 * 
 * 
 * @----------------
 * 
 *  @param Text Set
 *      @text 文字列セット
 *      @desc \SETにて呼び出す文字列のセットの配列
 * (上から \SET[1],\SET[2]... となります)
 *      @type string[]
 *      @default []
 *
 *  @param Wait Effects
 *      @text 終了待ちする演出
 *      @desc \WE実行時に終了待ちをする演出
 *      @type struct<WaitEffects>
 *      @default {"Scroll Map":"true","Set Movement Route":"true","Show Animation":"true","Show Balloon Icon":"true","Move Picture":"true","Tint Picture":"true","Tint Screen":"true","Flash Screen":"true","Shake Screen":"false","Set Weather Effect":"false","Fadeout BGM":"false","Fadeout BGS":"false","Play ME":"false"}
 *
 *  @param Skip Effects
 *      @text スキップする演出
 *      @desc 文章のスキップをした際、同時にスキップを行う演出
 *      @type struct<SkipEffects>
 *      @default {"Scroll Map":"true","Set Movement Route":"true","Move Picture":"true","Tint Picture":"true","Tint Screen":"false","Flash Screen":"false","Shake Screen":"false","Set Weather Effect":"false"}
 *
 *  @param Skip Effects Timing
 *      @text 演出スキップのタイミング
 *      @desc 
 *      @type select
 *          @option スキップ時
 *              @value skip
 *          @option 文章の表示終了時
 *              @value end of text
 *      @default skip
 *      @parent Skip Effects
 *
 *  @param Ruby Font Size
 *      @text ルビの文字サイズ
 *      @desc 
 *      @type number
 *          @min 4
 *          @max 99
 *      @default 14
 *
 *  @param Ruby Oy
 *      @text ルビY軸補正値
 *      @desc 
 *      @type number
 *          @min -9999
 *          @max 9999
 *      @default 0
 * 
 *  @param Ruby Outline Width
 *      @text ルビの縁の太さ
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 99
 *      @default 2
 *
 *  @param Always Leave Ruby Height
 *      @text 常にルビの高さを空ける
 *      @desc 
 *      @type boolean
 *      @default false
 *      @parent Ruby
 *
 *  @param Default
 *      @text デフォルト値
 *      @desc 
 *      @type struct<Default>
 *      @default {"Anime Type":"1","Message Row":"4","FadeOut Type":"0","Text Speed":"60","Outline Color":"0,0,0,0.5","Outline Width":"4","Ruby Color":"255,255,255"}
 *
 */

/*~struct~WaitEffects:ja
 *  @param Scroll Map
 *      @text マップのスクロール
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Set Movement Route
 *      @text 移動ルートの設定
 *      @desc [動作を繰り返す]は除く
 *      @type boolean
 *      @default true
 *
 *  @param Show Animation
 *      @text アニメーションの表示
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Show Balloon Icon
 *      @text フキダシアイコンの表示
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Move Picture
 *      @text ピクチャの移動
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Picture
 *      @text ピクチャの色調変更
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Screen
 *      @text 画面の色調変更
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Flash Screen
 *      @text 画面のフラッシュ
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Shake Screen
 *      @text 画面のシェイク
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Set Weather Effect
 *      @text 天候の設定
 *      @desc 
 *      @type boolean
 *      @default false
 * 
 *  @param Fadeout BGM
 *      @text BGMのフェードアウト
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Fadeout BGS
 *      @text BGSのフェードアウト
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Play ME
 *      @text MEの演奏
 *      @desc 
 *      @type boolean
 *      @default false
 *
 */

/*~struct~SkipEffects:ja
 *  @param Scroll Map
 *      @text マップのスクロール
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Set Movement Route
 *      @text 移動ルートの設定
 *      @desc [動作を繰り返す]は除く
 *      @type boolean
 *      @default true
 *
 *  @param Move Picture
 *      @text ピクチャの移動
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Picture
 *      @text ピクチャの色調変更
 *      @desc 
 *      @type boolean
 *      @default true
 *
 *  @param Tint Screen
 *      @text 画面の色調変更
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Flash Screen
 *      @text 画面のフラッシュ
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Shake Screen
 *      @text 画面のシェイク
 *      @desc 
 *      @type boolean
 *      @default false
 *
 *  @param Set Weather Effect
 *      @text 天候の設定
 *      @desc 
 *      @type boolean
 *      @default false
 * 
 */

/*~struct~Default:ja
 *  @param Anime Type
 *      @text アニメーションタイプ
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 999
 *      @default 1
 *
 *  @param Message Row
 *      @text メッセージ行数
 *      @desc [メッセージウィンドウの表示行数]のデフォルト値
 *      @type number
 *          @min 1
 *          @max 99
 *      @default 4
 *
 *  @param FadeOut Type
 *      @text フェードアウトタイプ
 *      @desc 
 *      @type select
 *          @option 0:デフォルト(瞬時に消える)
 *              @value 0
 *          @option 1:徐々に消える(早め)
 *              @value 1
 *          @option 2:徐々に消える(遅め)
 *              @value 2
 *          @option 3:上にスクロール
 *              @value 3
 *          @option 4:Op3| 下に文章を追加
 *              @value 4
 *      @default 0
 *
 *  @param Text Speed
 *      @text 文章の表示速度
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 999
 *      @default 60
 *
 *  @param Outline Color
 *      @text 文字の縁の色
 *      @desc RGBAで指定 / アルファ値は 0～1.0 で指定
 *      @default 0,0,0,0.5
 *
 *  @param Outline Width
 *      @text 文字の縁の太さ
 *      @desc 
 *      @type number
 *          @min 0
 *          @max 99
 *      @default 4
 *
 *  @param Ruby Color
 *      @text ルビの色
 *      @desc RGBで指定
 *      @default 255,255,255
 *
 */

(() => {
    'use strict';

    const pluginName = 'MPP_MessageEX';
    
    // Plugin Parameters
    const parameters = PluginManager.parameters(pluginName);
    const paramReplace = (key, value) => {
        try {
            return JSON.parse(value);
        } catch (e) {
            return value;
        }
    };
    const param_TextSet = JSON.parse(parameters['Text Set'] || '[]');
    const param_WaitEffects = JSON.parse(parameters['Wait Effects'] || '{}', paramReplace);
    const param_SkipEffects = JSON.parse(parameters['Skip Effects'] || '{}', paramReplace);
    const param_SkipEffectsTiming = parameters['Skip Effects Timing'] || 'skip';
    const param_RubyFontSize = Number(parameters['Ruby Font Size'] || 14);
    const param_RubyOy = Number(parameters['Ruby Oy'] || 0);
    const param_RubyOutlineWidth = Number(parameters['Ruby Outline Width'] || 2);
    const param_AlwaysLeaveRubyHeight = parameters['Always Leave Ruby Height'] === 'true';

    const param_Default = JSON.parse(parameters['Default'] || '{}', paramReplace);
    const getDefaultParam = (name, def = null) => {
        return name in param_Default ? param_Default[name] : def;
    };
    
    // Dealing with other plugins
    const __base = (obj, prop) => {
        if (obj.hasOwnProperty(prop)) {
            return obj[prop];
        } else {
            const proto = Object.getPrototypeOf(obj);
            return function () { return proto[prop].apply(this, arguments); };
        }
    };
    const _importedPlugin = (...names) => {
        return names.some(name => PluginManager._scripts.includes(name));
    };
    
    // MPP_Patch.js
    let param_Patch6 = false;
    if (_importedPlugin('MPP_Patch')) {
        const patchParameters = PluginManager.parameters('MPP_Patch');
        param_Patch6 = patchParameters['Patch6 enabled?'] === 'true';
    }
    
    // MathExt
    const MathExt = (() => {
        // Number.prototype.clamp と違い、下限優先
        const clamp = (x, min, max) => Math.max(Math.min(x, max), min);
        return { clamp };
    })();

    // RPG Maker Param
    const getTextColor = function(index) {
        return Utils.RPGMAKER_NAME === 'MV'
            ? this.textColor(index)
            : ColorManager.textColor(index);
    };
    
    //-----------------------------------------------------------------------------
    // Bitmap
    
    if (!Bitmap.prototype.destroy) {
        
        Bitmap.prototype.destroy = function() {
            if (this._baseTexture) {
                this._baseTexture.destroy();
                this.__baseTexture = null;
            }
            this._destroyCanvas();
        };
        
        Bitmap.prototype._destroyCanvas = function() {
            if (this._canvas) {
                this._canvas.width = 0;
                this._canvas.height = 0;
                this.__canvas = null;
            }
        };
        
    }

    //-------------------------------------------------------------------------
    // WebAudio

    WebAudio.prototype.realVolume = function() {
        return this._gainNode ? this._gainNode.gain.value : 0;
    };

    //-------------------------------------------------------------------------
    // Html5Audio

    if (Utils.RPGMAKER_NAME === 'MV') {
        
        Html5Audio.realVolume = function() {
            return this._audioElement ? this._audioElement.volume : 0;
        };
        
    }

    //-------------------------------------------------------------------------
    // AudioManager

    AudioManager.isBgmFadeOuting = function() {
        return (
            this._bgmBuffer &&
            !this._currentBgm &&
            this._bgmBuffer.realVolume() > 0
        );
    };

    AudioManager.isBgsFadeOuting = function() {
        return (
            this._bgsBuffer &&
            !this._currentBgs &&
            this._bgsBuffer.realVolume() > 0
        );
    };

    AudioManager.isMePlaying = function() {
        return this._meBuffer && this._meBuffer.isPlaying();
    };

    //-----------------------------------------------------------------------------
    // Game_Character

    Game_Character.prototype.isMoveRouteForcingNr = function() {
        return this.isMoveRouteForcing() && !this._moveRoute.repeat;
    };

    //-------------------------------------------------------------------------
    // Game_Map

    Game_Map.prototype.isAnyMoveRouteForcingNr = function() {
        return (
            this.events().some(e => e.isMoveRouteForcingNr()) ||
            $gamePlayer.isMoveRouteForcingNr()
        );
    };

    Game_Map.prototype.isAnyAnimationPlaying = function() {
        return (
            this.events().some(e => e.isAnimationPlaying()) ||
            $gamePlayer.isAnimationPlaying()
        );
    };

    Game_Map.prototype.isAnyBalloonPlaying = function() {
        return (
            this.events().some(e => e.isBalloonPlaying()) ||
            $gamePlayer.isBalloonPlaying()
        );
    };

    //-------------------------------------------------------------------------
    // Game_Screen

    Game_Screen.prototype.isAnyPictureMoving = function() {
        return this._pictures.some(p => p && p.isMoving());
    };

    Game_Screen.prototype.isAnyPictureTinting = function() {
        return this._pictures.some(p => p && p.isTinting());
    };

    Game_Screen.prototype.isTinting = function() {
        return this._toneDuration > 0;
    };

    Game_Screen.prototype.isFlashing = function() {
        return this._flashDuration > 0;
    };

    Game_Screen.prototype.isShaking = function() {
        return this._shakeDuration > 0;
    };

    Game_Screen.prototype.isWeatherChanging = function() {
        return this._weatherDuration > 0;
    };

    //-------------------------------------------------------------------------
    // Game_Picture

    Game_Picture.prototype.isMoving = function() {
        return this._duration > 0;
    };

    Game_Picture.prototype.isTinting = function() {
        return this._toneDuration > 0;
    };

    //-------------------------------------------------------------------------
    // Game_Message

    const _Game_Message_initialize = Game_Message.prototype.initialize;
    Game_Message.prototype.initialize = function() {
        _Game_Message_initialize.apply(this, arguments);
        this._messageRow = getDefaultParam('Message Row', 4);
        this._fadeOutType = getDefaultParam('FadeOut Type', 0);
        this._effectSkip = false;
    };

    const _Game_Message_clear = Game_Message.prototype.clear;
    Game_Message.prototype.clear = function() {
        _Game_Message_clear.apply(this, arguments);
        this._sceneEffectSkip = false;
    };

    Game_Message.prototype.clearSceneEffectSkip = function() {
        this._sceneEffectSkip = false;
    };

    Game_Message.prototype.requestSceneEffectSkip = function() {
        this._sceneEffectSkip = true;
    };

    Game_Message.prototype.messageRow = function() {
        return this._messageRow;
    };

    Game_Message.prototype.fadeOutType = function() {
        return this._fadeOutType;
    };

    Game_Message.prototype.effectSkip = function() {
        return this._effectSkip;
    };

    Game_Message.prototype.sceneEffectSkip = function() {
        return this._sceneEffectSkip;
    };

    Game_Message.prototype.setMessageRow = function(row) {
        this._messageRow = row;
    };

    Game_Message.prototype.setFadeOutType = function(type) {
        this._fadeOutType = type;
    };

    Game_Message.prototype.setEffectSkip = function(skip) {
        this._effectSkip = skip;
    };

    const _Game_Message_isBusy = Game_Message.prototype.isBusy;
    Game_Message.prototype.isBusy = function() {
        return (
            _Game_Message_isBusy.apply(this, arguments) ||
            this._sceneEffectSkip
        );
    };

    //-----------------------------------------------------------------------------
    // Game_Interpreter

    const _mzCommands = {
        SetMesRow: { name:'messageRow', keys:['row'] },
        SetMesFadeOut: { name:'messageFadeOut', keys:['type'] },
        SetEffectSkip: { name:'effectSkip', keys:['boolean'] }
    };
    Object.assign(_mzCommands, {
        'メッセージ行数設定': _mzCommands.SetMesRow,
        'メッセージフェードアウト': _mzCommands.SetMesFadeOut,
        '演出スキップ': _mzCommands.SetEffectSkip
    });

    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command, args) {
        _Game_Interpreter_pluginCommand.apply(this, arguments);
        const params = _mzCommands[command];
        if (params) {
            const args2 = Object.assign(
                {}, ...params.keys.map((k, i) => ({ [k]: args[i] }))
            );
            PluginManager.callCommandMV(this, pluginName, params.name, args2);
        }
    };
    
    //-------------------------------------------------------------------------
    // PluginManager
    
    if (!PluginManager.registerCommand && !PluginManager.registerCommandMV) {
        PluginManager._commandsMV = PluginManager._commandsMV || {};

        PluginManager.registerCommandMV = function(pluginName, commandName, func) {
            const key = pluginName + ':' + commandName;
            this._commandsMV[key] = func;
        };
        
        PluginManager.callCommandMV = function(self, pluginName, commandName, args) {
            const key = pluginName + ':' + commandName;
            const func = this._commandsMV[key];
            if (typeof func === 'function') {
                func.bind(self)(args);
            }
        };
    }

    {
        const _registerCommand = PluginManager.registerCommand || PluginManager.registerCommandMV;

        _registerCommand.call(PluginManager, pluginName, 'messageRow', args => {
            $gameMessage.setMessageRow(PluginManager.mppValue(args.row));
        });

        _registerCommand.call(PluginManager, pluginName, 'messageFadeOut', args => {
            $gameMessage.setFadeOutType(PluginManager.mppValue(args.type));
        });

        _registerCommand.call(PluginManager, pluginName, 'effectSkip', args => {
            $gameMessage.setEffectSkip(args.boolean === 'true');
        });
    }

    PluginManager.mppValue = function(value) {
        const match = /^V\[(\d+)\]$/i.exec(value);
        return match ? $gameVariables.value(+match[1]) : +value;
    };
    
    //-------------------------------------------------------------------------
    // Sprite_TextCharacter

    function Sprite_TextCharacter() {
        this.initialize.apply(this, arguments);
    }

    Sprite_TextCharacter.prototype = Object.create(Sprite.prototype);
    Sprite_TextCharacter.prototype.constructor = Sprite_TextCharacter;

    Sprite_TextCharacter.prototype.initialize = function() {
        Sprite.prototype.initialize.call(this);
        this._rect = null;
        this._list = null;
        this._index = -1;
        this._waitCount = 0;
    };

    Sprite_TextCharacter.prototype.setCallback = function(callback) {
        this._drawCallback = callback;
    };

    Sprite_TextCharacter.prototype.setup = function(bitmap, rect, list) {
        this.bitmap = bitmap;
        this._rect = rect;
        this._list = list;
        this._index = 0;
        this._waitCount = 0;
        this.initBasic();
        this.initMove();
        this.initScale();
        this.initOpacity();
        this.initTone();
        this.initRotation();
        this.initFrame();
        this.x = rect.x;
        this.y = rect.y;
        this.scale.x = 1.0;
        this.scale.y = 1.0;
        this.opacity = 255;
        this.anchor.x = 0;
        this.anchor.y = 0;
        this.setColorTone([0, 0, 0, 0]);
        this.rotation = 0;
        this.update();
    };

    Sprite_TextCharacter.prototype.initBasic = function() {
        this._origin = 0;
        this._offsetX = 0;
        this._offsetY = 0;
    };

    Sprite_TextCharacter.prototype.initMove = function() {
        this._targetX = 0;
        this._targetY = 0;
        this._moveDuration = 0;
    };

    Sprite_TextCharacter.prototype.initScale = function() {
        this._targetScaleX = 1;
        this._targetScaleY = 1;
        this._scaleDuration = 0;
    };

    Sprite_TextCharacter.prototype.initOpacity = function() {
        this._targetOpacity = 255;
        this._opacityDuration = 0;
    };

    Sprite_TextCharacter.prototype.initTone = function() {
        this._tone = null;
        this._toneTarget = null;
        this._toneDuration = 0;
    };

    Sprite_TextCharacter.prototype.initRotation = function() {
        this._angle = 0;
        this._targetAngle = 0;
        this._angleDuration = 0;
    };

    Sprite_TextCharacter.prototype.initFrame = function() {
        const bitmap = this._bitmap;
        if (bitmap) {
            this.setFrame(0, 0, bitmap.width, bitmap.height);
        } else {
            this.setFrame(0, 0, 0, 0);
        }
        this._frame2 = null;
        this._frame2Target = null;
        this._frame2Duration = 0;
    };

    Sprite_TextCharacter.prototype.isPlaying = function() {
        return !!this._list;
    };

    Sprite_TextCharacter.prototype.isEffecting = function() {
        return (
            this._moveDuration > 0 ||
            this._scaleDuration > 0 ||
            this._opacityDuration > 0 ||
            this._toneDuration > 0 ||
            this._angleDuration > 0 ||
            this._frame2Duration > 0
        );
    };

    Sprite_TextCharacter.prototype.update = function() {
        Sprite.prototype.update.call(this);
        if (this.isPlaying() && this.updateCommand()) {
            this.updateMove();
            this.updateScale();
            this.updateOpacity();
            this.updateTone();
            this.updateRotation();
            this.updateFrame2();
        }
    };

    Sprite_TextCharacter.prototype.updateCommand = function() {
        for (;;) {
            if (this.updateWaitCount()) {
                return true;
            }
            const code = this._list[this._index];
            if (code) {
                const [command, ...args] = code.split(' ');
                this.callMethod(command, args.map(Number));
            } else {
                this.draw();
                return false;
            }
            this._index++;
        }
    };

    Sprite_TextCharacter.prototype.updateWaitCount = function() {
        if (this._waitCount < 0) {
            if (this.isEffecting()) return true;
            this._waitCount = 0;
        } else if (this._waitCount > 0) {
            this._waitCount--;
            return true;
        }
        return false;
    };

    Sprite_TextCharacter.prototype.callMethod = function(command, args) {
        switch (command) {
            case 'show':
                this.commandShow(args);
                break;
            case 'move':
                this.commandMove(args);
                break;
            case 'scale':
                this.commandScale(args);
                break;
            case 'opacity':
                this.commandOpacity(args);
                break;
            case 'rotate':
                this.commandRotate(args);
                break;
            case 'tone':
                this.commandTone(args);
                break;
            case 'frame':
                this.commandFrame(args);
                break;
            case 'wait':
                this.commandWait(args);
                break;
            case 'finish':
                this.commandFinish(args);
                break;
        }
    };

    Sprite_TextCharacter.prototype.updateMove = function() {
        if (this._moveDuration > 0) {
            const d = this._moveDuration;
            this._offsetX = (this._offsetX * (d - 1) + this._targetX) / d;
            this._offsetY = (this._offsetY * (d - 1) + this._targetY) / d;
            this._moveDuration--;
        }
        this.x = this._rect.x + this._offsetX;
        this.y = this._rect.y + this._offsetY;
        if (this._origin === 1) {
            this.x += this._rect.width / 2;
            this.y += this._rect.height / 2;
        }
        this.refreshAnchor();
    };

    Sprite_TextCharacter.prototype.updateScale = function() {
        if (this._scaleDuration > 0) {
            const d = this._scaleDuration;
            this.scale.x = (this.scale.x * (d - 1) + this._targetScaleX) / d;
            this.scale.y = (this.scale.y * (d - 1) + this._targetScaleY) / d;
            this._scaleDuration--;
        }
    };

    Sprite_TextCharacter.prototype.updateOpacity = function() {
        if (this._opacityDuration > 0) {
            const d = this._opacityDuration;
            this.opacity = (this.opacity * (d - 1) + this._targetOpacity) / d;
            this._opacityDuration--;
        }
    };

    Sprite_TextCharacter.prototype.updateTone = function() {
        if (this._toneDuration > 0) {
            const d = this._toneDuration;
            for (const [i, target] of this._toneTarget.entries()) {
                this._tone[i] = (this._tone[i] * (d - 1) + target) / d;
            }
            this._toneDuration--;
        }
        if (this._tone) this.setColorTone(this._tone);
    };

    Sprite_TextCharacter.prototype.updateRotation = function() {
        if (this._angleDuration > 0) {
            const d = this._angleDuration;
            this._angle = (this._angle * (d - 1) + this._targetAngle) / d;
            this._angleDuration--;
        }
        this.rotation = this._angle * Math.PI / 180;
    };

    Sprite_TextCharacter.prototype.updateFrame2 = function() {
        if (this._frame2Duration > 0) {
            const d = this._frame2Duration;
            for (const [i, target] of this._frame2Target.entries()) {
                this._frame2[i] = (this._frame2[i] * (d - 1) + target) / d;
            }
            this._frame2Duration--;
            this.refreshFrame2();
            this.refreshAnchor();
        }
    };

    Sprite_TextCharacter.prototype.refreshFrame2 = function() {
        const { width, height } = this.bitmap;
        const frame = this._frame2;
        const fx = Math.round(width * frame[0] / 100);
        const fy = Math.round(height * frame[1] / 100);
        const fw = Math.max(Math.round(width * frame[2] / 100) - fx, 0);
        const fh = Math.max(Math.round(height * frame[3] / 100) - fy, 0);
        this.setFrame(fx, fy, fw, fh);
    };

    Sprite_TextCharacter.prototype.refreshAnchor = function() {
        const ox = (this._origin === 0 ? 0 : this._rect.width / 2) + 4;
        const oy = this._origin === 0 ? 0 : this._rect.height / 2;
        this.anchor.x = this.width > 0 ? ox / this.width : 0;
        this.anchor.y = this.height > 0 ? oy / this.height : 0;
    };

    Sprite_TextCharacter.prototype.draw = function() {
        if (this._drawCallback) {
            this._drawCallback(this.bitmap, this._rect);
        }
        this.delete();
    };

    Sprite_TextCharacter.prototype.delete = function() {
        this.parent.removeChild(this);
        this.bitmap.destroy();
        this.bitmap = null;
        this._list = null;
    };

    Sprite_TextCharacter.prototype.commandShow = function(args) {
        this._origin = MathExt.clamp(args[0] || 0, 0, 1);
        this._offsetX = args[1] || 0;
        this._offsetY = args[2] || 0;
        this.scale.x = args.length > 3 ? args[3] / 100 : 1;
        this.scale.y = args.length > 4 ? args[4] / 100 : 1;
        this.opacity = args.length > 5 ? args[5] : 255;
        this.initMove();
        this.initScale();
        this.initOpacity();
        this.initTone();
        this.initRotation();
        this.initFrame();
    };

    Sprite_TextCharacter.prototype.commandMove = function(args) {
        this._targetX = args[0] || 0;
        this._targetY = args[1] || 0;
        this._moveDuration = Math.max(args[2] || 0, 0);
        if (this._moveDuration === 0) {
            this._offsetX = this._targetX;
            this._offsetY = this._targetY;
        }
    };

    Sprite_TextCharacter.prototype.commandScale = function(args) {
        this._targetScaleX = args.length > 0 ? args[0] / 100 : 1;
        this._targetScaleY = args.length > 0 ? args[1] / 100 : 1;
        this._scaleDuration = Math.max(args[2] || 0, 0);
        if (this._scaleDuration === 0) {
            this.scale.x  = this._targetScaleX;
            this.scale.y  = this._targetScaleY;
        }
    };

    Sprite_TextCharacter.prototype.commandOpacity = function(args) {
        this._targetOpacity = args.length > 0 ? MathExt.clamp(args[0], 0, 255) : 255;
        this._opacityDuration = Math.max(args[1] || 0, 0);
        if (this._opacityDuration === 0) {
            this.opacity  = this._targetOpacity;
        }
    };

    Sprite_TextCharacter.prototype.commandRotate = function(args) {
        this._targetAngle = args[0] || 0;
        this._angleDuration = Math.max(args[1] || 0, 0);
        if (this._angleDuration === 0) {
            this._angle = this._targetAngle;
        }
    };

    Sprite_TextCharacter.prototype.commandTone = function(args) {
        if (!this._tone) this._tone = [0, 0, 0, 0];
        this._toneTarget = [];
        for (let i = 0; i < 4; i++) {
            this._toneTarget[i] = MathExt.clamp(args[i] || 0, -255, 255);
        }
        this._toneDuration = Math.max(args[4] || 0, 0);
        if (this._toneDuration === 0) {
            this._tone = this._toneTarget.clone();
            this.setColorTone(this._tone);
        }
    };

    Sprite_TextCharacter.prototype.commandFrame = function(args) {
        if (!this._frame2) this._frame2 = [0, 0, 100, 100];
        this._frame2Target = [];
        this._frame2Target[0] = args.length > 0 ? MathExt.clamp(args[0], 0, 100) : 0;
        this._frame2Target[1] = args.length > 1 ? MathExt.clamp(args[1], 0, 100) : 0;
        this._frame2Target[2] = args.length > 2 ? MathExt.clamp(args[2], 0, 100) : 100;
        this._frame2Target[3] = args.length > 3 ? MathExt.clamp(args[3], 0, 100) : 100;
        this._frame2Duration = Math.max(args[4] || 0, 0);
        if (this._frame2Duration === 0) {
            this._frame2 = this._frame2Target.clone();
            this.refreshFrame2();
        }
    };

    Sprite_TextCharacter.prototype.commandWait = function(args) {
        this._waitCount = args.length > 0 ? Math.max(args[0], 0) : -1;
    };

    Sprite_TextCharacter.prototype.commandFinish = function(args) {
        const count = Math.max(args[0] || 0, 0);
        this.commandMove([0, 0, count]);
        this.commandScale([100, 100, count]);
        this.commandOpacity([255, count]);
        this.commandRotate([0, count]);
        this.commandTone([0, 0, 0, 0, count]);
        this.commandFrame([0, 0, 100, 100, count]);
        this.commandWait([count]);
    };

    //-----------------------------------------------------------------------------
    // Window_Base

    const _Window_Base_convertEscapeCharacters = Window_Base.prototype.convertEscapeCharacters;
    Window_Base.prototype.convertEscapeCharacters = function(text) {
        return _Window_Base_convertEscapeCharacters.apply(this, arguments)
            .replace(/\x1bSET\[(\d+)\]/gi, (_, p1) => {
                const index = +p1;
                const setText = index > 0 ? param_TextSet[index - 1] : null;
                return setText ? this.convertEscapeCharacters(setText) : '';
            })
            .replace(/\x1b([SIWA])(I?)N\[(\d+)\]/gi, (_, p1, p2, p3) => {
                return this.mppConvertItemName(p1, p2 !== '', +p3);
            });
    };

    Window_Base.prototype.mppConvertItemName = function(type, icon, itemId) {
        let text = '';
        let item;
        switch (type.toUpperCase()) {
            case 'S':
                item = $dataSkills[itemId];
                break;
            case 'I':
                item = $dataItems[itemId];
                break;
            case 'W':
                item = $dataWeapons[itemId];
                break;
            case 'A':
                item = $dataArmors[itemId];
                break;
        }
        if (item) {
            if (icon) text += `\x1bI[${item.iconIndex}]`;
            text += item.name;
        }
        return text;
    };

    //-------------------------------------------------------------------------
    // Window_Message
    
    Window_Message.ANIMATIONS = [
        null,
        [ // type 1
            'show 0 -6 0 100 100 0',
            'finish 15'
        ],
        [ // type 2
            'show 0 0 0 0 100',
            'scale 75 100 6',
            'wait',
            'finish 6'
        ],
        [ // type 3
            'show 1 0 0 0 0',
            'scale 60 60 7',
            'wait',
            'scale 100 100 7',
            'wait',
            'scale 120 120 8',
            'wait',
            'finish 8'
        ],
        [ // type 4
            'show',
            'frame 0 0 0 100 0',
            'finish 10'
        ],
    ];
    
    const _Window_Message_initialize = Window_Message.prototype.initialize;
    Window_Message.prototype.initialize = function() {
        this._messageRow = $gameMessage.messageRow();
        this._textInfo = [];
        this._rubyBitmap = new Bitmap();
        _Window_Message_initialize.apply(this, arguments);
        this.createCharacterContainer();
        this._characterSprites = [];
        this._waitEffect = false;
    };

    const _Window_Message_destroy = __base(Window_Message.prototype, 'destroy');
    Window_Message.prototype.destroy = function(options) {
        this._rubyBitmap.destroy();
        _Window_Message_destroy.apply(this, arguments);
    };

    Window_Message.prototype.createCharacterContainer = function() {
        if (Utils.RPGMAKER_NAME === 'MV') {
            this._characterContainer = new Sprite();
            this.addChild(this._characterContainer);
            this.updateCharacterContainer();
        }
    };

    const _Window_Message_fittingHeight = __base(Window_Message.prototype, 'fittingHeight');
    Window_Message.prototype.fittingHeight = function(numLines) {
        let height = _Window_Message_fittingHeight.apply(this, arguments);
        if (param_AlwaysLeaveRubyHeight) {
            height += this.baseRubyHeight() * numLines;
        }
        return height;
    };

    const _Window_Message_resetFontSettings = __base(Window_Message.prototype, 'resetFontSettings');
    Window_Message.prototype.resetFontSettings = function() {
        _Window_Message_resetFontSettings.apply(this, arguments);
        const rubyRgb = getDefaultParam('Ruby Color');
        const rubyColor = rubyRgb ? `rgb(${rubyRgb})` : getTextColor.call(this, 0);
        const outlineColor = `rgba(${getDefaultParam('Outline Color', '0, 0, 0, 0.5')})`;
        this.contents.fontBold = false;
        this.contents.fontItalic = false;
        this.contents.paintOpacity = 255;
        this._paintOpacity = 255;
        this.contents.outlineColor = outlineColor;
        this.contents.outlineWidth = getDefaultParam('Outline Width', 4);
        this._rubyBitmap.textColor = rubyColor;
        this._rubyBitmap.fontSize = param_RubyFontSize;
        this._rubyBitmap.outlineWidth = param_RubyOutlineWidth;
        this._rubyBitmap.outlineColor = outlineColor;
    };

    const _Window_Message_initMembers = Window_Message.prototype.initMembers;
    Window_Message.prototype.initMembers = function() {
        _Window_Message_initMembers.apply(this, arguments);
        this.clearMessageExFlags();
    };

    // overwrite mv
    Window_Message.prototype.windowHeight = function() {
        return this.fittingHeight(this.numVisibleRows());
    };

    const _Window_Message_clearFlags = Window_Message.prototype.clearFlags;
    Window_Message.prototype.clearFlags = function() {
        _Window_Message_clearFlags.apply(this, arguments);
        this.clearMessageExDefault();
        this._lastBottomY = 0;
        this._messageCount = 0;
    };

    Window_Message.prototype.clearMessageExFlags = function() {
        this._auto = false;
        this._effectSkip = $gameMessage.effectSkip();
        this._fadeOut = false;
        this._fadeOutType = $gameMessage.fadeOutType();
    };

    Window_Message.prototype.clearMessageExDefault = function() {
        this._speed = getDefaultParam('Text Speed', 60);
        this._animeType = getDefaultParam('Anime Type', 0);
    };

    Window_Message.prototype.numVisibleRows = function() {
        return this._messageRow;
    };

    Window_Message.prototype.getAnimationList = function() {
        return Window_Message.ANIMATIONS[this._animeType];
    };

    Window_Message.prototype.isCharacterAnimation = function() {
        return this.getAnimationList() && !this._showFast && !this._lineShowFast;
    };

    Window_Message.prototype.getTextCharacterSprite = function() {
        let sprite = this._characterSprites.find(sprite => !sprite.isPlaying());
        if (!sprite) {
            sprite = new Sprite_TextCharacter();
            sprite.setCallback(this.drawTextCharacter.bind(this));
            this._characterSprites.push(sprite);
        }
        if (this._characterContainer) {
            this._characterContainer.addChild(sprite);
        } else {
            this.addInnerChild(sprite);
        }
        return sprite;
    };

    Window_Message.prototype.drawTextCharacter = function(bitmap, rect) {
        this.contents.blt(bitmap, 0, 0, bitmap.width, bitmap.height, rect.x - 4, rect.y);
    };

    const _Window_Message_flushTextState = __base(Window_Base.prototype, 'flushTextState');
    Window_Message.prototype.flushTextState = function(textState) {
        if (textState.drawing && this.isCharacterAnimation()) {
            this.flushTextStateForAnimation(textState);
        } else {
            this.contents.paintOpacity = this._paintOpacity;
            _Window_Message_flushTextState.apply(this, arguments);
            this.contents.paintOpacity = 255;
        }
    };
    
    Window_Message.prototype.flushTextStateForAnimation = function(textState) {
        const list = this.getAnimationList();
        const text = textState.buffer;
        const rtl = textState.rtl;
        const { y, height } = textState;
        const width = this.textWidth(text);
        const x = rtl ? textState.x - width : textState.x;
        const bitmap = this.createCharacterBitmap(width + 8, height);
        bitmap.drawText(text, 4, 0, width + 4, height);

        const sprite = this.getTextCharacterSprite();
        const rect = new Rectangle(x, y, width, height);
        sprite.setup(bitmap, rect, list);
        textState.x += rtl ? -width : width;
        textState.buffer = this.createTextBuffer(rtl);

        const outputWidth = Math.abs(textState.x - textState.startX);
        if (textState.outputWidth < outputWidth) {
            textState.outputWidth = outputWidth;
        }
        textState.outputHeight = y - textState.startY + height;
    };
    
    const _Window_Message_convertEscapeCharacters = __base(Window_Message.prototype, 'convertEscapeCharacters');
    Window_Message.prototype.convertEscapeCharacters = function(text) {
        return _Window_Message_convertEscapeCharacters.apply(this, arguments)
            .replace(/\x1bA(?![NIT])/gi, () => {
                this._auto = true;
                return '';
            })
            .replace(/\x1bES/gi, () => {
                this._effectSkip = true;
                return '';
            });
    };

    const _Window_Message_update = Window_Message.prototype.update;
    Window_Message.prototype.update = function() {
        _Window_Message_update.apply(this, arguments);
        this.updateCharacterContainer();
    };

    Window_Message.prototype.updateCharacterContainer = function() {
        if (this._characterContainer) {
            const pad = this._padding;
            this._characterContainer.move(pad, pad);
        }
    };

    const _Window_Message_startMessage = Window_Message.prototype.startMessage;
    Window_Message.prototype.startMessage = function() {
        this.clearMessageExFlags();
        _Window_Message_startMessage.apply(this, arguments);
        if (this.contents.height !== this.contentsHeight()) {
            this.createContents();
        }
    };

    const _Window_Message_updatePlacement = Window_Message.prototype.updatePlacement;
    Window_Message.prototype.updatePlacement = function() {
        this._messageRow = $gameMessage.messageRow();
        this.height = this.windowHeight();
        _Window_Message_updatePlacement.apply(this, arguments);
    };
    
        const _Window_Message_terminateMessage = Window_Message.prototype.terminateMessage;
    Window_Message.prototype.terminateMessage = function() {
        if (!this.isOpen() || this._fadeOutType === 0) {
            this.terminateMessageEx();
        } else {
            this._fadeOut = true;
        }
    };
    
    Window_Message.prototype.terminateMessageEx = function() {
        _Window_Message_terminateMessage.call(this);
        if (this._effectSkip && param_SkipEffectsTiming === 'end of text') {
            $gameMessage.requestSceneEffectSkip();
        }
    };

    const _Window_Message_updateWait = Window_Message.prototype.updateWait;
    Window_Message.prototype.updateWait = function() {
        return (
            _Window_Message_updateWait.apply(this, arguments) ||
            this.updateFadeOut() ||
            this.updateWaitEffects()
        );
    };

    const _Window_Message_updateMessage = Window_Message.prototype.updateMessage;
    Window_Message.prototype.updateMessage = function() {
        if (this._textState) {
            this.updateShowFast();
            this._messageCount += this._speed;
            while (this.isContinueMessage()) {
                if (_Window_Message_updateMessage.apply(this, arguments)) {
                    this._messageCount = Math.max(this._messageCount - 60, 0);
                } else {
                    break;
                }
            }
            return true;
        }
        return this._characterSprites.some(sprite => sprite.isPlaying());
    };

    Window_Message.prototype.isContinueMessage = function() {
        return (this._messageCount >= 60 || this._speed === 0) && !this.isWaiting();
    };

    Window_Message.prototype.updateFadeOut = function() {
        if (this._fadeOut) {
            if (this.updateFadeOutMove()) {
                this._fadeOut = false;
                this.terminateMessageEx();
            }
            return true;
        }
        return false;
    };

    Window_Message.prototype.updateFadeOutMove = function() {
        switch (this._fadeOutType) {
            case 1:
                this.contentsOpacity -= 8;
                return (this.contentsOpacity === 0);
            case 2:
                this.contentsOpacity -= 3;
                return (this.contentsOpacity === 0);
            case 3:
                this.origin.y += 6;
                return (this.origin.y >= this._lastBottomY);
        }
        return true;
    };

    Window_Message.prototype.updateWaitEffects = function() {
        if (this._waitEffect) {
            if (param_Patch6) this.updateShowFast();
            this._waitEffect = this.isEffectingEx(param_WaitEffects);
            return true;
        }
        return false;
    };

    const _Window_Message_areSettingsChanged = Window_Message.prototype.areSettingsChanged;
    Window_Message.prototype.areSettingsChanged = function() {
        return (
            _Window_Message_areSettingsChanged.apply(this, arguments) ||
            this._messageRow !== $gameMessage.messageRow()
        );
    };

    const _Window_Message_updateShowFast = Window_Message.prototype.updateShowFast;
    Window_Message.prototype.updateShowFast = function() {
        const lastShowFast = this._showFast;
        if (!this._auto) {
            _Window_Message_updateShowFast.apply(this, arguments);
        }
        if (!lastShowFast && this._showFast) {
            if (this._effectSkip && param_SkipEffectsTiming === 'skip') {
                $gameMessage.requestSceneEffectSkip();
            }
            for (const item of this._characterSprites) {
                if (item.isPlaying()) item.draw();
            }
        }
    };

    const _Window_Message_newPage = Window_Message.prototype.newPage;
    Window_Message.prototype.newPage = function(textState) {
        for (const item of this._characterSprites) {
            if (item.isPlaying()) item.delete();
        }
        _Window_Message_newPage.apply(this, arguments);
        this.contentsOpacity = 255;
        this.origin.y = 0;
        textState.rubyHeight = this.realRubyHeight(textState);
        textState.y += textState.rubyHeight;
        this._lastBottomY = 0;
    };

    Window_Message.prototype.realRubyHeight = function(textState) {
        const line = textState.text.slice(textState.index).split('\n')[0];
        if (param_AlwaysLeaveRubyHeight || /\x1bRB\[.+?\]/i.test(line)) {
            return this.baseRubyHeight();
        }
        return 0;
    };

    Window_Message.prototype.baseRubyHeight = function() {
        return Math.max(param_RubyFontSize - param_RubyOy, 0);
    };
    
    const _Window_Message_processCharacter = __base(Window_Message.prototype, 'processCharacter');
    Window_Message.prototype.processCharacter = function(textState) {
        _Window_Message_processCharacter.apply(this, arguments);
        this._lastBottomY = textState.y + textState.height;
    };

    const _Window_Message_processNewLine = Window_Message.prototype.processNewLine;
    Window_Message.prototype.processNewLine = function(textState) {
        if (this.isEndOfText(textState) && textState.x === textState.left) {
            return;
        }
        _Window_Message_processNewLine.apply(this, arguments);
        textState.rubyHeight = this.realRubyHeight(textState);
        textState.y += textState.rubyHeight;
        if (this.needsNewPage(textState)) {
            this.startPause();
        }
    };

    const _Window_Message_processEscapeCharacter = Window_Message.prototype.processEscapeCharacter;
    Window_Message.prototype.processEscapeCharacter = function(code, textState) {
        switch (code) {
            case 'SP':
                this._speed = this.obtainEscapeParam(textState);
                break;
            case 'AT':
                this._animeType = this.obtainEscapeParam(textState);
                break;
            case 'CO':
                this.processGroupCharacter(textState, this.obtainEscapeTexts(textState));
                break;
            case 'RB':
                this.processRubyCharacter(textState, this.obtainEscapeTexts(textState));
                break;
            case 'MX':
                textState.x += this.obtainEscapeParam2(textState);
                break;
            case 'MY':
                textState.y += this.obtainEscapeParam2(textState);
                break;
            case 'PX':
                textState.x = this.obtainEscapeParam(textState);
                break;
            case 'PY':
                textState.y = this.obtainEscapeParam(textState);
                break;
            case 'SW':
                $gameSwitches.setValue(this.obtainEscapeParam(textState), true);
                break;
            case 'WE':
                this.waitForEffect();
                break;
            case 'C':
                this.contents.textColor = this.obtainEscapeColor(textState);
                break;
            case 'FS':
                this.contents.fontSize = this.obtainEscapeParam(textState);
                break;
            case 'FB':
                this.contents.fontBold = !this.contents.fontBold;
                break;
            case 'FI':
                this.contents.fontItalic = !this.contents.fontItalic;
                break;
            case 'OP':
                this._paintOpacity = this.obtainEscapeParam(textState);
                break;
            case 'OC': {
                const color = this.obtainEscapeColor(textState, 'rgba(0,0,0,0.5)');
                this.contents.outlineColor = color;
                this._rubyBitmap.outlineColor = color;
                break;
            }
            case 'OW':
                this.contents.outlineWidth = this.obtainEscapeParam(textState);
                break;
            case 'RC':
                const rubyRgb = getDefaultParam('Ruby Color');
                const rubyColor = rubyRgb ? `rgb(${rubyRgb})` : getTextColor.call(this, 0);
                this._rubyBitmap.textColor = this.obtainEscapeColor(textState, rubyColor);
                break;
            case 'DF':
                this.defaultTextInfo();
                break;
            case 'SV':
                this.saveTextInfo();
                break;
            case 'LD':
                this.loadTextInfo();
                break;
            case 'FO':
                this._fadeOutType = this.obtainEscapeParam(textState);
                break;
            default:
                _Window_Message_processEscapeCharacter.apply(this, arguments);
                break;
        }
    };

    Window_Message.prototype.waitForEffect = function() {
        if (this.isEffectingEx(param_WaitEffects)) {
            this._waitEffect = true;
            this._waitCount = 1;
        }
    };

    Window_Message.prototype.obtainEscapeParam2 = function(textState) {
        const arr = /^\[-?\d+\]/.exec(textState.text.slice(textState.index));
        if (arr) {
            textState.index += arr[0].length;
            return parseInt(arr[0].slice(1));
        }
        return '';
    };

    Window_Message.prototype.obtainEscapeTexts = function(textState) {
        const arr = /^\[(.+?)\]/.exec(textState.text.slice(textState.index));
        if (arr) {
            textState.index += arr[0].length;
            return arr[1].split(',');
        }
        return [];
    };

    Window_Message.prototype.obtainEscapeColor = function(textState, defaultColor) {
        const arr = /^\[([\d\s,\.]+)\]/.exec(textState.text.slice(textState.index));
        if (arr) {
            textState.index += arr[0].length;
            switch (arr[1].split(',').length) {
                case 1:
                    const index = +arr[1];
                    return index === 0
                        ? defaultColor || getTextColor.call(this, 0)
                        : getTextColor.call(this, index);
                case 3:
                    return `rgb(${arr[1]})`;
                case 4:
                    return `rgba(${arr[1]})`;
            }
        }
        return '';
    };

    Window_Message.prototype.defaultTextInfo = function() {
        this.clearMessageExDefault();
        this.resetFontSettings();
    };

    Window_Message.prototype.saveTextInfo = function() {
        this._textInfo[0] = this._speed;
        this._textInfo[1] = this._animeType;
        this._textInfo[2] = this.contents.textColor;
        this._textInfo[3] = this.contents.fontSize;
        this._textInfo[4] = this.contents.fontBold;
        this._textInfo[5] = this.contents.fontItalic;
        this._textInfo[6] = this._paintOpacity;
        this._textInfo[7] = this.contents.outlineColor;
        this._textInfo[8] = this.contents.outlineWidth;
        this._textInfo[9] = this._rubyBitmap.textColor;
        this._textInfo[10] = this._rubyBitmap.outlineColor;
    };

    Window_Message.prototype.loadTextInfo = function() {
        if (this._textInfo.length > 0) {
            this._speed = this._textInfo[0];
            this._animeType = this._textInfo[1];
            this.contents.textColor = this._textInfo[2];
            this.contents.fontSize = this._textInfo[3];
            this.contents.fontBold = this._textInfo[4];
            this.contents.fontItalic = this._textInfo[5];
            this._paintOpacity = this._textInfo[6];
            this.contents.outlineColor = this._textInfo[7];
            this.contents.outlineWidth = this._textInfo[8];
            this._rubyBitmap.textColor = this._textInfo[9];
            this._rubyBitmap.outlineColor = this._textInfo[10];
        }
    };

    Window_Message.prototype.isEffectingEx = function(info) {
        return  (
            (info['Scroll Map']         && $gameMap.isScrolling()) ||
            (info['Set Movement Route'] && $gameMap.isAnyMoveRouteForcingNr()) ||
            (info['Show Animation']     && $gameMap.isAnyAnimationPlaying()) ||
            (info['Show Balloon Icon']  && $gameMap.isAnyBalloonPlaying()) ||
            (info['Move Picture']       && $gameScreen.isAnyPictureMoving()) ||
            (info['Tint Picture']       && $gameScreen.isAnyPictureTinting()) ||
            (info['Tint Screen']        && $gameScreen.isTinting()) ||
            (info['Flash Screen']       && $gameScreen.isFlashing()) ||
            (info['Shake Screen']       && $gameScreen.isShaking()) ||
            (info['Set Weather Effect'] && $gameScreen.isWeatherChanging()) ||
            (info['Fadeout BGM']        && AudioManager.isBgmFadeOuting()) ||
            (info['Fadeout BGS']        && AudioManager.isBgsFadeOuting()) ||
            (info['Play ME']            && AudioManager.isMePlaying())
        );
    };

    const _Window_Message_processNormalCharacter = __base(Window_Message.prototype, 'processNormalCharacter');
    Window_Message.prototype.processNormalCharacter = function(textState) {
        if (this.isCharacterAnimation()) {
            this.processNormalCharacterForAnimation(textState);
        } else {
            this.contents.paintOpacity = this._paintOpacity;
            _Window_Message_processNormalCharacter.apply(this, arguments);
            this.contents.paintOpacity = 255;
        }
    };

    Window_Message.prototype.processNormalCharacterForAnimation = function(textState) {
        const list = this.getAnimationList();
        const c = textState.text[textState.index++];
        const { x, y, height } = textState;
        const width = this.textWidth(c);
        const bitmap = this.createCharacterBitmap(width + 8, height);
        bitmap.drawText(c, 4, 0, width + 4, height);

        const sprite = this.getTextCharacterSprite();
        const rect = new Rectangle(x, y, width, height);
        sprite.setup(bitmap, rect, list);
        textState.x += width;
    };

    // overwrite
    Window_Message.prototype.processDrawIcon = function(iconIndex, textState) {
        const pw = Window_Base._iconWidth || ImageManager.iconWidth;
        const ph = Window_Base._iconHeight || ImageManager.iconHeight;
        if (Utils.RPGMAKER_NAME === 'MV' || textState.drawing) {
            if (this.isCharacterAnimation()) {
                this.processDrawIconForAnimation(iconIndex, textState);
            } else {
                const offsetY = Math.floor(textState.height - ph) / 2;
                this.contents.paintOpacity = this._paintOpacity;
                this.drawIcon(iconIndex, textState.x + 2, textState.y + offsetY);
                this.contents.paintOpacity = 255;
            }
        }
        textState.x += pw + 4;
    };

    Window_Message.prototype.processDrawIconForAnimation = function(iconIndex, textState) {
        const pw = Window_Base._iconWidth || ImageManager.iconWidth;
        const ph = Window_Base._iconHeight || ImageManager.iconHeight;
        const { x, y, height } = textState;
        const offsetY = Math.floor(height - ph) / 2;
        const list = this.getAnimationList();
        const bitmap = this.createCharacterBitmap(pw + 12, height);
        const iconSet = ImageManager.loadSystem('IconSet');
        const sx = iconIndex % 16 * pw;
        const sy = Math.floor(iconIndex / 16) * ph;
        bitmap.blt(iconSet, sx, sy, pw, ph, 6, offsetY);

        const sprite = this.getTextCharacterSprite();
        const rect = new Rectangle(x, y, pw + 4, height);
        sprite.setup(bitmap, rect, list);
    };

    Window_Message.prototype.processGroupCharacter = function(textState, texts) {
        const c = texts[0];
        const width = this.textWidth(c);
        if (Utils.RPGMAKER_NAME === 'MV' || textState.drawing) {
            if (this.isCharacterAnimation()) {
                this.processGroupCharacterForAnimation(textState, texts);
            } else {
                this.contents.paintOpacity = this._paintOpacity;
                this.contents.drawText(c, textState.x, textState.y, w * 2, w);
                this.contents.paintOpacity = 255;
            }
        }
        textState.x += width;
    };

    Window_Message.prototype.processGroupCharacterForAnimation = function(textState, texts) {
        const c = texts[0];
        const width = this.textWidth(c);
        const { x, y, height } = textState;
        const list = this.getAnimationList();
        const bitmap = this.createCharacterBitmap(width + 8, height);
        bitmap.drawText(c, 4, 0, width + 4, height);

        const sprite = this.getTextCharacterSprite();
        const rect = new Rectangle(x, y, width, height);
        sprite.setup(bitmap, rect, list);
    };

    Window_Message.prototype.processRubyCharacter = function(textState, texts) {
        const [ c, r ] = texts;
        const { x, y, height } = textState;
        const cw = this.textWidth(c);
        const rubyBitmap = this._rubyBitmap;
        rubyBitmap.fontBold = this.contents.fontBold;
        rubyBitmap.fontItalic = this.contents.fontItalic;
        const rw = rubyBitmap.measureTextWidth(r);
        const rh = textState.rubyHeight;
        const rs = rubyBitmap.fontSize;
        const width = Math.max(cw, rw);
        if (Utils.RPGMAKER_NAME === 'MV' || textState.drawing) {
            rubyBitmap.clear();
            rubyBitmap.resize(rw + 16, rs + 8);
            rubyBitmap.drawText(r, 4, 0, rw + 4, rs + 8);
            if (this.isCharacterAnimation()) {
                const list = this.getAnimationList();
                const bitmap = this.createCharacterBitmap(width + 8, height + rh);
                const cx = (width - cw) / 2;
                const rx = (width - rw) / 2;
                const ry = -4;
                bitmap.drawText(c, cx + 4, rh, width + 4, height);
                bitmap.blt(rubyBitmap, 0, 0, rw + 8, rs + 8, rx, ry);

                const sprite = this.getTextCharacterSprite();
                const rect = new Rectangle(x, y - rh, width, height);
                sprite.setup(bitmap, rect, list);
            } else {
                const cx = x + (width - cw) / 2;
                const rx = x + (width - rw) / 2;
                const ry = y - rh - 4;
                this.contents.paintOpacity = this._paintOpacity;
                this.contents.drawText(c, cx, y, cw + 4, height);
                this.contents.blt(rubyBitmap, 0, 0, rw + 8, rs + 8, rx - 4, ry);
                this.contents.paintOpacity = 255;
            }
        }
        textState.x += width;
    };

    Window_Message.prototype.createCharacterBitmap = function(width, height) {
        const bitmap = new Bitmap(width, height);
        bitmap.fontFace = this.contents.fontFace;
        bitmap.fontSize = this.contents.fontSize;
        bitmap.fontBold = this.contents.fontBold;
        bitmap.fontItalic = this.contents.fontItalic;
        bitmap.textColor = this.contents.textColor;
        bitmap.paintOpacity = this._paintOpacity;
        bitmap.outlineColor = this.contents.outlineColor;
        bitmap.outlineWidth = this.contents.outlineWidth;
        return bitmap;
    };

    const _Window_Message_calcTextHeight = Window_Message.prototype.calcTextHeight;
    Window_Message.prototype.calcTextHeight = function(textState, all) {
        if (Utils.RPGMAKER_NAME === 'MZ') {
            return _Window_Message_calcTextHeight.apply(this, arguments);
        }
        const lines = textState.text.slice(textState.index).split('\n');
        const regExp = /\x1bFS\[(\d+)\]/gi;
        let maxFontSize = this.contents.fontSize;
        for (;;) {
            const arr = regExp.exec(lines[0]);
            if (arr) {
                maxFontSize = Math.max(maxFontSize, +arr[1]);
            } else {
                break;
            }
        }
        const height = _Window_Message_calcTextHeight.apply(this, arguments);
        return Math.max(height, maxFontSize + 8);
    };

    if (!Window_Message.prototype.isWaiting) {
        Window_Message.prototype.isWaiting = function() {
            return this.pause || this._waitCount > 0;
        };
    }
    
    const _Window_Message_isWaiting = Window_Message.prototype.isWaiting;
    Window_Message.prototype.isWaiting = function() {
        return _Window_Message_isWaiting.apply(this, arguments) || this._waitEffect;
    };
    
    //-------------------------------------------------------------------------
    // Scene_Map

    const _Scene_Map_updateMainMultiply = Scene_Map.prototype.updateMainMultiply;
    Scene_Map.prototype.updateMainMultiply = function() {
        _Scene_Map_updateMainMultiply.apply(this, arguments);
        if ($gameMessage.sceneEffectSkip()) {
            for (let i = 1; i < 99; i++) {
                if (this._messageWindow.isEffectingEx(param_SkipEffects)) {
                    this.updateMain();
                } else {
                    $gameMessage.clearSceneEffectSkip();
                    break;
                }
            }
        }
    };

})();
