Gainmessage
//=============================================================================
// GainMessage.js
//=============================================================================

/*:
 * @target MZ
 * @plugindesc Shows a message when event commands change gold, items, weapons, or armors.
 * @author taroxd
 *
 * @param itemFormat
 * @text Item Format
 * @desc Message format for items, weapons, and armors. Supports \action, \value, \icon, \name, and regular Show Text escape codes.
 * @type string
 * @default \action \icon\name * \value
 *
 * @param goldFormat
 * @text Gold Format
 * @desc Message format for gold. Supports \action, \value, \icon, \name, and regular Show Text escape codes.
 * @type string
 * @default \action \icon\value \name
 *
 * @param actionGain
 * @text Gain Action
 * @desc Text used by \action when the party gains something.
 * @type string
 * @default Got
 *
 * @param actionLose
 * @text Lose Action
 * @desc Text used by \action when the party loses something.
 * @type string
 * @default Lost
 *
 * @param goldIconIndex
 * @text Gold Icon Index
 * @desc Icon index used by \icon for gold. Negative values make \icon empty.
 * @type number
 * @min -1
 * @default 160
 *
 * @param background
 * @text Message Background
 * @desc Background type used by gain/loss messages.
 * @type select
 * @option Window
 * @value 0
 * @option Dim
 * @value 1
 * @option Transparent
 * @value 2
 * @default 1
 *
 * @param position
 * @text Message Position
 * @desc Position used by gain/loss messages.
 * @type select
 * @option Top
 * @value 0
 * @option Middle
 * @value 1
 * @option Bottom
 * @value 2
 * @default 1
 *
 * @param gainGoldSe
 * @text Gain Gold SE
 * @desc SE played when gaining gold. Leave blank to disable.
 * @type file
 * @dir audio/se/
 * @default Coin
 *
 * @param loseGoldSe
 * @text Lose Gold SE
 * @desc SE played when losing gold. Leave blank to disable.
 * @type file
 * @dir audio/se/
 * @default Blow2
 *
 * @param gainItemSe
 * @text Gain Item SE
 * @desc SE played when gaining an item, weapon, or armor. Leave blank to disable.
 * @type file
 * @dir audio/se/
 * @default Item1
 *
 * @param loseItemSe
 * @text Lose Item SE
 * @desc SE played when losing an item, weapon, or armor. Leave blank to disable.
 * @type file
 * @dir audio/se/
 * @default Blow2
 *
 * @param seVolume
 * @text SE Volume
 * @desc Volume used for configured sound effects.
 * @type number
 * @min 0
 * @max 100
 * @default 90
 *
 * @param sePitch
 * @text SE Pitch
 * @desc Pitch used for configured sound effects.
 * @type number
 * @min 50
 * @max 150
 * @default 100
 *
 * @param sePan
 * @text SE Pan
 * @desc Pan used for configured sound effects.
 * @type number
 * @min -100
 * @max 100
 * @default 0
 *
 * @command off
 * @text Suppress Messages
 * @desc Suppresses the next count gain/loss messages. This counter is not saved.
 *
 * @arg count
 * @text Count
 * @desc Number of upcoming gain/loss messages to suppress.
 * @type number
 * @min 1
 * @default 1
 *
 * @help
 * This plugin shows a message when these event commands change the party's
 * inventory:
 *   Change Gold
 *   Change Items
 *   Change Weapons
 *   Change Armors
 *
 * RPG Maker MZ does not show such messages by default; the default commands
 * only update party data.
 *
 * Message format escape codes:
 *   \name    Item name or currency unit.
 *   \value   Actual changed amount after inventory limits are applied.
 *   \icon    Item icon or configured gold icon.
 *   \action  Gain Action or Lose Action.
 *
 * Regular Show Text escape codes are also supported.
 *
 * Plugin command:
 *   Suppress Messages
 *     count: suppresses the next count gain/loss messages.
 *
 * For compatibility with old-style plugin commands, this plugin also accepts:
 *   GainMessage off
 *   GainMessage off 3
 *
 * The suppression counter is kept only in memory. It is reset on new game and
 * after loading a save file.
 */

