//============================================================================
// Gabe MZ - Fog Effects
//----------------------------------------------------------------------------
// 29/06/21 | Version: 2.1.0 | Added opacity and tone control
// 14/01/21 | Version: 2.0.2 | PT-BR header typo fix
// 03/09/20 | Version: 2.0.1 | Scene return bug fix
// 02/09/20 | Version: 2.0.0 | Completely rewritten code
// 28/09/20 | Version> 1.1.0 | Redone fog effects layer system 
// 26/08/20 | Version: 1.0.1 | Cleaned code and help section improved
// 25/08/20 | Version: 1.0.0 | Released
//----------------------------------------------------------------------------
// This software is released under the zlib License.
//============================================================================
/*:
 * @target MZ
 * @plugindesc [v2.1.0] マップ／戦闘でフォグ（霧）演出を表示できます。
 * @author Gabe (Gabriel Nascimento)
 * @url https://github.com/comuns-rpgmaker/GabeMZ
 * 
 * @help Gabe MZ - Fog Effects（日本語化）
 * - このプラグインは zlib License で公開されています。
 * 
 * ■概要
 * マップおよび戦闘画面に、フォグ（霧・煙などのタイル状エフェクト）を表示できます。
 * 
 * ■使い方
 * 1) まずプラグインパラメータ「フォグ設定」で、使用するフォグ素材と挙動を登録します。
 * 2) 次に、以下のいずれかでフォグを追加します。
 *   - マップのメモ欄（注釈）にタグを記述
 *   - プラグインコマンドを使用
 * 
 * ■画像ファイルの配置
 * フォグ画像は img/fogs/ に配置してください。
 * 
 * ■プラグインコマンド
 * ・フォグ効果の設定
 *   指定レイヤーに、指定IDのフォグを設定／追加します。
 * 
 * ・レイヤーのフォグを削除
 *   指定レイヤーからフォグを削除します。
 * 
 * ・フォグ不透明度の設定
 *   指定レイヤーの不透明度を設定します（時間指定で遷移可能）。
 * 
 * ・フォグ色調の設定
 *   指定レイヤーの色調（トーン）を設定します（時間指定で遷移可能）。
 * 
 * ・画面クリア
 *   すべてのレイヤーのフォグを削除します。
 * 
 * ・マップでフォグ表示切替
 *   マップ上でフォグを表示するかを切り替えます。
 * 
 * ・戦闘でフォグ表示切替
 *   戦闘中にフォグを表示するかを切り替えます。
 * 
 * ■マップメモタグ
 * <addFog layer: id>
 *   指定レイヤーに、指定IDのフォグを追加します。
 * 
 * 例：
 *   <addFog 1: 1>  → レイヤー1にID1のフォグ
 *   <addFog 4: 3>  → レイヤー4にID3のフォグ
 *   <addFog 2: 7>  → レイヤー2にID7のフォグ
 * 
 * @param fogSettings
 * @text フォグ設定
 * @desc ゲームで使用するフォグ効果の設定一覧です（マップ／戦闘で共通）。
 * @type struct<fogSettingsStruct>[]
 * 
 * @param fogInMap
 * @text マップでフォグ表示？
 * @desc デフォルトでマップにフォグを表示するかを設定します。
 * @type boolean
 * @default true
 * 
 * @param fogInBattle
 * @text 戦闘でフォグ表示？
 * @desc デフォルトで戦闘中にフォグを表示するかを設定します。
 * @type boolean
 * @default true
 * 
 * @param fogOptionsMenuCommand
 * @text オプションメニュー
 * @default ===============================================
 * 
 * @param commandEnabled
 * @text オプション項目を有効化
 * @parent fogOptionsMenuCommand
 * @desc true の場合、オプションにフォグ表示のON/OFF項目を追加します。
 * @type boolean
 * @default true
 * 
 * @param commandName
 * @text オプション項目名
 * @parent fogOptionsMenuCommand
 * @desc オプションに表示する項目名を設定します。
 * @type text
 * @default Fog Effects
 * 
 * @command setFogEffect
 * @text フォグ効果の設定
 * @desc 指定レイヤーにフォグ効果を設定／追加します。
 * 
 * @arg settings
 * @text フォグ効果の設定
 * @desc 指定レイヤーにフォグ効果を設定／追加します。
 * @type struct<setFogStruct>[]
 * 
 * @command removeFogEffectLayer
 * @text レイヤーのフォグを削除
 * @desc 指定レイヤーからフォグ効果を削除します。
 * 
 * @arg fogLayer
 * @text 対象レイヤー
 * @desc フォグを削除するレイヤーIDを指定します。
 * @type number[]
 * @min 1
 * 
 * @command clearScreen
 * @text 画面クリア
 * @desc 画面上のすべてのフォグ効果を削除します。
 * 
 * @command setFogOpacity
 * @text フォグ不透明度の設定
 * @desc 指定レイヤーのフォグ不透明度を設定します。
 * 
 * @arg layer
 * @text レイヤー
 * @desc 対象のフォグレイヤーIDです。
 * @type number
 * @min 1
 * @default 1
 * 
 * @arg opacity
 * @text 不透明度
 * @desc 目標の不透明度（0〜255）です。
 * @type number
 * @min 0
 * @default 255
 * 
 * @arg time
 * @text 時間
 * @desc 変化にかける時間（フレーム）。0なら即時反映します。
 * @min 0
 * @default 0
 * 
 * @command setFogTone
 * @text フォグ色調の設定
 * @desc 指定レイヤーのフォグ色調（トーン）を設定します。
 * 
 * @arg layer
 * @text レイヤー
 * @desc 対象のフォグレイヤーIDです。
 * @type number
 * @min 1
 * @default 1
 * 
 * @arg tone
 * @text 色調（トーン）
 * @desc 目標の色調です。形式：[赤, 緑, 青, グレー]
 * @type text
 * @default [0, 0, 0, 0]
 * 
 * @arg time
 * @text 時間
 * @desc 変化にかける時間（フレーム）。0なら即時反映します。
 * @min 0
 * @default 0
 * 
 * @command setFogInMap
 * @text マップでフォグ表示切替
 * @desc マップ上でフォグを表示するかを設定します。
 * 
 * @arg fogInMap
 * @text マップでフォグ表示？
 * @desc マップ上でフォグを表示するかを設定します。
 * @type boolean
 * @default true
 * 
 * @command setFogInBattle
 * @text 戦闘でフォグ表示切替
 * @desc 戦闘中にフォグを表示するかを設定します。
 * 
 * @arg fogInBattle
 * @text 戦闘でフォグ表示？
 * @desc 戦闘中にフォグを表示するかを設定します。
 * @type boolean
 * @default true
 */

