//=============================================================================
// KMS_MapActiveMessage.js
//   Last update: 2019/08/31
//=============================================================================

/*:
 * @plugindesc
 * [v0.2.0] Show messages automatically for events on map.
 * 
 * @author Kameo (Kamesoft)
 *
 * @param Balloon offset Y
 * @default 20
 * @desc Vertical position offset for balloons by pixel.
 *
 * @param Balloon margin
 * @default -8
 * @desc Adjust frame size of balloons by pixel.
 *
 * @param Default range
 * @default 4
 * @desc Default distance for displaying message by tile unit.
 *
 * @param Display duration
 * @default 300
 * @desc Display frame count.
 *
 * @param Max message count
 * @default 10
 * @desc Max number of active messages.
 *
 * @param Message skin
 * @default ActiveMessageSkin
 * @require 1
 * @dir img/system/
 * @type file
 * @desc Message skin for active message. Load from img/system.
 *
 *
 * @help
 *
 * ## Plugin command
 *
 * MapActiveMessage enable      # Enable active message
 * MapActiveMessage disable     # Display active message
 * MapActiveMessage show 10     # Show message of the event whose ID is 10
 * MapActiveMessage show        # Show messages of all events
 *
 */

/*:ja
 * @plugindesc
 * [v0.2.0] プレイヤーが近付いたときに、自動的にメッセージを表示するイベントを作成します。
 * 
 * @author かめお (Kamesoft)
 *
 * @param Balloon offset Y
 * @default 20
 * @desc 吹き出しの縦方向の位置をピクセル単位で調整します。
 *
 * @param Balloon margin
 * @default -8
 * @desc 吹き出しの枠サイズをピクセル単位で調整します。
 *
 * @param Default range
 * @default 4
 * @desc メッセージを表示する距離をタイル単位で指定します。
 *
 * @param Display duration
 * @default 300
 * @desc メッセージ表示時間をフレーム単位で指定します。
 *
 * @param Max message count
 * @default 10
 * @desc 表示できるメッセージの最大数です。
 *
 * @param Message skin
 * @default ActiveMessageSkin
 * @require 1
 * @dir img/system/
 * @type file
 * @desc メッセージの表示に使用するスキン画像です。 img/system から読み込みます。
 *
 *
 * @help
 *
 * ■ プラグインコマンド
 *
 * MapActiveMessage enable      # アクティブメッセージを有効にします。
 * MapActiveMessage disable     # アクティブメッセージを無効にします。
 * MapActiveMessage show 10     # イベント ID:10 のメッセージを表示します。
 * MapActiveMessage show        # 全イベントのメッセージを表示します。
 *
 */

var KMS = KMS || {};