(() => {
    "use strict";

    const pluginName = "GainMessage";
    const parameters = PluginManager.parameters(pluginName);

    const stringParameter = (name, defaultValue) => {
        const text = parameters[name];
        return text === undefined ? defaultValue : text;
    };

    const numberParameter = (name, defaultValue) => {
        const text = parameters[name];
        const value = text === undefined || text === "" ? defaultValue : Number(text);
        return Number.isFinite(value) ? value : defaultValue;
    };

    const itemFormat = stringParameter("itemFormat", "\\action \\icon\\name * \\value");
    const goldFormat = stringParameter("goldFormat", "\\action \\icon\\value \\name");
    const actionGain = stringParameter("actionGain", "Got");
    const actionLose = stringParameter("actionLose", "Lost");
    const goldIconIndex = numberParameter("goldIconIndex", 160);
    const background = numberParameter("background", 1);
    const position = numberParameter("position", 1);
    const gainGoldSe = stringParameter("gainGoldSe", "Coin");
    const loseGoldSe = stringParameter("loseGoldSe", "Blow2");
    const gainItemSe = stringParameter("gainItemSe", "Item1");
    const loseItemSe = stringParameter("loseItemSe", "Blow2");
    const seVolume = numberParameter("seVolume", 90);
    const sePitch = numberParameter("sePitch", 100);
    const sePan = numberParameter("sePan", 0);

    let suppressCount = 0;

    const resetSuppressCount = () => {
        suppressCount = 0;
    };

    const normalizedCount = value => {
        const count = Number(value === undefined || value === "" ? 1 : value);
        return Number.isFinite(count) ? Math.max(Math.floor(count), 0) : 1;
    };

    const suppressMessages = count => {
        suppressCount += normalizedCount(count);
    };

    const isEquipmentItem = item => {
        return DataManager.isWeapon(item) || DataManager.isArmor(item);
    };

    const numItemsWithEquip = (item, includeEquip) => {
        let count = $gameParty.numItems(item);
        if (includeEquip && isEquipmentItem(item)) {
            for (const actor of $gameParty.members()) {
                count += actor.equips().filter(equip => equip === item).length;
            }
        }
        return count;
    };

    const iconText = iconIndex => {
        return iconIndex >= 0 ? "\\I[" + iconIndex + "]" : "";
    };

    const makeMessage = (value, item) => {
        const format = item ? itemFormat : goldFormat;
        const replacements = {
            "\\action": value > 0 ? actionGain : actionLose,
            "\\value": String(Math.abs(value)),
            "\\icon": item ? iconText(item.iconIndex) : iconText(goldIconIndex),
            "\\name": item ? item.name : TextManager.currencyUnit
        };
        return format.replace(/\\(?:action|value|icon|name)/gi, match => {
            return replacements[match.toLowerCase()] || match;
        });
    };

    const playSe = (value, item) => {
        const name = item ? (value > 0 ? gainItemSe : loseItemSe) : (value > 0 ? gainGoldSe : loseGoldSe);
        if (name) {
            AudioManager.playSe({
                name: name,
                volume: seVolume,
                pitch: sePitch,
                pan: sePan
            });
        }
    };

    const shouldSuppressMessage = () => {
        if (suppressCount > 0) {
            suppressCount--;
            return true;
        }
        return false;
    };

    const showMessage = (interpreter, value, item) => {
        if (value === 0 || shouldSuppressMessage()) {
            return;
        }
        $gameMessage.setBackground(background);
        $gameMessage.setPositionType(position);
        $gameMessage.add(makeMessage(value, item));
        playSe(value, item);
        interpreter.setWaitMode("message");
    };

    const canStartMessage = () => {
        return suppressCount > 0 || !$gameMessage.isBusy();
    };

    PluginManager.registerCommand(pluginName, "off", args => {
        suppressMessages(args.count);
    });

    const _DataManager_createGameObjects = DataManager.createGameObjects;
    DataManager.createGameObjects = function() {
        const result = _DataManager_createGameObjects.apply(this, arguments);
        resetSuppressCount();
        return result;
    };

    const _DataManager_extractSaveContents = DataManager.extractSaveContents;
    DataManager.extractSaveContents = function(contents) {
        const result = _DataManager_extractSaveContents.apply(this, arguments);
        resetSuppressCount();
        return result;
    };

    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command, args) {
        const result = _Game_Interpreter_pluginCommand.apply(this, arguments);
        if (String(command).toLowerCase() === pluginName.toLowerCase()) {
            const subCommand = String(args[0] || "").toLowerCase();
            if (subCommand === "off") {
                const legacyCount = String(args[1] || "").toLowerCase() === "count" ? args[2] : args[1];
                suppressMessages(legacyCount);
            }
        }
        return result;
    };

    const _Game_Interpreter_command125 = Game_Interpreter.prototype.command125;
    Game_Interpreter.prototype.command125 = function(params) {
        if (!canStartMessage()) {
            return false;
        }
        const lastGold = $gameParty.gold();
        const result = _Game_Interpreter_command125.apply(this, arguments);
        showMessage(this, $gameParty.gold() - lastGold, null);
        return result;
    };

    const _Game_Interpreter_command126 = Game_Interpreter.prototype.command126;
    Game_Interpreter.prototype.command126 = function(params) {
        if (!canStartMessage()) {
            return false;
        }
        const item = $dataItems[params[0]];
        const lastNumber = numItemsWithEquip(item, false);
        const result = _Game_Interpreter_command126.apply(this, arguments);
        showMessage(this, numItemsWithEquip(item, false) - lastNumber, item);
        return result;
    };

    const _Game_Interpreter_command127 = Game_Interpreter.prototype.command127;
    Game_Interpreter.prototype.command127 = function(params) {
        if (!canStartMessage()) {
            return false;
        }
        const item = $dataWeapons[params[0]];
        const includeEquip = params[4];
        const lastNumber = numItemsWithEquip(item, includeEquip);
        const result = _Game_Interpreter_command127.apply(this, arguments);
        showMessage(this, numItemsWithEquip(item, includeEquip) - lastNumber, item);
        return result;
    };

    const _Game_Interpreter_command128 = Game_Interpreter.prototype.command128;
    Game_Interpreter.prototype.command128 = function(params) {
        if (!canStartMessage()) {
            return false;
        }
        const item = $dataArmors[params[0]];
        const includeEquip = params[4];
        const lastNumber = numItemsWithEquip(item, includeEquip);
        const result = _Game_Interpreter_command128.apply(this, arguments);
        showMessage(this, numItemsWithEquip(item, includeEquip) - lastNumber, item);
        return result;
    };
})();