/*~struct~setFogStruct:
 * @param fogID
 * @text フォグ効果ID
 * @desc 使用するフォグ効果のIDです。
 * @type number
 * @min 1
 * 
 * @param fogLayer
 * @text フォグレイヤー
 * @desc 追加するレイヤーIDです。
 * @type number
 * @min 1
 */

/*~struct~fogSettingsStruct:
 * @param fogFilename
 * @text フォグ画像ファイル
 * @desc 使用するフォグ画像ファイル名です（img/fogs/）。
 * @type file
 * @dir img/fogs/
 * 
 * @param fogOpacity
 * @text フォグ不透明度
 * @desc 初期不透明度（0〜255）です。
 * @type number
 * @min 0
 * @max 255
 * @default 255
 * 
 * @param fogBlendMode
 * @text 合成方法
 * @desc フォグの合成方法です。
 * @type select
 * @option 通常
 * @value 0
 * @option 加算
 * @value 1
 * @option 減算
 * @value 2
 * @default 0
 * 
 * @param fogMoveX
 * @text 移動速度X
 * @desc フォグのX方向移動速度です。
 * @type number
 * @decimals 1
 * @min -50
 * @max 50
 * @default 0
 * 
 * @param fogMoveY
 * @text 移動速度Y
 * @desc フォグのY方向移動速度です。
 * @type number
 * @decimals 1
 * @min -50
 * @max 50
 * @default 0
 */


var Imported = Imported || {};
Imported.GMZ_FogEffects = true;

