//=============================================================================
// RPG Maker MZ - RegionSetting
//
// Copyright 2024 Panzer-IV. All rights reserved.
// This source code or any portion thereof must not be
// reproduced or used without licensed in any manner whatsoever.
//=============================================================================
/*:
 * @target MZ
 * @plugindesc リージョン設定プラグイン
 * @author 四号戦車
 * @base PluginCommonBase
 * @base DebugSwitch
 * @base TextResource
 * @base PluginUtils
 * @orderAfter PluginCommonBase
 * @orderAfter DebugSwitch
 * @orderAfter TextResource
 * @orderAfter PluginUtils
 *
 * @param regions
 * @text リージョン設定
 * @desc リージョンと効果の指定です。
 * @type struct<RegionSetting>[]
 * @default []
 *
 * @param passageSetting
 * @text 通行不可デフォルト設定
 * @desc 通行不可リージョンが設定されていない
 *       タイルの通行設定
 * @type select
 * @default default_passage
 * @option ツクールの設定に従う
 * @value default_passage
 * @option 全て通行可
 * @value all_passible
 * @option 全て通行不可
 * @value all_not_passible
 *
 *
 * @help RegionSetting.js
 *
 * Version: 0.0.3
 *
 * マップリージョンに特殊処理を持たせる設定プラグイン
 *
 */
/*~struct~RegionSetting:
 *
 * @param name
 * @text 設定名
 * @desc 設定用の名前です。
 *       入力時識別用にお使い下さい。
 * @type string
 * @default
 *
 * @param region
 * @text リージョン番号
 * @desc 指定リージョンの番号です。
 * @type number
 * @min 0
 * @default 0
 *
 * @param effect
 * @text 効果
 * @desc 指定リージョンに付与する効果を指定します。
 * @type select
 * @default none
 * @option なし
 * @value none
 * @option 通行不可
 * @value blockPassible
 * @option 左方向だけ移動不可
 * @value onlyLeftBlock
 * @option 上方向だけ移動不可
 * @value onlyUpBlock
 * @option 右方向だけ移動不可
 * @value onlyRightBlock
 * @option 下方向だけ移動不可
 * @value onlyDownBlock
 * @option 上下のみ移動可能
 * @value onlyVirtical
 * @option 左右のみ移動可能
 * @value onlyHorizontal
 * @option 左と上だけ移動不可
 * @value onlyUpLeftBlock
 * @option 上と右だけ移動不可
 * @value onlyUpRightBlock
 * @option 右と下だけ移動不可
 * @value onlyDownRightBlock
 * @option 下と左だけ移動不可
 * @value onlyDownLeftBlock
 * @option 強制通行
 * @value forcePassible
 * @option 通行不可（キャラクターの描画をマップより上に）
 * @value blockPassibleWithOverlayTile
 * @option 強制的にマップより優先表示(☆無視)
 * @value overlayTile
 *
 * @param maskOnFarPlayer
 * @text 近づくと消える壁設定
 * @text ONにすると「近づくと消える壁」に出来ます。
 * @type boolean
 * @default false
 *
 */