Eventhelper
//=============================================================================
// EventHelper.js
//=============================================================================

/*:
 * @target MZ
 * @plugindesc Adds convenience APIs for event scripts and movement-route scripts.
 * @author taroxd
 *
 * @command AddBattleLog
 * @text Add Battle Log
 * @desc Adds text to the battle log. Does nothing outside battle.
 *
 * @arg text
 * @text Text
 * @type multiline_string
 * @default
 *
 * @command SelfSwitch
 * @text Set Self Switch
 * @desc Sets a self switch. Map ID 0 uses the current map; Event ID 0 uses the current event.
 *
 * @arg mapId
 * @text Map ID
 * @desc 0 uses the current map.
 * @type number
 * @min 0
 * @default 0
 *
 * @arg eventId
 * @text Event ID
 * @desc 0 uses the current event.
 * @type number
 * @min 0
 * @default 0
 *
 * @arg letter
 * @text Letter
 * @type select
 * @option A
 * @option B
 * @option C
 * @option D
 * @default A
 *
 * @arg operation
 * @text Operation
 * @type select
 * @option ON
 * @value on
 * @option OFF
 * @value off
 * @default on
 *
 * @command StartShake
 * @text Start Screen Shake
 * @desc Starts screen shake. Duration 0 continues until Stop Screen Shake is called.
 *
 * @arg power
 * @text Power
 * @type number
 * @min 0
 * @default 5
 *
 * @arg speed
 * @text Speed
 * @type number
 * @min 0
 * @default 5
 *
 * @arg duration
 * @text Duration
 * @desc 0 continues until stopped.
 * @type number
 * @min 0
 * @default 0
 *
 * @command StopShake
 * @text Stop Screen Shake
 * @desc Stops the current screen shake immediately.
 *
 * @help
 * This plugin adds convenience APIs for event scripts and movement-route
 * scripts. It does not change RPG Maker MZ's script evaluators, so script
 * calls must explicitly use "this".
 *
 * Event script APIs
 *   this.thisEvent()
 *     Returns the current event, or null when it is not on the current map.
 *
 *   this.addBattleLog(text)
 *     Adds text to the battle log. Does nothing outside battle.
 *     Example: this.addBattleLog("The ground shakes.");
 *
 *   this.selfSwitch(eventId = 0, mapId = 0)
 *     Returns a self-switch reference. Event ID 0 means the current event;
 *     Map ID 0 means the current map.
 *     Examples:
 *       this.selfSwitch().a = true;
 *       this.selfSwitch(3).b = false;
 *       this.selfSwitch(3, 2).c = true;
 *
 *   this.startShake(power = 5, speed = 5, duration = 0)
 *     Starts a screen shake. Duration 0 continues until stopped.
 *     Example: this.startShake(5, 5);
 *
 *   this.stopShake()
 *     Stops screen shake immediately.
 *     Example: this.stopShake();
 *
 * Plugin commands
 *   Add Battle Log, Set Self Switch, Start Screen Shake, and Stop Screen
 *   Shake provide the same event-script features through the event editor.
 *
 * Movement-route script APIs
 * These calls are for the Script command in Set Movement Route. They also
 * require "this", which is the character whose route is running.
 *
 *   this.zoomX, this.zoomY, this.zoom
 *   this.angle, this.mirror, this.ox, this.oy
 *     Control the character sprite's scale, rotation, horizontal flip, and
 *     pivot offset.
 *     Examples:
 *       this.zoom = 1.5;
 *       this.zoomX = 2;
 *       this.angle = 45;
 *       this.mirror = true;
 *
 *   this.forcePattern(pattern)
 *     Sets the walking-character pattern to 0, 1, or 2.
 *     Example: this.forcePattern(2);
 *
 *   this.forceBushDepth(depth)
 *     Fixes bush depth to a numeric value. Pass null to restore normal bush
 *     depth behavior.
 *     Examples:
 *       this.forceBushDepth(12);
 *       this.forceBushDepth(null);
 *
 *   this.lineTo(x, y)
 *     Moves in a straight line to the map position.
 *     Example: this.lineTo(15, 12);
 *
 *   this.jumpTo(x, y)
 *     Jumps to the map position.
 *     Example: this.jumpTo(10, 8);
 *
 * Player flags
 *   $gamePlayer.waiting = true;
 *     Prevents player movement until set to false.
 *
 *   $gamePlayer.disableScroll = true;
 *     Prevents automatic map scrolling until set to false.
 */

