//=============================================================================
// KMS_MapActiveMessage.js
//   Last update: 2022/05/22
//=============================================================================

/*:
 * @plugindesc
 * [v0.3.2] 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
 * フキダシのヤツ、CBRって所が改造
 * [v0.3.2] プレイヤーが近付いたときに、自動的にメッセージを表示するイベントを作成します。
 *
 * @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,
			loopMessage: /<(?:アクティブメッセージループ|ActiveMessageLoop)>/i,
			beginMessage: /<(?:アクティブメッセージ|ActiveMessage)\s*[:\s][^>]+$/i,
			endMessage: /([^>]*)>/
		},

		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.pushMapActiveMessages = function (targetEvent, messages, needsLoop, isForced) {
		var message = {
			id: this._mapActiveMessageNextId++,
			event: targetEvent,
			messages: messages,
			needsLoop: needsLoop,
			isForced: isForced
		};
		this._mapActiveMessages.push(message);
	};

	/**
	 * アクティブメッセージのクリア
	 */
	Game_Temp.prototype.clearMapActiveMessage = function () {
		this._mapActiveMessageNextId = 0;
		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.pushMapActiveMessages(event, event.getMapActiveMessages(), event.needsMapActiveMessageLoop());
		});
	};

	/**
	 * アクティブメッセージの強制表示
	 */
	Game_Map.prototype.forceDisplayActiveMessage = function (eventId) {
		var invoker = function (event) {
			$gameTemp.pushMapActiveMessages(event, event.getMapActiveMessages(), event.needsMapActiveMessageLoop(), 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._mapActiveMessageList = [];
		this._mapActiveMessageRange = Params.defaultRange;
		this._mapActiveMessageDuration = Params.displayDuration;
		this._needsMapActiveMessageLoop = false;
		this._isMapActiveMessageShown = false;

		var page = this.page();
		if (isNullOrUndefined(page)) {
			return;
		}

		var parsed = this.parseCommentForMapActiveMessage(page);
		var mergedComment = parsed.mergedComment;
		var commentUnits = parsed.commentUnits;

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

		// ループ設定を解析
		var loopMatch = Const.regex.loopMessage.exec(mergedComment);
		if (!isNullOrUndefined(loopMatch)) {
			this._needsMapActiveMessageLoop = true;
		}

		commentUnits.forEach(function (unit) {
			// メッセージ定義を解析
			var messageMatch = Const.regex.activeMessage.exec(unit);
			if (!isNullOrUndefined(messageMatch)) {
				this._mapActiveMessageList.push({
					text: messageMatch[1],
					duration: this._mapActiveMessageDuration
				});
			}

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

				// 直前のメッセージの duration を変更
				if (this._mapActiveMessageList.length > 0) {
					this._mapActiveMessageList[this._mapActiveMessageList.length - 1].duration = duration;
				}
			}
		}, this);
	};

	/**
	 * アクティブメッセージ用のコメント解析
	 *
	 * @param {Object}  page    解析対象のイベントページ
	 */
	Game_Event.prototype.parseCommentForMapActiveMessage = function (page) {
		function isComment(command) {
			return command && (command.code === 108 || command.code === 408);
		}

		// 注釈以外に達するまで解析
		var commandIndex = 0;
		var command = page.list[commandIndex];
		var mergedComment = ""; // コメントを結合した文字列
		var commentUnit = "";
		var commentUnits = []; // 解析単位ごとに結合した文字列のリスト
		var inMessage = false;
		while (isComment(command)) {
			if (mergedComment.length > 0) {
				mergedComment += "\n";
			}

			var commentText = command.parameters[0];
			mergedComment += commentText;

			if (inMessage) {
				if (commentUnit.length > 0) {
					commentUnit += "\n";
				}

				commentUnit += commentText;

				if (Const.regex.endMessage.test(commentText)) {
					commentUnits.push(commentUnit);
					commentUnit = "";
					inMessage = false;
				}
			} else {
				if (Const.regex.beginMessage.test(commentText)) {
					commentUnit = commentText;
					inMessage = true;
				} else {
					commentUnits.push(commentText);
				}
			}

			command = page.list[++commandIndex];
		}

		if (inMessage) {
			commentUnits.push(commentUnit);
		}

		return {
			mergedComment: mergedComment,
			commentUnits: commentUnits
		};
	};

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

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

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

	/**
	 * アクティブメッセージをループするか
	 *
	 * @return {Boolean} true if needs loop
	 */
	Game_Event.prototype.needsMapActiveMessageLoop = function () {
		return this._needsMapActiveMessageLoop;
	};

	/**
	 * アクティブメッセージを表示するフレーム数
	 *
	 * @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.prototype);
	Sprite_MapActiveMessageBalloon.prototype.constructor = Sprite_MapActiveMessageBalloon;

	Sprite_MapActiveMessageBalloon.prototype.initialize = function () {
		Sprite.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 + 4, 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 + 1,
				frame.srcSize,
				vertexSize.width - 2,
				vertexSize.height,
				newDx + 1,
				newDy,
				vertexSize.width - 2,
				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 - 2, dy, rightWidth + 4, 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, { x: 0, y: 0, width: 64, height: 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();

		this.CBR_t = 0;
	};

	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._message = null;
		this._pendingMessages = [];
		this._textState = null;
		this._event = null;
		this._characterSprite = null;
		this._targetPageIndex = 0;
		this._duration = 0;
		this._isHiding = false;
		this._balloonPosition = Const.balloonPosition.above;
	};

	Window_MapActiveMessage.prototype.printDebug = function (message) {
		if (Const.debug) {
			console.log("[MapActiveMessage] " + this.getId() + ": " + message);
		}
	};

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

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

	Window_MapActiveMessage.prototype.hide = function () {
		// ロールバック処理中はメッセージ無しで呼ばれる
		if (this._message) {
			this.printDebug("Hide");
		}

		this._duration = 0;
		this._isHiding = true;

		Window_Base.prototype.hide.call(this);

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

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

		this.printDebug("Fading out");

		this._isHiding = true;

		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;
	};

	/**
	 * メッセージ ID を取得
	 */
	Window_MapActiveMessage.prototype.getId = function () {
		return this._message.id;
	};

	/**
	 * ループ表示するか
	 */
	Window_MapActiveMessage.prototype.needsLoop = function () {
		return this._message.needsLoop;
	};

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

	/**
	 * フェードイン中か
	 */
	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.isHiding = function () {
		return this._isHiding;
	};

	/**
	 * 表示中のイベントと同一イベントか
	 */
	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}              message     表示するメッセージ
	 * @param {Sprite_Character}    sprite      表示対象のキャラクタースプライト
	 */
	Window_MapActiveMessage.prototype.display = function (message, sprite) {
		if (isNullOrUndefined(message)) {
			throw "'message' must not be null or undefined.";
		}

		// 表示のキャンセル
		function rollback() {
			this.initMembers();
			this.hide();
		}

		if (message.messages.length <= 0 || isNullOrUndefined(message.event)) {
			rollback.call(this);
			return;
		}

		this._message = message;
		this._event = message.event;
		this._characterSprite = sprite;

		if (!this.isEventInScreen()) {
			rollback.call(this);
			return;
		}

		this._targetPageIndex = this._event.pageIndex();

		this.initializeMessage();
		this.show();

		this.printDebug("Display new message for event " + this._event.eventId() + ".");

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

	/**
	 * メッセージの初期化
	 */
	Window_MapActiveMessage.prototype.initializeMessage = function () {
		this._pendingMessages = this._message.messages.slice();

		this.displayNextMessage();
	};

	/**
	 * 次のメッセージを表示
	 */
	Window_MapActiveMessage.prototype.displayNextMessage = function () {
		var message = this._pendingMessages.shift();
		if (isNullOrUndefined(message)) {
			return false;
		}

		this._textState = {
			index: 0,
			text: this.convertEscapeCharacters(message.text)
		};
		this._allTextWidth = 0;
		this._initialDisplayDuration = message.duration;
		this._duration = this._initialDisplayDuration;

		this.newPage(this._textState);
		this.updatePosition();
		this.updateOpacity();
		this.refreshBalloon();
		///　CBRここでテキスト入力
		this.drawTextEx(this._textState.text, 10, 10, this._textState.width);

		return 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);
		//CBR heightはここ
		this.contents.fontSize = 22;
		var lines = textState.text.split("\n");
		this._allTextHeight = this.calcTextHeight(textState, false) * lines.length + 20;

		textState.width = 0;
		for (var i = 0; i < lines.length; i++) {
			var w = this.textWidth(lines[i]);
			if (w && textState.width < w) {
				textState.width = w;
			}
		}
		//this._allTextWidth = 598;

		//textState.width = this.textWidth(textState.text)

		this.updateMessage();

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

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

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

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

		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);
		var beforePosit = this.getBalloonPosition();
		this._balloonPosition = Const.balloonPosition.above;

		// 画面左右にははみ出させない
		//CBRまだよくわかんないから発生した時にやる
		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;
		}

		// 画面下側にはみ出る場合は上に表示
		else 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();
		}

		//#####################　CBR、位置が違う場合は再描写
		if (beforePosit != this._balloonPosition) {
			this.refreshBalloon();
		}
	};

	/**
	 * ウィンドウのサイズを更新
	 */
	Window_MapActiveMessage.prototype.updateWindowSize = function () {
		var offset = $gameSystem.windowPadding() * 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();
		}
	};

	/**
	 * プレイヤーと距離が離れていたら消去する
	 *
	 * @return {Boolean} true if hidden
	 */
	Window_MapActiveMessage.prototype.hideFarMessage = function () {
		// 表示範囲外に出た場合は消す
		var distance = $gamePlayer.calcDistanceForMapActiveMessage(this._event);
		if (distance > this._event.getMapActiveMessageRange()) {
			this.fadeOut();
			return true;
		}

		return false;
	};

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

		this.hideFarMessage();
	};

	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()) {
			if (this.displayNextMessage()) {
				this.printDebug("Display next message.");
			} else if (this.needsLoop() && !this.isHiding()) {
				// 距離が離れていたらループしない
				if (!this.hideFarMessage()) {
					//this.printDebug('Loop message.');
					//this.initializeMessage();
				}
			} else {
				// 表示が終了したら初期化
				this.printDebug("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.width+20);
			if (this._textState.width) {
				this._allTextWidth = this._textState.width + 20;
			}
		}

		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.x = x;
		//this._activeMessageWindowLayer.y = y;
		//this._activeMessageWindowLayer.width = width;
		//this._activeMessageWindowLayer.height = height;

		//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();

				const ww = Graphics.boxWidth;
				const wh = this.calcWindowHeight(4, false) + 8;
				const wx = (Graphics.boxWidth - ww) / 2;
				const wy = 0;
				const rect = new Rectangle(wx, wy, ww, wh);

				var window = new Window_MapActiveMessage(rect);
				window.setBalloonSprite(balloon);
				window.display(message, character);
				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;
		}
	};
})();