(() => {
	"use strict";

    //========================================================================
    // プラグイン定義
    //========================================================================
    const pluginName = "RegionSetting";
    const script = document.currentScript;
    const param  = PluginUtils.reparseParameter(script);

    //========================================================================
    // 定数定義
    //========================================================================
    const DIRECTION_UP = 8;
    const DIRECTION_LEFT = 4;
    const DIRECTION_RIGHT = 6;
    const DIRECTION_DOWN = 2;

    const checkDirection = (direction, ...blockdirections) => {
        return !blockdirections.some(dir => dir === direction);
    };

    //========================================================================
    // クラス定義 (Sprite_MapMask)
    //========================================================================
    class Sprite_MapMask extends Sprite {
        constructor(mask) {
            super(new Bitmap($gameMap.tileWidth(), $gameMap.tileHeight()));

            this._mapMask = mask;
            this.bitmap.fillAll('#000000');
        }

        get group() {
            return this._mapMask?.group;
        }

        set maskVisible(value) {
            if (!!this._mapMask) {
                this._mapMask.visible = value;
            }
        }

        update() {
            super.update();
            this.updatePosition();
            this.updateOpacity();
        }

        updatePosition() {
            if (!!this._mapMask) {
                this.x = this._mapMask.screenX();
                this.y = this._mapMask.screenY();
                this.z = 6;
            }
        }

        isNearThePlayer() {
            if (!!this._mapMask) {
                return this._mapMask.isNearThePlayer();
            }
            return false;
        }

        updateOpacity() {
            if (!!this._mapMask) {
                if (this._mapMask.isVisible()) {
                    if (this.opacity < 255) {
                        this.opacity += 10;
                        if (this.opacity >= 255) {
                            this.opacity = 255;
                        }
                    }
                } else {
                    if (this.opacity > 0) {
                        this.opacity -= 10;
                        if (this.opacity < 0) {
                            this.opacity = 0;
                        }
                    }
                }
            }
        }
    };

    //========================================================================
    // クラス定義 (Game_MapMask)
    //========================================================================
    class Game_MapMask {
        constructor(mask) {
            this._realX = mask.x;
            this._realY = mask.y;
            this._effect = mask.effect;
            this._group = mask.group;
            this._visible = true;
        }

        get x() {
            return this._realX;
        }

        get y() {
            return this._realY;
        }

        get group() {
            return this._group;
        }

        set visible(value) {
            this._visible = value;
        }

        screenX() {
            const tw = $gameMap.tileWidth();
            const scrolledX = $gameMap.adjustX(this._realX);
            return Math.floor(scrolledX * tw);
        }

        screenY() {
            const th = $gameMap.tileHeight();
            const scrolledY = $gameMap.adjustY(this._realY);
            return Math.floor(scrolledY * th);
        }

        isNearThePlayer() {
            const sx = Math.abs($gameMap.deltaX(this._realX, $gamePlayer.x));
            const sy = Math.abs($gameMap.deltaY(this._realY, $gamePlayer.y));
            return sx + sy < 5;
        }

        show() {
            this._visible = true;
        }

        hide() {
            this._visible = false;
        }

        isVisible() {
            return this._visible;
        }
    }

    //========================================================================
    // コアスクリプト変更部 (Game_Map)
    //========================================================================
    const _Game_Map_prototype_setup = Game_Map.prototype.setup;
    Game_Map.prototype.setup = function(mapId) {
        _Game_Map_prototype_setup.apply(this, arguments);

        const width = $dataMap.width;
        const height = $dataMap.height;

        const masks = [];
        for (let y = 1; y <= height; y++) {
            for (let x = 1; x <= width; x++) {
                const regionId = this.regionId(x, y);
                const setting = param.regions.find(region => {
                    return region.region === regionId && region.maskOnFarPlayer;
                });

                if (!!setting) {
                    masks.push({
                        x: x,
                        y: y,
                        effect: setting.effect, regionId,
                        group: regionId,
                        visible: true,
                    });
                }
            }
        }

        this._maskGroups = masks.map(mask => {
            return mask.group;
        }).filter((group, index, self) => {
            return self.indexOf(group) === index;
        });
        this._mapMasks = masks;
    };

    Game_Map.prototype.mapMasks = function() {
        return this._mapMasks;
    };

    Game_Map.prototype.mapMaskGroups = function() {
        return this._maskGroups;
    };

    //========================================================================
    // コアスクリプト変更部 (Spriteset_Map)
    //========================================================================
    const _Spriteset_Map_prototype_createCharacters = Spriteset_Map.prototype.createCharacters;
    Spriteset_Map.prototype.createCharacters = function() {
        _Spriteset_Map_prototype_createCharacters.apply(this, arguments);

        this._mapMaskSprites = [];

        for (const mask of $gameMap.mapMasks()) {
            this._mapMaskSprites.push(new Sprite_MapMask(new Game_MapMask(mask)));
        }

        this._mapMaskSprites.forEach(sprite => {
            this._tilemap.addChild(sprite);
        });
    };

    const _Spriteset_Map_prototype_update = Spriteset_Map.prototype.update;
    Spriteset_Map.prototype.update = function() {
        if (!!this._mapMaskSprites) {
            const maskGroups = $gameMap.mapMaskGroups();
            maskGroups.forEach(group => {
                const groups = this._mapMaskSprites.filter(maskSprite => {
                    return maskSprite.group === group;
                });

                const visible = groups.every(maskSprite => {
                    return !maskSprite.isNearThePlayer();
                })

                groups.forEach(maskSprite => {
                    maskSprite.maskVisible = visible;
                });
            });
        }
        _Spriteset_Map_prototype_update.apply(this, arguments);
    };

    //========================================================================
    // コアスクリプト変更部 (Game_Map)
    //========================================================================
    const _Game_Map_prototype_isPassable = Game_Map.prototype.isPassable;
    Game_Map.prototype.isPassable = function(x, y, d) {
        const regionId = this.regionId(x, y);
        const setting = param.regions.find(region => {
            return region.region === regionId;
        });

        const passageSeeting = param.passageSetting;
        const defaultPassible = () => {
            switch (passageSeeting) {
                case 'all_passible':
                    return true;
                case 'all_not_passible':
                    return false;
                case 'default_passage':
                default:
                    return _Game_Map_prototype_isPassable.apply(this, arguments);
            } // switch
        };

        if (!!setting) {
            switch (setting.effect) {
                case 'blockPassible':
                case 'blockPassibleWithOverlayTile':
                    return false;
                case 'forcePassible':
                    return true;
                case 'onlyLeftBlock':
                    return checkDirection(d, DIRECTION_LEFT);
                case 'onlyUpBlock':
                    return checkDirection(d, DIRECTION_UP);
                case 'onlyRightBlock':
                    return checkDirection(d, DIRECTION_RIGHT);
                case 'onlyDownBlock':
                    return checkDirection(d, DIRECTION_DOWN);
                case 'onlyVirtical':
                    return checkDirection(d, DIRECTION_LEFT, DIRECTION_RIGHT);
                case 'onlyHorizontal':
                    return checkDirection(d, DIRECTION_UP, DIRECTION_DOWN);
                case 'onlyUpLeftBlock':
                    return checkDirection(d, DIRECTION_UP, DIRECTION_LEFT);
                case 'onlyUpRightBlock':
                    return checkDirection(d, DIRECTION_UP, DIRECTION_RIGHT);
                case 'onlyDownRightBlock':
                    return checkDirection(d, DIRECTION_DOWN, DIRECTION_RIGHT);
                case 'onlyDownLeftBlock':
                    return checkDirection(d, DIRECTION_DOWN, DIRECTION_LEFT);
                default:
                    return defaultPassible();
            } // switch
        } else {
            return defaultPassible();
        }
    };

    Game_Map.prototype.isOverlayTile = function(x, y) {
        const regionId = this.regionId(x, y);
        const setting = param.regions.find(region => {
            return region.region === regionId;
        });
        return !!setting && [
            'blockPassibleWithOverlayTile',
            'overlayTile',
        ].some(effect => setting.effect === effect);
    };

    Game_Map.prototype.isBlockPassible = function(x, y) {
        const regionId = this.regionId(x, y);
        const setting = param.regions.find(region => {
            return region.region === regionId;
        });
        return !!setting && setting.effect === 'blockPassible';
    };

    //========================================================================
    // コアスクリプト変更部 (Game_Event)
    //========================================================================
    const _Game_Event_start = Game_Event.prototype.start;
    Game_Event.prototype.start = function() {
        if (this.isTriggerIn([0, 1])) {
            // プレイヤーが侵入できない箇所にいるイベントはインタラクトもさせない
            const x = $gamePlayer.x;
            const y = $gamePlayer.y;
            const d = $gamePlayer.direction();

            if (!$gameMap.isPassable(x, y, d)) {
                return;
            }
        } else if (this.isTriggerIn([2])) {
            // イベント側からプレイヤーのいる箇所に侵入不可ならインタラクトさせない
            const x = $gamePlayer.x;
            const y = $gamePlayer.y;
            const d = this.reverseDir(this.direction());

            if (!$gameMap.isPassable(x, y, d)) {
                return;
            }
        }
        _Game_Event_start.apply(this, arguments);
    };

    //========================================================================
    // コアスクリプト変更部 (Game_Event)
    //========================================================================
    const _Game_CharacterBase_screenZ = Game_CharacterBase.prototype.screenZ;
    Game_CharacterBase.prototype.screenZ = function() {
        const z = _Game_CharacterBase_screenZ.apply(this, arguments);
        if ($gameMap.isOverlayTile(this.x, this.y)) {
            return z + 3;
        }
        if ($gameMap.isBlockPassible(this.x, this.y - 1)) {
            return z + 3;
        }
        return z;
    };

    //========================================================================
    // プラグインコマンド
    //========================================================================
})();