(() => {
    "use strict";

    const pluginName = "EventHelper";

    const finiteNumber = (value, fallback) => {
        const number = Number(value);
        return Number.isFinite(number) ? number : fallback;
    };

    const selfSwitchLetter = letter => {
        const value = String(letter || "A").toUpperCase();
        return ["A", "B", "C", "D"].includes(value) ? value : "A";
    };

    function EventHelperSelfSwitch(mapId, eventId) {
        this._eventHelperMapId = mapId;
        this._eventHelperEventId = eventId;
    }

    EventHelperSelfSwitch.prototype.key = function(letter) {
        return [this._eventHelperMapId, this._eventHelperEventId, selfSwitchLetter(letter)];
    };

    EventHelperSelfSwitch.prototype.get = function(letter) {
        return $gameSelfSwitches.value(this.key(letter));
    };

    EventHelperSelfSwitch.prototype.set = function(letter, value) {
        $gameSelfSwitches.setValue(this.key(letter), !!value);
        return this;
    };

    Object.defineProperties(EventHelperSelfSwitch.prototype, {
        a: {
            get: function() {
                return this.get("A");
            },
            set: function(value) {
                this.set("A", value);
            }
        },
        b: {
            get: function() {
                return this.get("B");
            },
            set: function(value) {
                this.set("B", value);
            }
        },
        c: {
            get: function() {
                return this.get("C");
            },
            set: function(value) {
                this.set("C", value);
            }
        },
        d: {
            get: function() {
                return this.get("D");
            },
            set: function(value) {
                this.set("D", value);
            }
        }
    });

    Game_Interpreter.prototype.thisEvent = function() {
        return this.character(0);
    };

    Game_Interpreter.prototype.addBattleLog = function(text) {
        const scene = SceneManager._scene;
        if (scene instanceof Scene_Battle && scene._logWindow) {
            scene._logWindow.addText(text == null ? "" : String(text));
        }
    };

    Game_Interpreter.prototype.selfSwitch = function(eventId = 0, mapId = 0) {
        const resolvedMapId = finiteNumber(mapId, 0) > 0 ? finiteNumber(mapId, 0) : $gameMap.mapId();
        const resolvedEventId = finiteNumber(eventId, 0) > 0 ? finiteNumber(eventId, 0) : this.eventId();
        return new EventHelperSelfSwitch(resolvedMapId, resolvedEventId);
    };

    Game_Interpreter.prototype.startShake = function(power = 5, speed = 5, duration = 0) {
        const resolvedDuration = finiteNumber(duration, 0);
        $gameScreen.startShake(
            finiteNumber(power, 5),
            finiteNumber(speed, 5),
            resolvedDuration > 0 ? resolvedDuration : Infinity
        );
    };

    Game_Interpreter.prototype.stopShake = function() {
        $gameScreen.clearShake();
    };

    PluginManager.registerCommand(pluginName, "AddBattleLog", function(args) {
        this.addBattleLog(args.text);
    });

    PluginManager.registerCommand(pluginName, "SelfSwitch", function(args) {
        this.selfSwitch(args.eventId, args.mapId).set(args.letter, args.operation !== "off");
    });

    PluginManager.registerCommand(pluginName, "StartShake", function(args) {
        this.startShake(args.power, args.speed, args.duration);
    });

    PluginManager.registerCommand(pluginName, "StopShake", function() {
        this.stopShake();
    });

    const characterProperty = (field, defaultValue) => ({
        get: function() {
            return this[field] == null ? defaultValue : this[field];
        },
        set: function(value) {
            if (value == null) {
                this[field] = null;
                return;
            }
            const number = Number(value);
            this[field] = Number.isFinite(number) ? number : null;
        }
    });

    Object.defineProperties(Game_CharacterBase.prototype, {
        zoomX: characterProperty("_eventHelperZoomX", 1),
        zoomY: characterProperty("_eventHelperZoomY", 1),
        angle: characterProperty("_eventHelperAngle", 0),
        ox: characterProperty("_eventHelperOx", 0),
        oy: characterProperty("_eventHelperOy", 0),
        zoom: {
            get: function() {
                return this.zoomX;
            },
            set: function(value) {
                this.zoomX = value;
                this.zoomY = value;
            }
        },
        mirror: {
            get: function() {
                return this._eventHelperMirror == null ? false : this._eventHelperMirror;
            },
            set: function(value) {
                this._eventHelperMirror = value == null ? null : !!value;
            }
        }
    });

    Game_CharacterBase.prototype.forcePattern = function(pattern) {
        const value = Math.max(0, Math.min(2, Math.floor(finiteNumber(pattern, 1))));
        this._eventHelperForcedPattern = value;
        this._originalPattern = value;
        this.setPattern(value);
    };

    Game_CharacterBase.prototype.forceBushDepth = function(depth) {
        const value = Number(depth);
        this._eventHelperBushDepth = depth == null || depth === "" || !Number.isFinite(value) ? null : Math.max(0, value);
        this.refreshBushDepth();
    };

    Game_CharacterBase.prototype.lineTo = function(x, y) {
        const targetX = finiteNumber(x, this.x);
        const targetY = finiteNumber(y, this.y);
        const deltaX = targetX - this._realX;
        const deltaY = targetY - this._realY;
        const distance = Math.hypot(deltaX, deltaY);

        this._x = targetX;
        this._y = targetY;
        this.straighten();

        if (distance === 0) {
            this._realX = targetX;
            this._realY = targetY;
            this._eventHelperLineTo = null;
            this.refreshBushDepth();
            return;
        }

        const distancePerFrame = this.distancePerFrame();
        this._eventHelperLineTo = {
            targetX: targetX,
            targetY: targetY,
            moveX: (deltaX / distance) * distancePerFrame,
            moveY: (deltaY / distance) * distancePerFrame
        };
    };

    Game_CharacterBase.prototype.updateEventHelperLineTo = function() {
        const lineTo = this._eventHelperLineTo;
        const deltaX = lineTo.targetX - this._realX;
        const deltaY = lineTo.targetY - this._realY;
        const remainingDistance = Math.hypot(deltaX, deltaY);

        if (remainingDistance <= this.distancePerFrame()) {
            this._realX = lineTo.targetX;
            this._realY = lineTo.targetY;
            this._eventHelperLineTo = null;
            this.refreshBushDepth();
        } else {
            this._realX += lineTo.moveX;
            this._realY += lineTo.moveY;
        }
    };

    Game_CharacterBase.prototype.jumpTo = function(x, y) {
        const targetX = finiteNumber(x, this.x);
        const targetY = finiteNumber(y, this.y);
        this.jump(targetX - this.x, targetY - this.y);
    };

    const _Game_CharacterBase_updateMove = Game_CharacterBase.prototype.updateMove;
    Game_CharacterBase.prototype.updateMove = function() {
        if (this._eventHelperLineTo) {
            this.updateEventHelperLineTo();
        } else {
            _Game_CharacterBase_updateMove.apply(this, arguments);
        }
    };

    const _Game_CharacterBase_updatePattern = Game_CharacterBase.prototype.updatePattern;
    Game_CharacterBase.prototype.updatePattern = function() {
        _Game_CharacterBase_updatePattern.apply(this, arguments);
        if (this._eventHelperForcedPattern != null) {
            this.setPattern(this._eventHelperForcedPattern);
        }
    };

    const _Game_CharacterBase_refreshBushDepth = Game_CharacterBase.prototype.refreshBushDepth;
    Game_CharacterBase.prototype.refreshBushDepth = function() {
        if (this._eventHelperBushDepth != null) {
            this._bushDepth = this._eventHelperBushDepth;
        } else {
            _Game_CharacterBase_refreshBushDepth.apply(this, arguments);
        }
    };

    const _Sprite_Character_updateOther = Sprite_Character.prototype.updateOther;
    Sprite_Character.prototype.updateOther = function() {
        _Sprite_Character_updateOther.apply(this, arguments);

        const character = this._character;
        const zoomX = character._eventHelperZoomX;
        const zoomY = character._eventHelperZoomY;
        const mirror = character._eventHelperMirror;

        if (zoomX != null || mirror != null) {
            const scaleX = zoomX == null ? 1 : zoomX;
            this.scale.x = scaleX * (mirror ? -1 : 1);
        }
        if (zoomY != null) {
            this.scale.y = zoomY;
        }
        if (character._eventHelperAngle != null) {
            this.angle = character._eventHelperAngle;
        }
        if (character._eventHelperOx != null) {
            this.pivot.x = character._eventHelperOx;
        }
        if (character._eventHelperOy != null) {
            this.pivot.y = character._eventHelperOy;
        }
    };

    Object.defineProperties(Game_Player.prototype, {
        waiting: {
            get: function() {
                return !!this._eventHelperWaiting;
            },
            set: function(value) {
                this._eventHelperWaiting = !!value;
            }
        },
        disableScroll: {
            get: function() {
                return !!this._eventHelperDisableScroll;
            },
            set: function(value) {
                this._eventHelperDisableScroll = !!value;
            }
        }
    });

    const _Game_Player_canMove = Game_Player.prototype.canMove;
    Game_Player.prototype.canMove = function() {
        return !this.waiting && _Game_Player_canMove.apply(this, arguments);
    };

    const _Game_Player_updateScroll = Game_Player.prototype.updateScroll;
    Game_Player.prototype.updateScroll = function(lastScrolledX, lastScrolledY) {
        if (!this.disableScroll) {
            _Game_Player_updateScroll.apply(this, arguments);
        }
    };
})();