var GabeMZ                = GabeMZ || {};
GabeMZ.FogEffects         = GabeMZ.FogEffects || {};
GabeMZ.FogEffects.VERSION = [2, 1, 0];

(() => {

    const pluginName = "GabeMZ_FogEffects";
    const params = PluginManager.parameters(pluginName);

    GabeMZ.FogEffects.fogSettings = JSON.parse(params.fogSettings);
    GabeMZ.FogEffects.fogInMap    = params.fogInMap == "true";
    GabeMZ.FogEffects.fogInBattle = params.fogInBattle == "true";
    GabeMZ.FogEffects.commandEnabled = params.commandEnabled == "true";
    GabeMZ.FogEffects.commandName   = params.commandName;
    GabeMZ.FogEffects.fogList = [];
    GabeMZ.FogEffects.currentMap = 0;

    //-----------------------------------------------------------------------------
    // PluginManager
    //
    // The static class that manages the plugins.

    PluginManager.registerCommand(pluginName, "setFogEffect", args => {
        GabeMZ.FogEffects.tempFog = [];
        settings = JSON.parse(args.settings);
        settings.forEach( arg => {
            arg = JSON.parse(arg);
            GabeMZ.FogEffects.tempFog.push([parseInt(arg.fogID), parseInt(arg.fogLayer)]);
        });
        GabeMZ.FogEffects.needRefresh = true;
    });

    PluginManager.registerCommand(pluginName, "removeFogEffectLayer", args => {
        GabeMZ.FogEffects.tempFog = [];
        const layers = JSON.parse(args.fogLayer);
        layers.forEach(layer => {GabeMZ.FogEffects.tempFog.push([null, parseInt(layer)])});
        GabeMZ.FogEffects.needRefresh = true;
    });

    PluginManager.registerCommand(pluginName, "clearScreen", args => {
        GabeMZ.FogEffects.tempFog = {id: null, layer: null};
        GabeMZ.FogEffects.needRefresh = true;
    });

    PluginManager.registerCommand(pluginName, "setFogOpacity", args => {
        const layer = parseInt(args.layer);
        const opacity = parseInt(args.opacity);
        const time = parseInt(args.time);
        if (!GabeMZ.FogEffects.fogList[layer]) return;
        GabeMZ.FogEffects.fogList[layer].setOpacityTarget(opacity, time);
    });

    PluginManager.registerCommand(pluginName, "setFogTone", args => {
        const layer = parseInt(args.layer);
        const tone = JSON.parse(args.tone);
        const time = parseInt(args.time);
        if (!GabeMZ.FogEffects.fogList[layer]) return;
        GabeMZ.FogEffects.fogList[layer].setToneTarget(tone, time);
    });

    PluginManager.registerCommand(pluginName, "setFogInMap", args => {
        GabeMZ.FogEffects.fogInMap = JSON.parse(args.fogInMap);
        if (!GabeMZ.FogEffects.fogInMap) {
            GabeMZ.FogEffects.tempFog = {id: null, layer: null};
            GabeMZ.FogEffects.needRefresh = true;
        }
    });

    PluginManager.registerCommand(pluginName, "setFogInBattle", args => {
        GabeMZ.FogEffects.fogInBattle = JSON.parse(args.fogInBattle);
    });

    //-----------------------------------------------------------------------------
    // ImageManager
    //
    // The static class that loads images, creates bitmap objects and retains them.

    ImageManager.loadFogs = function(filename) {
        return this.loadBitmap("img/fogs/", filename);
    };

    //-----------------------------------------------------------------------------
    // Sprite_Fog
    //-----------------------------------------------------------------------------

    function Sprite_Fog() {
        this.initialize(...arguments);
    }

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

    Sprite_Fog.prototype.initialize = function() {
        TilingSprite.prototype.initialize.call(this, ...arguments);
        this.initMembers();
    };

    Sprite_Fog.prototype.initMembers = function() {
        this.constX = 0;
        this.constY = 0;
        this._opacityTarget = 0;
        this._opacityTime = 0;
        this._tone = [0, 0, 0, 0];
        this._toneTarget = [0, 0, 0, 0];
        this._toneTime = 0;
    };

    Sprite_Fog.prototype.update = function() {
        TilingSprite.prototype.update.call(this);
        this._updateMovement();
        this._updateColorFilter();
        this._updateOpacity();
        this._updateTone();
    };

    Sprite_Fog.prototype._updateMovement = function() {
        this.constX += this.speedX;
        this.origin.x = ($gameMap.displayX() * $gameMap.tileWidth()) + this.constX - 96;
        this.constY += this.speedY;
        this.origin.y = ($gameMap.displayY() * $gameMap.tileHeight()) + this.constY - 96;
    }

    Sprite_Fog.prototype._updateColorFilter = function() {
        if (!this._colorFilter) this.createColorFilter();
        if (this._colorFilter.uniforms.colorTone.toString() != this._tone.toString()) {
            this._colorFilter.setColorTone(this._tone);
        }
    };

    Sprite_Fog.prototype._updateOpacity = function() {
        if (this._opacityTime > 0) {
            this.opacity += this._opacityTarget;
            this._opacityTime--;
        }
    };

    Sprite_Fog.prototype._updateTone = function() {
        if (this._toneTime > 0) {
            const d = this._toneTime;
            for (let i = 0; i < 4; i++) {
                this._tone[i] = (this._tone[i] * (d - 1) + this._toneTarget[i]) / d;
            }
            this._toneTime--;
        }
    };

    Sprite_Fog.prototype.createColorFilter = function() {
        this._colorFilter = new ColorFilter();
        if (!this.filters) {
            this.filters = [];
        }
        this.filters.push(this._colorFilter);
    };

    Sprite_Fog.prototype.setOpacityTarget = function(opacity, time) {
        this._opacityTime = time;
        this._opacityTarget = (opacity - this.opacity) / time;
        if (this._opacityTime === 0) {
            this.opacity = opacity;
        }
    };

    Sprite_Fog.prototype.setToneTarget = function(tone, time) {
        this._toneTime = time;
        this._toneTarget = tone.clone();
        if (this._toneTime === 0) {
            this._tone = this._toneTarget.clone();
        } 
    };

    //-----------------------------------------------------------------------------
    // Spriteset_Fog
    //
    // The set of sprites of fogs used in map and battle.

    function Spriteset_Fog() {
        this.initialize(...arguments);
    }

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

    Spriteset_Fog.prototype.initialize = function() {
        Sprite.prototype.initialize.call(this);
        this.setFrame(0, 0, Graphics.width, Graphics.height);
        this.createFogList();  
    };

    Spriteset_Fog.prototype.createFogList = function() {
        if (GabeMZ.FogEffects.currentMap == $gameMap.mapId()) {
            GabeMZ.FogEffects.fogList.forEach( (fog, id) => {
                if (fog) this.createFog(fog.id, id, fog);
            });
        } else {
            GabeMZ.FogEffects.currentMap = $gameMap.mapId()
            this.clearList();
            const reg = /<addFog\s*(\d+):\s*(\d+)>/g;
            let match;
            while (match = reg.exec($dataMap.note)) {
                this.createFog(match[2], match[1]);
            }
        }
    };

    Spriteset_Fog.prototype.update = function() {
        Sprite.prototype.update.call(this);
        if (GabeMZ.FogEffects.needRefresh) this.refreshFogList();
    };

    Spriteset_Fog.prototype.createFog = function(id, layer, fog) {
        if (layer < 1) return;
        const fogSetting = JSON.parse(GabeMZ.FogEffects.fogSettings[id - 1]);
        const sprite = new Sprite_Fog(ImageManager.loadFogs(fogSetting.fogFilename));
        sprite.move(-96, -96, Graphics.width + 192, Graphics.height + 192);
        sprite.opacity = fog ? fog.opacity : parseInt(fogSetting.fogOpacity);
        sprite.blendMode = parseInt(fogSetting.fogBlendMode);
        sprite.origin.x = -96;
        sprite.origin.y = -96;
        sprite.speedX = -parseFloat(fogSetting.fogMoveX);
        sprite.speedY = -parseFloat(fogSetting.fogMoveY);
        sprite.id = id;
        sprite.z = layer;
        if (fog) {
            sprite.constX = fog.constX;
            sprite.constY = fog.constY;
            sprite._opacityTarget = fog._opacityTarget;
            sprite._opacityTime = fog._opacityTime;
            sprite._tone = fog._tone;
            sprite._toneTarget = fog._toneTarget;
            sprite._toneTime = fog._toneTime;
        }
        this.addChild(sprite); 
        this._sortChildren();
        GabeMZ.FogEffects.fogList[layer] = sprite;
    };

    Spriteset_Fog.prototype._sortChildren = function() {
        this.children.sort(this._compareChildOrder.bind(this));
    };
    
    Spriteset_Fog.prototype._compareChildOrder = function(a, b) {
        if (a.z !== b.z) {
            return a.z - b.z;
        } else if (a.y !== b.y) {
            return a.y - b.y;
        } else {
            return a.spriteId - b.spriteId;
        }
    };

    Spriteset_Fog.prototype.refreshFogList = function() {
        if (GabeMZ.FogEffects.tempFog.length > 0) {
            GabeMZ.FogEffects.tempFog.forEach(fog => {
                this.removeChild(GabeMZ.FogEffects.fogList[fog[1]]); 
                GabeMZ.FogEffects.fogList[fog[1]] = null;
                if (fog[0]) this.createFog(fog[0], fog[1]);
            });
        } else {
            this.clearList();
        }
        GabeMZ.FogEffects.needRefresh = false;
    };

    Spriteset_Fog.prototype.clearList = function() {
        GabeMZ.FogEffects.fogList.forEach(fog => {this.removeChild(fog)});
        GabeMZ.FogEffects.fogList = [];
    };

    //-----------------------------------------------------------------------------
    // Spriteset_Base
    //
    // The superclass of Spriteset_Map and Spriteset_Battle.

    Spriteset_Base.prototype.createFogLayer = function() {
        if (!ConfigManager.fogEffects) return;
        this._fogLayer = new Spriteset_Fog();
        this._baseSprite.addChild(this._fogLayer);
    };

    //-----------------------------------------------------------------------------
    // Spriteset_Map
    //
    // The set of sprites on the map screen.

    const _Spriteset_Map_createWeather = Spriteset_Map.prototype.createWeather;
    Spriteset_Map.prototype.createWeather = function() {
        if (GabeMZ.FogEffects.fogInMap) this.createFogLayer();
        _Spriteset_Map_createWeather.call(this);
    };

    //-----------------------------------------------------------------------------
    // Spriteset_Battle
    //
    // The set of sprites on the battle screen.

    const _Spriteset_Battle_createLowerLayer = Spriteset_Battle.prototype.createLowerLayer;
    Spriteset_Battle.prototype.createLowerLayer = function() {
        _Spriteset_Battle_createLowerLayer.call(this);
        if (GabeMZ.FogEffects.fogInBattle) this.createFogLayer();
    };

    //-----------------------------------------------------------------------------
    // Window_Options
    //
    // The window for changing various settings on the options screen.

    const _Window_Options_addGeneralOptions = Window_Options.prototype.addGeneralOptions;
    Window_Options.prototype.addGeneralOptions = function() {
        _Window_Options_addGeneralOptions.call(this);
        if (GabeMZ.FogEffects.commandEnabled) this.addFogOptions();
    };

    Window_Options.prototype.addFogOptions = function() {
        this.addCommand(GabeMZ.FogEffects.commandName, "fogEffects");
    };

    //-----------------------------------------------------------------------------
    // ConfigManager
    //
    // The static class that manages the configuration data.

    ConfigManager.fogEffects = true;

    const _ConfigManager_makeData = ConfigManager.makeData;
    ConfigManager.makeData = function() {
        const config = _ConfigManager_makeData.call(this);
        config.fogEffects = this.fogEffects;
        return config;
    };

    const _ConfigManager_applyData = ConfigManager.applyData;
    ConfigManager.applyData = function(config) {
        _ConfigManager_applyData.call(this, config);
        this.fogEffects = this.readFlag(config, "fogEffects", true);
    };

})();