(function () {

    'use strict';

    // 定数
    var Const =
    {
        debug: false,              // デバッグモード
        pluginCode: 'MapActiveMessage', // プラグインコード
        regex: {
            activeMessage: /<(?:アクティブメッセージ|ActiveMessage)\s*[:\s]\s*([^>]+)>/i,
            displayRange: /<(?:アクティブメッセージ距離|ActiveMessageRange)\s*[:\s]\s*(\d+)>/i,
            displayDuration: /<(?:アクティブメッセージ表示時間|ActiveMessageDuration)\s*[:\s]\s*(\d+)>/i,
            beginMessage: /<(?:アクティブメッセージ開始|BeginActiveMessage)>/i, // * 未使用
            endMessage: /<(?:アクティブメッセージ終了|EndActiveMessage)>/i    // * 未使用
        },

        balloonFrameInfo: { margin: 4, size: 24, lineLength: 48, srcSize: 96 },
        balloonVertexSize: { width: 24, height: 48 },

        // 吹き出しの表示位置
        balloonPosition: { below: 0, above: 1 }
    };

    var PluginName = 'KMS_' + Const.pluginCode;

    KMS.imported = KMS.imported || {};
    KMS.imported[Const.pluginCode] = true;

    function isNullOrUndefined(value) {
        return value === null || value === undefined;
    }

    // デフォルト値つきで文字列から int を解析
    function parseIntWithDefault(param, defaultValue) {
        var value = parseInt(param);
        return isNaN(value) ? defaultValue : value;
    }

    var pluginParams = PluginManager.parameters(PluginName);
    var Params = {};
    Params.messageSkin = pluginParams['Message skin'] || 'ActiveMessageSkin';
    Params.messageCountMax = Math.max(parseIntWithDefault(pluginParams['Max message count'], 8), 1);
    Params.balloonOffsetY = parseIntWithDefault(pluginParams['Balloon offset Y'], 20);
    Params.balloonMargin = parseIntWithDefault(pluginParams['Balloon margin'], -8);
    Params.defaultRange = Math.max(parseIntWithDefault(pluginParams['Default range'], 4), 1);
    Params.displayDuration = Math.max(parseIntWithDefault(pluginParams['Display duration'], 300), 1);
    Params.fadeFrameCount = Math.max(parseIntWithDefault(pluginParams['Fade frame count'], 8), 1);
    Params.fontSize = Math.max(parseIntWithDefault(pluginParams['Font size'], 18), 1);

    // デバッグログ
    var debuglog;
    if (Const.debug) {
        debuglog = function () { console.log(arguments); };
    }
    else {
        debuglog = function () { };
    }

    var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function (command, args) {
        _Game_Interpreter_pluginCommand.call(this, command, args);

        if (command !== Const.pluginCode) {
            return;
        }

        switch (args[0]) {
            case 'enable':      // 有効化
                $gameSystem.setMapActiveMessageEnabled(true);
                break;

            case 'disable':     // 無効化
                $gameSystem.setMapActiveMessageEnabled(false);
                break;

            case 'show':        // 強制表示 (eventId)
                // eventId 省略時 (undefined) は全イベントが対象
                $gameMap.forceDisplayActiveMessage(parseInt(args[1]));
                break;

            default:
                // 不明なコマンド
                console.error('[%s %s] Unknown command.', Const.pluginCode, args[0]);
                break;
        }
    };

    // タイリング描画
    function tileBlt(dstImage, srcImage, sx, sy, sw, sh, dx, dy, dw, dh) {
        for (var offY = 0; offY < dh; offY += sh) {
            for (var offX = 0; offX < dw; offX += sw) {
                var tempSh = Math.min(sh, dh - offY);
                var tempSw = Math.min(sw, dw - offX);
                dstImage.blt(
                    srcImage,
                    sx, sy, tempSw, tempSh,
                    dx + offX, dy + offY, tempSw, tempSh
                );
            }
        }
    }


    //-----------------------------------------------------------------------------
    // Game_Temp

    var _Game_Temp_initialize = Game_Temp.prototype.initialize;
    Game_Temp.prototype.initialize = function () {
        _Game_Temp_initialize.call(this);

        this.clearMapActiveMessage();
    };

    /**
     * アクティブメッセージの登録
     */
    Game_Temp.prototype.pushMapActiveMessage = function (text, targetEvent, isForced) {
        var message = { text: text, event: targetEvent, isForced: !!isForced };
        this._mapActiveMessages.push(message);
    };

    /**
     * アクティブメッセージのクリア
     */
    Game_Temp.prototype.clearMapActiveMessage = function () {
        this._mapActiveMessages = [];
    };

    /**
     * 次のアクティブメッセージを取得
     */
    Game_Temp.prototype.popNextMapActiveMessage = function () {
        return this._mapActiveMessages.shift();
    };

    /**
     * アクティブメッセージが登録されているか
     */
    Game_Temp.prototype.isMapActiveMessageReady = function () {
        return this._mapActiveMessages.length > 0;
    };


    //-----------------------------------------------------------------------------
    // Game_System

    /**
     * アクティブメッセージの有効状態を取得
     */
    Game_System.prototype.isMapActiveMessageEnabled = function () {
        return !isNullOrUndefined(this._mapActiveMessageEnabled) ?
            this._mapActiveMessageEnabled :
            true;
    };

    /**
     * アクティブメッセージの有効状態を設定
     */
    Game_System.prototype.setMapActiveMessageEnabled = function (enabled) {
        this._mapActiveMessageEnabled = !!enabled;
    };



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

    var _Game_Map_setup = Game_Map.prototype.setup;
    Game_Map.prototype.setup = function (mapId) {
        _Game_Map_setup.call(this, mapId);

        this.initializeActiveMessage();
    };

    /**
     * アクティブメッセージの初期化
     */
    Game_Map.prototype.initializeActiveMessage = function () {
        // 最初は全イベントの表示済みフラグを解除
        this.clearActiveMessageShownFlags();
    };

    /**
     * アクティブメッセージの表示済みフラグを解除
     */
    Game_Map.prototype.clearActiveMessageShownFlags = function () {
        this.events().forEach(function (event) {
            event.setMapActiveMessageShown(false);
        });

        this.requestUpdateActiveMessage();
    };

    /**
     * アクティブメッセージの更新要求
     */
    Game_Map.prototype.requestUpdateActiveMessage = function () {
        this._needsUpdateActiveMessage = true;
    };

    var _Game_Map_update = Game_Map.prototype.update;
    Game_Map.prototype.update = function (sceneActive) {
        _Game_Map_update.call(this, sceneActive);

        this.updateDisplayActiveMessage();
    };

    /**
     * アクティブメッセージの表示判定
     */
    Game_Map.prototype.updateDisplayActiveMessage = function () {
        if (!this._needsUpdateActiveMessage) {
            return;
        }

        this._needsUpdateActiveMessage = false;

        // アクティブメッセージの登録
        var events = $gamePlayer.findAvailableMapActiveMessageEvents();
        events.forEach(function (event) {
            $gameTemp.pushMapActiveMessage(event.getMapActiveMessage(), event);
        });
    };

    /**
     * アクティブメッセージの強制表示
     */
    Game_Map.prototype.forceDisplayActiveMessage = function (eventId) {
        var invoker = function (event) {
            $gameTemp.pushMapActiveMessage(event.getMapActiveMessage(), event, true);
        };

        if (isNullOrUndefined(eventId) || isNaN(eventId)) {
            // 全表示
            this.events().forEach(invoker);
        }
        else {
            // イベント ID 指定
            this.events().filter(function (event) {
                return event.eventId() === eventId;
            }).forEach(invoker);
        }
    };

    var _Game_Map_refresh = Game_Map.prototype.refresh;
    Game_Map.prototype.refresh = function () {
        _Game_Map_refresh.call(this);

        this.requestUpdateActiveMessage();
    };


    //-----------------------------------------------------------------------------
    // Game_CharacterBase

    var _Game_CharacterBase = Game_CharacterBase.prototype.setDirection;
    Game_CharacterBase.prototype.setDirection = function (d) {
        _Game_CharacterBase.call(this, d);

        // 移動時には方向指定があるはずなので、ここでメッセージの更新要求を出す
        if (!isNullOrUndefined($gameMap)) {
            $gameMap.requestUpdateActiveMessage();
        }
    };


    //-----------------------------------------------------------------------------
    // Game_Player

    /**
     * アクティブメッセージを表示可能なイベントを探す
     */
    Game_Player.prototype.findAvailableMapActiveMessageEvents = function () {
        if (!this.canMove()) {
            return [];
        }

        // 指定距離内でメッセージ未表示のイベントを列挙
        var candidateEvents = $gameMap.events().filter(function (event) {
            return event.hasMapActiveMessage() &&
                !event.isMapActiveMessageShown() &&
                this.calcDistanceForMapActiveMessage(event) <= event.getMapActiveMessageRange();
        }, this);

        // 表示済みフラグの解除判定
        $gameMap.events().forEach(function (event) {
            var distance = this.calcDistanceForMapActiveMessage(event);
            if (event.isMapActiveMessageShown()) {
                // メッセージ表示距離から出ていれば表示済みフラグ解除
                if (distance > event.getMapActiveMessageRange()) {
                    event.setMapActiveMessageShown(false);
                }
            }
        }, this);

        return candidateEvents;
    };

    Game_Player.prototype.calcDistanceForMapActiveMessage = function (event) {
        var diffX = this.x - event.x;
        var diffY = this.y - event.y;

        return Math.sqrt(diffX * diffX + diffY * diffY);
    };


    //-----------------------------------------------------------------------------
    // Game_Event

    var _Game_Event_setupPage = Game_Event.prototype.setupPage;
    Game_Event.prototype.setupPage = function () {
        _Game_Event_setupPage.call(this);

        this.setupMapActiveMessage();
    };

    /**
     * 現在のページ番号を取得
     */
    Game_Event.prototype.pageIndex = function () {
        return this._pageIndex;
    };

    /**
     * アクティブメッセージを構築
     */
    Game_Event.prototype.setupMapActiveMessage = function () {
        this._mapActiveMessage = null;
        this._mapActiveMessageRange = Params.defaultRange;
        this._mapActiveMessageDuration = Params.displayDuration;
        this._isMapActiveMessageShown = false;

        // 注釈以外に達するまで解析
        var page = this.page();
        if (isNullOrUndefined(page)) {
            return;
        }

        var isComment = function (command) {
            return command && (command.code === 108 || command.code === 408);
        };

        // 複数行対応のために注釈を結合
        var index = 0;
        var command = page.list[index];
        var comment = '';
        while (isComment(command)) {
            if (comment.length > 0) {
                comment += '\n';
            }
            comment += command.parameters[0];
            command = page.list[++index];
        }

        // メッセージ定義を解析
        var messageMatch = Const.regex.activeMessage.exec(comment);
        if (!isNullOrUndefined(messageMatch)) {
            this._mapActiveMessage = messageMatch[1];
        }

        // メッセージ距離を解析
        var rangeMatch = Const.regex.displayRange.exec(comment);
        if (!isNullOrUndefined(rangeMatch)) {
            this._mapActiveMessageRange =
                Math.max(parseIntWithDefault(rangeMatch[1], Params.defaultRange), 1);
        }

        // メッセージ表示時間を解析
        var durationMatch = Const.regex.displayDuration.exec(comment);
        if (!isNullOrUndefined(durationMatch)) {
            this._mapActiveMessageDuration =
                parseIntWithDefault(durationMatch[1], Params.displayDuration);
        }
    };

    /**
     * アクティブメッセージを取得
     *
     * @return {String} メッセージ
     */
    Game_Event.prototype.getMapActiveMessage = function () {
        return this._mapActiveMessage;
    };

    /**
     * アクティブメッセージが存在するか
     *
     * @return {Boolean} true if exists
     */
    Game_Event.prototype.hasMapActiveMessage = function () {
        return !isNullOrUndefined(this._mapActiveMessage);
    };

    /**
     * アクティブメッセージを表示可能な範囲
     *
     * @return {Number} タイル単位の表示範囲半径
     */
    Game_Event.prototype.getMapActiveMessageRange = function () {
        return this._mapActiveMessageRange;
    };

    /**
     * アクティブメッセージを表示するフレーム数
     *
     * @return {Number} 自動消去するまでのフレーム数
     */
    Game_Event.prototype.getMapActiveMessageDuration = function () {
        return this._mapActiveMessageDuration;
    };

    /**
     * アクティブメッセージを表示済みか
     *
     * @return {Boolean} true if shown
     */
    Game_Event.prototype.isMapActiveMessageShown = function () {
        return this._isMapActiveMessageShown;
    };

    /**
     * アクティブメッセージ表示済みかどうかを設定
     *
     * @param {Boolean} shown 設定する値
     */
    Game_Event.prototype.setMapActiveMessageShown = function (shown) {
        this._isMapActiveMessageShown = !!shown;
    };


    //-----------------------------------------------------------------------------
    // Sprite_MapActiveMessageBalloon
    //
    // アクティブメッセージの吹き出し用スプライト

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

    Sprite_MapActiveMessageBalloon.prototype = Object.create(Sprite_Base.prototype);
    Sprite_MapActiveMessageBalloon.prototype.constructor = Sprite_MapActiveMessageBalloon;

    Sprite_MapActiveMessageBalloon.prototype.initialize = function () {
        Sprite_Base.prototype.initialize.call(this);

        this.anchor.y = 0.5;
        this.initMembers();
    };

    Sprite_MapActiveMessageBalloon.prototype.initMembers = function () {
        this._owner = null;
    };

    /**
     * オーナーウィンドウの設定
     */
    Sprite_MapActiveMessageBalloon.prototype.setOwner = function (owner) {
        this._owner = owner;
    };

    /**
     * 位置をオーナーウィンドウに合わせる
     */
    Sprite_MapActiveMessageBalloon.prototype.followOwner = function () {
        this.y = this._owner.height / 2;
    };

    /**
     * サイズをオーナーウィンドウに合わせる
     */
    Sprite_MapActiveMessageBalloon.prototype.fitToOwner = function () {
        if (isNullOrUndefined(this._owner)) {
            return;
        }

        var frameSize = Const.balloonFrameInfo.size;
        var vertexHeight = Const.balloonVertexSize.height;
        var heightOffset = (vertexHeight - frameSize) * 2;

        // Bitmap のサイズ調整
        var bitmapWidth = this._owner.width;
        var bitmapHeight = this._owner.height + heightOffset;
        if (isNullOrUndefined(this.bitmap) ||
            (this.width != bitmapWidth || this.height != bitmapHeight)) {
            this.bitmap = new Bitmap(bitmapWidth, bitmapHeight);
        }
        else {
            this.bitmap.clear();
        }

        if (Const.debug) {
            this.bitmap.fillRect(0, 0, bitmapWidth, bitmapHeight, 'rgba(160, 255, 160, 0.5)');
        }
    };

    /**
     * 吹き出しの再描画
     */
    Sprite_MapActiveMessageBalloon.prototype.refresh = function () {
        if (isNullOrUndefined(this._owner)) {
            return;
        }

        // サイズの調整
        this.fitToOwner();

        // 吹き出しの描画
        var frameSize = Const.balloonFrameInfo.size;
        var vertexHeight = Const.balloonVertexSize.height;
        var offsetX = -Params.balloonMargin;
        var offsetY = vertexHeight - frameSize - Params.balloonMargin;
        this.drawBalloon(
            offsetX,
            offsetY,
            this.width - offsetX * 2,
            this.height - offsetY * 2
        );
    };

    /**
     * 吹き出しを描画
     */
    Sprite_MapActiveMessageBalloon.prototype.drawBalloon = function (x, y, width, height) {
        if (isNullOrUndefined(this._owner)) {
            return;
        }

        this.drawBalloonBackground(x, y, width, height);
        this.drawBalloonFrame(x, y, width, height);
    };

    /**
     * 吹き出しの背景を描画
     */
    Sprite_MapActiveMessageBalloon.prototype.drawBalloonBackground = function (x, y, width, height) {
        var srcImage = this._owner.getWindowSkin();
        var frame = Const.balloonFrameInfo;

        var prevOpacity = this.bitmap.paintOpacity;
        this.bitmap.paintOpacity = this._owner.backOpacity;

        // 背景下層
        this.bitmap.blt(
            srcImage,
            0, 0, frame.srcSize, frame.srcSize,
            x + frame.margin, y + frame.margin, width - frame.margin * 2, height - frame.margin * 2
        );

        // 背景上層
        tileBlt(
            this.bitmap, srcImage,
            0, frame.srcSize, frame.srcSize, frame.srcSize,
            x + frame.margin, y + frame.margin, width - frame.margin * 2, height - frame.margin * 2
        );

        this.bitmap.paintOpacity = prevOpacity;
    };

    /**
     * 吹き出しの枠を描画
     */
    Sprite_MapActiveMessageBalloon.prototype.drawBalloonFrame = function (x, y, width, height) {
        var srcImage = this._owner.getWindowSkin();
        var vertexOffset = this._owner.getBalloonVertexX();
        var balloonPos = this._owner.getBalloonPosition();

        var frame = Const.balloonFrameInfo;
        var vertexSize = Const.balloonVertexSize;

        // 吹き出しの頂点は枠内に収める
        vertexOffset = Math.min(
            Math.max(vertexOffset, 0),
            width - (frame.size * 2 + vertexSize.width)
        );

        // 縦枠 + コーナーを描画
        function drawFrameV(sx, dx, dy) {
            this.bitmap.blt(
                srcImage,
                sx, 0, frame.size, frame.size,
                dx, dy, frame.size, frame.size
            );
            this.bitmap.blt(
                srcImage,
                sx, frame.size, frame.size, frame.lineLength,
                dx, dy + frame.size, frame.size, height - frame.size * 2
            );
            this.bitmap.blt(
                srcImage,
                sx, frame.size + frame.lineLength, frame.size, frame.size,
                dx, dy + height - frame.size, frame.size, frame.size
            );
        }

        // 横枠と頂点を描画
        function drawFrameH(sy, dx, dy, showVertex) {
            var baseSx = frame.srcSize + frame.size;
            var frameWidth = width - frame.size * 2;
            var leftWidth = showVertex ? vertexOffset : frameWidth;

            // 枠線の左側 (頂点がない場合は全体)
            this.bitmap.blt(
                srcImage,
                baseSx, sy, frame.lineLength, frame.size,
                dx, dy, leftWidth, frame.size
            );

            if (!showVertex) {
                // 頂点を描画しない
                return;
            }

            // sy > 0 なら下枠、sy === 0 なら上枠のはず
            var newDx = dx + leftWidth;
            var newDy = dy + (sy > 0 ? 0 : -(vertexSize.height - frame.size));

            // 頂点
            var vertexSx = frame.srcSize + (sy > 0 ? 0 : vertexSize.width);
            this.bitmap.blt(
                srcImage,
                vertexSx, frame.srcSize, vertexSize.width, vertexSize.height,
                newDx, newDy, vertexSize.width, vertexSize.height
            );

            if (Const.debug) {
                this.bitmap.fillRect(newDx, newDy, vertexSize.width, vertexSize.height, 'rgba(255, 160, 160, 0.5)');
            }

            // 枠線の右側
            var rightWidth = frameWidth - (leftWidth + vertexSize.width);
            if (rightWidth > 0) {
                newDx += vertexSize.width;
                this.bitmap.blt(
                    srcImage,
                    baseSx, sy, frame.lineLength, frame.size,
                    newDx, dy, rightWidth, frame.size
                );
            }
        }

        // 左枠
        drawFrameV.call(this, frame.srcSize, x, y);

        // 右枠
        drawFrameV.call(this, frame.srcSize + frame.size + frame.lineLength, x + width - frame.size, y);

        // 上枠 + 頂点
        drawFrameH.call(
            this,
            0, x + frame.size, y,
            balloonPos === Const.balloonPosition.below
        );

        // 下枠 + 頂点
        drawFrameH.call(
            this,
            frame.size + frame.lineLength, x + frame.size, y + height - frame.size,
            balloonPos === Const.balloonPosition.above
        );
    };


    //-----------------------------------------------------------------------------
    // Sprite_Character

    /**
     * 同一のイベントか判定
     */
    Sprite_Character.prototype.isSameEvent = function (event) {
        if (isNullOrUndefined(this._character) ||
            isNullOrUndefined(event) ||
            !(this._character instanceof Game_Event)) {
            return false;
        }

        return this._character.eventId() === event.eventId();
    };


    //-----------------------------------------------------------------------------
    // Spriteset_Map

    /**
     * 指定されたイベントに対応するキャラクタースプライトを取得
     */
    Spriteset_Map.prototype.findCharacterSpriteByEvent = function (event) {
        var sameSprites = this._characterSprites.filter(function (sprite) {
            return sprite.isSameEvent(event);
        });

        if (sameSprites.length > 0) {
            return sameSprites[0];
        }
        else {
            return null;
        }
    };


    //-----------------------------------------------------------------------------
    // Window_MapActiveMessage
    //
    // マップ上でアクティブメッセージを表示するウィンドウ
    // (スプライトでも良いが、メッセージ周りはウィンドウの方が扱いやすい)

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

    Window_MapActiveMessage.prototype = Object.create(Window_Base.prototype);
    Window_MapActiveMessage.prototype.constructor = Window_MapActiveMessage;

    // メッセージの一時描画バッファ
    Window_MapActiveMessage.tempMessageBuffer = null;

    /**
     * アクティブメッセージ用スキンの事前読み込み
     */
    Window_MapActiveMessage.preloadWindowskin = function () {
        ImageManager.loadSystem(Params.messageSkin);
    };

    Window_MapActiveMessage.prototype.initialize = function () {
        // コンテンツサイズは内容に応じて可変なので、ウィンドウサイズは仮
        Window_Base.prototype.initialize.call(this, 0, 0, 64, 64);

        if (isNullOrUndefined(Window_MapActiveMessage.tempMessageBuffer)) {
            Window_MapActiveMessage.tempMessageBuffer =
                new Bitmap(Graphics.boxWidth, Graphics.boxHeight / 2);
        }

        this._balloonSprite = null;

        this.opacity = 0;
        this.contentsOpacity = 0;
        this.initMembers();
    };

    Window_MapActiveMessage.prototype.standardFontSize = function () {
        return Params.fontSize;
    };

    Window_MapActiveMessage.prototype.loadWindowskin = function () {
        // デフォルトのスキンの代わりに、アクティブメッセージ用のスキンを読み込む
        this._windowskin = ImageManager.loadSystem(Params.messageSkin);
    };

    /**
     * ウィンドウスキンを取得
     */
    Window_MapActiveMessage.prototype.getWindowSkin = function () {
        return this._windowskin;
    };

    Window_MapActiveMessage.prototype.initMembers = function () {
        this._textState = null;
        this._event = null;
        this._characterSprite = null;
        this._targetPageIndex = 0;
        this._duration = 0;
        this._isForceDisplay = false;
        this._balloonPosition = Const.balloonPosition.above;
    };

    Window_MapActiveMessage.prototype.show = function () {
        Window_Base.prototype.show.call(this);

        if (!isNullOrUndefined(this._balloonSprite)) {
            this._balloonSprite.show();
        }
    };

    Window_MapActiveMessage.prototype.hide = function () {
        this._duration = 0;

        Window_Base.prototype.hide.call(this);

        if (!isNullOrUndefined(this._balloonSprite)) {
            this._balloonSprite.hide();
        }
    };

    /**
     * フェードアウト
     */
    Window_MapActiveMessage.prototype.fadeOut = function () {
        if (this.isFadingOut()) {
            return;
        }

        if (this.isFadingIn()) {
            // フェードイン中は同じ不透明度からフェードアウトする
            this._duration = this._initialDisplayDuration - this._duration;
        }
        else {
            this._duration = Params.fadeFrameCount;
        }
    };

    Window_MapActiveMessage.prototype.numVisibleRows = function () {
        return 4;
    };

    /**
     * 吹き出しスプライトの設定
     */
    Window_MapActiveMessage.prototype.setBalloonSprite = function (sprite) {
        sprite.setOwner(this);
        this._balloonSprite = sprite;

        // ウィンドウの裏に表示
        this.addChildAt(sprite, 0);
    };

    /**
     * イベントに対する吹き出しの表示位置を取得
     */
    Window_MapActiveMessage.prototype.getBalloonPosition = function () {
        return this._balloonPosition;
    };

    /**
     * メッセージ表示中か
     */
    Window_MapActiveMessage.prototype.isDisplaying = function () {
        return this._duration > 0;
    };

    /**
     * 強制表示されたか
     */
    Window_MapActiveMessage.prototype.isForced = function () {
        return this._isForceDisplay;
    };

    /**
     * フェードイン中か
     */
    Window_MapActiveMessage.prototype.isFadingIn = function () {
        return this._duration >= this._initialDisplayDuration - Params.fadeFrameCount;
    };

    /**
     * フェードアウト中か
     */
    Window_MapActiveMessage.prototype.isFadingOut = function () {
        return this._duration < Params.fadeFrameCount;
    };

    /**
     * 表示中のイベントと同一イベントか
     */
    Window_MapActiveMessage.prototype.isSameEvent = function (event) {
        if (isNullOrUndefined(this._event)) {
            return false;
        }

        // ページ番号まで同じ場合に同一と見なす
        if (this._event.eventId() === event.eventId() &&
            this._targetPageIndex === event.pageIndex()) {
            return true;
        }

        return false;
    };

    /**
     * イベントが画面内にあるか判定
     */
    Window_MapActiveMessage.prototype.isEventInScreen = function () {
        if (isNullOrUndefined(this._event)) {
            return false;
        }

        var x = this._event.screenX();
        var y = this._event.screenY();
        return x >= 0 && y >= 0 && x <= Graphics.boxWidth && y <= Graphics.boxHeight;
    };

    /**
     * メッセージの表示開始
     * 
     * @param {String}              text        '\n' 区切りのメッセージ
     * @param {Game_Event}          event       表示対象のイベント
     * @param {Sprite_Character}    sprite      表示対象のキャラクタースプライト
     * @param {boolean}             isForce     表示時間満了まで強制表示
     */
    Window_MapActiveMessage.prototype.display = function (text, event, sprite, isForce) {
        // 表示のキャンセル
        function rollback() {
            this.initMembers();
            this.hide();
        }

        if (isNullOrUndefined(text) || isNullOrUndefined(event)) {
            rollback.call(this);
            return;
        }

        this._event = event;
        this._characterSprite = sprite;
        if (!this.isEventInScreen()) {
            rollback.call(this);
            return;
        }

        // 表示パラメータの設定
        this._textState =
        {
            index: 0,
            text: this.convertEscapeCharacters(text)
        };
        this._allTextWidth = 0;
        this._targetPageIndex = event.pageIndex();
        this._initialDisplayDuration = event.getMapActiveMessageDuration();
        this._duration = this._initialDisplayDuration;
        this._isForceDisplay = !!isForce;

        this.newPage(this._textState);
        this.updatePosition();
        this.updateOpacity();
        this.refreshBalloon();

        this.show();

        // 多重表示抑止のため、表示済みフラグを立てる
        event.setMapActiveMessageShown(true);
    };

    /**
     * 新しいページの表示
     */
    Window_MapActiveMessage.prototype.newPage = function (textState) {
        // コンテンツを一時領域に書く
        this.contents = Window_MapActiveMessage.tempMessageBuffer;
        this.contents.clear();
        this.resetFontSettings();
        textState.x = this.newLineX();
        textState.y = 0;
        textState.left = this.newLineX();
        textState.height = this.calcTextHeight(textState, false);
        this._allTextHeight = this.calcTextHeight(textState, true);

        this.updateMessage();

        // コンテンツを一時領域から正式に反映
        this.createContents();
        this.contents.blt(
            Window_MapActiveMessage.tempMessageBuffer,
            0, 0, this._allTextWidth, this._allTextHeight,
            0, 0, this._allTextWidth, this._allTextHeight
        );
    };

    /**
     * メッセージの表示位置を設定
     */
    Window_MapActiveMessage.prototype.updatePosition = function () {
        if (isNullOrUndefined(this._event)) {
            return;
        }

        // 画面外に出る場合は消去
        if (!this.isEventInScreen()) {
            this.hide();
            return;
        }

        // 吹き出しの枠ぶんのマージン
        var margin = Const.balloonFrameInfo.size;

        var balloonOffsetY = Const.balloonVertexSize.height
            - Params.balloonOffsetY
            - margin;
        var heightOffset = this.height
            + (!isNullOrUndefined(this._characterSprite) ? this._characterSprite.patternHeight() : $gameMap.tileHeight());

        // デフォルトの位置は上
        var x = this._event.screenX() - this.width / 2;
        var y = this._event.screenY() - (heightOffset + balloonOffsetY);
        this._balloonPosition = Const.balloonPosition.above;

        // 画面左右にははみ出させない
        x = Math.min(Math.max(x, margin), Graphics.boxWidth - this.width - margin);

        // 画面上側にはみ出る場合は下に表示
        if (y < margin) {
            y = this._event.screenY() + balloonOffsetY;
            this._balloonPosition = Const.balloonPosition.below;
        }

        // 画面下側にはみ出る場合は上に表示
        if (y + this.height > Graphics.boxHeight - margin) {
            y -= heightOffset + balloonOffsetY;
            this._balloonPosition = Const.balloonPosition.above;
        }

        this.x = x;
        this.y = y;

        if (!isNullOrUndefined(this._balloonSprite)) {
            // 吹き出しの頂点表示を考慮した位置に移動
            this._balloonSprite.followOwner();
        }
    };

    /**
     * ウィンドウのサイズを更新
     */
    Window_MapActiveMessage.prototype.updateWindowSize = function () {
        var offset = this.standardPadding() * 2;
        this.width = this._allTextWidth + offset;
        this.height = this._allTextHeight + offset;
    };

    /**
     * 不透明度の更新
     */
    Window_MapActiveMessage.prototype.updateOpacity = function () {
        var opacity = 255;
        if (this.isFadingIn()) {
            // フェードイン
            opacity = 255 * (this._initialDisplayDuration - this._duration) / Params.fadeFrameCount;
        }
        else if (this.isFadingOut()) {
            // フェードアウト
            opacity = 255 * this._duration / Params.fadeFrameCount;
        }

        this.contentsOpacity = opacity;
        if (!isNullOrUndefined(this._balloonSprite)) {
            this._balloonSprite.opacity = opacity;
        }
    };

    /**
     * 吹き出しの再描画
     */
    Window_MapActiveMessage.prototype.refreshBalloon = function () {
        if (!isNullOrUndefined(this._balloonSprite)) {
            this._balloonSprite.refresh();
        }
    };

    /**
     * プレイヤーとの距離を確認
     */
    Window_MapActiveMessage.prototype.checkPlayerDistance = function () {
        // 強制表示 or 消去中は距離判定しない
        if (this.isForced() ||
            this._duration <= Params.fadeFrameCount) {
            return;
        }

        // 表示範囲外に出た場合は消す
        var distance = $gamePlayer.calcDistanceForMapActiveMessage(this._event);
        if (distance > this._event.getMapActiveMessageRange()) {
            this.fadeOut();
        }
    };

    Window_MapActiveMessage.prototype.update = function () {
        Window_Base.prototype.update.call(this);

        if (!isNullOrUndefined(this._balloonSprite)) {
            this._balloonSprite.update();
        }

        if (!this.isDisplaying()) {
            return;
        }

        this._duration--;
        this.updateOpacity();
        this.updatePosition();
        this.checkPlayerDistance();

        // 表示が終了したら初期化
        if (!this.isDisplaying()) {
            debuglog('[MapActiveMessage] Finished to display');
            this.initMembers();
        }
    };

    /**
     * メッセージの更新
     */
    Window_MapActiveMessage.prototype.updateMessage = function () {
        if (isNullOrUndefined(this._textState)) {
            return false;
        }

        while (!this.isEndOfText(this._textState)) {
            this.processCharacter(this._textState);
            this._allTextWidth = Math.max(this._allTextWidth, this._textState.x);
        }

        this.updateWindowSize();

        return true;
    };

    /**
     * 吹き出しの頂点位置を取得
     */
    Window_MapActiveMessage.prototype.getBalloonVertexX = function () {
        if (isNullOrUndefined(this._event)) {
            return 0;
        }

        return this._event.screenX()
            - this.x
            - Const.balloonFrameInfo.size
            - Const.balloonVertexSize.width / 2
            + Params.balloonMargin;
    };

    /**
     * 改行時の描画先 X 座標
     */
    Window_MapActiveMessage.prototype.newLineX = function () {
        return 0;
    };

    /**
     * 改行の処理
     */
    Window_MapActiveMessage.prototype.processNewLine = function (textState) {
        this._allTextWidth = Math.max(this._allTextWidth, textState.x);

        Window_Base.prototype.processNewLine.call(this, textState);
    };

    /**
     * 改ページの処理
     */
    Window_MapActiveMessage.prototype.processNewPage = function (textState) {
        Window_Base.prototype.processNewPage.call(this, textState);
        if (textState.text[textState.index] === '\n') {
            textState.index++;
        }

        textState.y = this.contents.height;
    };

    /**
     * テキスト終端か
     */
    Window_MapActiveMessage.prototype.isEndOfText = function (textState) {
        return textState.index >= textState.text.length;
    };

    /**
     * 改ページが必要か
     */
    Window_MapActiveMessage.prototype.needsNewPage = function (textState) {
        return !this.isEndOfText(textState) &&
            textState.y + textState.height > this.contents.height;
    };


    //-----------------------------------------------------------------------------
    // Scene_Map

    var _Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
    Scene_Map.prototype.createAllWindows = function () {
        _Scene_Map_createAllWindows.call(this);

        this.createActiveMessageWindow();
    };

    /**
     * アクティブメッセージウィンドウの作成
     */
    Scene_Map.prototype.createActiveMessageWindow = function () {
        if (!isNullOrUndefined(this._activeMessageWindowLayer)) {
            return;
        }

        var width = Graphics.boxWidth;
        var height = Graphics.boxHeight;
        var x = (Graphics.width - width) / 2;
        var y = (Graphics.height - height) / 2;
        this._activeMessageWindowLayer = new WindowLayer();
        this._activeMessageWindowLayer.move(x, y, width, height);
        this.addChild(this._activeMessageWindowLayer);

        Window_MapActiveMessage.preloadWindowskin();
    };

    var _Scene_Map_update = Scene_Map.prototype.update;
    Scene_Map.prototype.update = function () {
        _Scene_Map_update.call(this);

        this.updateActiveMessageDisplay();
    };

    var _Scene_Map_stop = Scene_Map.prototype.stop;
    Scene_Map.prototype.stop = function () {
        _Scene_Map_stop.call(this);

        $gameTemp.clearMapActiveMessage();
    };

    var _Scene_Map_terminate = Scene_Map.prototype.terminate;
    Scene_Map.prototype.terminate = function () {
        _Scene_Map_terminate.call(this);

        this.destroyActiveMessageWindows();
    };

    /**
     * アクティブメッセージウィンドウの破棄
     */
    Scene_Map.prototype.destroyActiveMessageWindows = function () {
        this._activeMessageWindowLayer.removeChildren();
        this.removeChild(this._activeMessageWindowLayer);
        this._activeMessageWindowLayer = null;
    };

    /**
     * アクティブメッセージの表示を更新
     */
    Scene_Map.prototype.updateActiveMessageDisplay = function () {
        if (!$gameSystem.isMapActiveMessageEnabled() ||
            $gameMap.isEventRunning()) {
            // メッセージを表示できないときは非表示にしつつ各種フラグを解除
            $gameTemp.clearMapActiveMessage();
            $gameMap.clearActiveMessageShownFlags();
            this._activeMessageWindowLayer.children.forEach(function (child) {
                child.fadeOut();
            });
            return;
        }

        // 表示が完了したものを削除
        this._activeMessageWindowLayer.children.filter(function (child) {
            return !child.isDisplaying();
        }).forEach(function (item) {
            this._activeMessageWindowLayer.removeChild(item);
        }, this);

        // 要求されているメッセージを表示できるだけ表示
        while ($gameTemp.isMapActiveMessageReady()) {
            if (!this.displayActiveMessage()) {
                break;
            }
        }
    };

    /**
     * 指定されたイベントのアクティブメッセージが表示されているか判定
     */
    Scene_Map.prototype.isActiveMessageDisplayed = function (event) {
        var layer = this._activeMessageWindowLayer;
        for (var i = 0; i < layer.children.length; i++) {
            var window = layer.children[i];
            if (window.isDisplaying() && window.isSameEvent(event)) {
                return true;
            }
        }

        return false;
    };

    /**
     * アクティブメッセージの表示
     */
    Scene_Map.prototype.displayActiveMessage = function () {
        var layer = this._activeMessageWindowLayer;

        // 最大数に達していたら表示を諦める
        // (children には吹き出しも含まれるので / 2)
        if (layer.children.length / 2 >= Params.messageCountMax) {
            return false;
        }

        // 予約されているメッセージを表示
        var message = $gameTemp.popNextMapActiveMessage();
        while (!isNullOrUndefined(message)) {
            // まだ表示していないもののみ表示
            if (!this.isActiveMessageDisplayed(message.event)) {
                var character = this._spriteset.findCharacterSpriteByEvent(message.event);
                var balloon = new Sprite_MapActiveMessageBalloon();
                var window = new Window_MapActiveMessage();
                window.setBalloonSprite(balloon);
                window.display(
                    message.text,
                    message.event,
                    character,
                    message.isForced);
                layer.addChild(window);
            }

            message = $gameTemp.popNextMapActiveMessage();
        }

        return true;
    };

    var _Scene_Map_snapForBattleBackground = Scene_Map.prototype.snapForBattleBackground;
    Scene_Map.prototype.snapForBattleBackground = function () {
        // アクティブメッセージはキャプチャしない
        var layer = this._activeMessageWindowLayer;
        if (!isNullOrUndefined(layer)) {
            layer.visible = false;
        }

        _Scene_Map_snapForBattleBackground.call(this);

        if (!isNullOrUndefined(layer)) {
            layer.visible = true;
        }
    };

})();