Displaypassage
//=============================================================================
// PassageExtra.js
//=============================================================================

/*:
 * @target MZ
 * @plugindesc Display passage information in playtest.
 * @author taroxd
 *
 * @param Opacity
 * @desc No description.
 * @type number
 * @max 255
 * @min 0
 * @default 150
 *
 * @param NG Color
 * @desc The color displayed above inpassable tiles.
 * @default #ff0000
 *
 * @param OK Color
 * @desc The color displayed above passable tiles.
 * @default #0000ff
 *
 * @param NG Width
 * @desc No description.
 * @type number
 * @max 24
 * @min 0
 * @default 4
 *
 * @param Only Test
 * @desc Enable only in playtest.
 * @type boolean
 * @default true
 *
 * @help This plugin does not provide plugin commands.
 *
 * Press Ctrl Key to display passage information.
 */

void function() {

    var parameters = PluginManager.parameters('DisplayPassage');

    var TEST_ONLY = parameters['Test Only'] !== 'false';
    var enable = !TEST_ONLY || Utils.isOptionValid("test");

    if (!enable) return;

    var OPACITY = parseInt(parameters['Opacity']);
    var NG_COLOR = parameters['NG Color'];
    var OK_COLOR = parameters['OK Color'];
    var NG_WIDTH = parseInt(parameters['NG Width']);

    // Advanced options:
    // Never change it unless you know what you are doing.

    var Z = 20;

    function shouldChangeVisibility() {
        return Input.isTriggered('control');
    }

    function PassageSprite() {
        TilingSprite.call(this);
        this.visible = false;
        this.move(0, 0, Graphics.width, Graphics.height);
        this.z = Z;
        this.opacity = OPACITY;
        this.refresh();
    }

    PassageSprite.prototype = Object.create(TilingSprite.prototype);
    PassageSprite.prototype.constructor = PassageSprite;

    PassageSprite.prototype.update = function() {
        if (shouldChangeVisibility()) {
            this.visible = !this.visible;
        }

        this.origin.x = $gameMap.displayX() * $gameMap.tileWidth();
        this.origin.y = $gameMap.displayY() * $gameMap.tileHeight();
    };

    PassageSprite.prototype.refresh = function() {
        var w = $gameMap.width();
        var h = $gameMap.height();
        this.bitmap = new Bitmap(
            w * $gameMap.tileWidth(),
            h * $gameMap.tileHeight());

        for (var x = 0; x < w; ++x) {
            for (var y = 0; y < h; ++y) {
                this.drawPoint(x, y);
            }
        }
    };

    PassageSprite.prototype.drawPoint = function(x, y) {
        var ngDirs = [2, 4, 6, 8].filter(function(d) {
            return !$gameMap.isPassable(x, y, d);
        });

        var bitmap = this.bitmap;
        var tw = $gameMap.tileWidth();
        var th = $gameMap.tileHeight();

        if (ngDirs.length === 4) {
            bitmap.fillRect(x * tw, y * th, tw, th, NG_COLOR);
            return;
        }

        bitmap.fillRect(x * tw, y * th, tw, th, OK_COLOR);

        ngDirs.forEach(function(d) {
            var dx = d === 6 ? tw - NG_WIDTH : 0;
            var dy = d === 2 ? th - NG_WIDTH : 0;
            var w, h;
            if (d === 2 || d === 8) {
                w = tw;
                h = NG_WIDTH;
            } else {
                w = NG_WIDTH;
                h = tw;
            }
            bitmap.fillRect(x * tw + dx, y * th + dy, w, h, NG_COLOR);
        });
    };

    var ct = Spriteset_Map.prototype.createTilemap;
    Spriteset_Map.prototype.createTilemap = function() {
        ct.call(this);
        this._tilemap.addChild(new PassageSprite);
    };
}();