//=============================================================================
// MPP_EncounterEffect.js
//=============================================================================
// Copyright (c) 2021 Mokusei Penguin
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
//=============================================================================

/*:
 * @target MV MZ
 * @plugindesc 遇敌战斗转场动画 - 将转场时的效果更改为特殊效果。
 * @author Mokusei Penguin
 * @url 
 *
 * @help [version 3.3.3]
 * 这个插件是RPG Maker MV和MZ通用的
 * 
 * ▼ 插件指令
 *  
    在要输入数值的项中，将值改成你需要的。
 *  
 *  〇 MV / MZ
 *  
 *  〇 EncEffType type  / setType
 *       type : Effect type
 *   - 效果类型将更改为指定的数字，但是只执行一次。
 * 
 *  〇 EncEffChar evId  / setCharacter
 *       evId : 作为效果核心的事件的ID
 * -效果将围绕指定的evId执行
 *  接下来只有一次。(-1:玩家，0:此事件，1-:指定ID的事件)
 * -此设置仅对效果类型1有效。
 *   
 *  〇 EncEffColor r g b  / setColor
 *       r g b : Effect color(RGB)
 *   - 下次动画的颜色，只执行一次
 * 
 * ▼ 插件指令补充 (MZ)
 *  在要输入数值的项中，选择“文本”和
    *写v[N]表示变量N。
 * 
 * ▼ Effect type
 *  0 : 默认
 *  1 : 围绕角色展开
 *  2 : 从屏幕左侧中断
 *  3 : 整个屏幕分散到前面
 * 
 * ▼ Other
 *  - 类型1的话，如果没有指定主要字符，
 * [玩家]将成为随机遭遇战的中心，
 * 和[本次事件]将是事件的中心
 * 事件命令[战斗处理]
 *  - 当在移动设备上执行时，一些效果将会
 * 因减重而省略。
 * 
 * ================================
 * Mail : wood_penguin＠yahoo.co.jp (＠ is half-width)
 * Blog : http://woodpenguin.blog.fc2.com/
 * License : MIT license
 * 
 *  @command setType
 *      @desc 只在下次战斗中更改效果类型。
 *      @arg type
 *          @desc 
 *          @type number
 *              @min 0
 *              @max 3
 *          @default 1
 *
 *
 *  @command setCharacter
 *      @desc 指定下一个效果的主要字符。
 * 此设置仅对效果类型1有效。
 *      @arg character
 *          @desc -1:玩家, 0:本次事件, 1-:指定ID的事件
 *          @type number
 *              @min -1
 *              @max 999
 *          @default 0
 *
 *  @command setColor
 *      @desc 指定下一个效果的颜色。
 *      @arg color
 *          @desc R,G,B
 *          @default 255,255,255
 *
 * 
 *  @param Effect Type
 *  @text 效果类型ID
 *  @desc 填入数字 0 : 默认 1 : 围绕角色展开
 *  2 : 从屏幕左侧中断 3 : 整个屏幕分散到前面
 *      @type number
 *          @min 0
 *          @max 3
 *      @default 1
 * 
 *  @param Effect Color
 *  @text 效果颜色
 *      @desc 输入 R,G,B 应该懂的吧
 *      @default 255,255,255
 * 
 */

(() => {
    'use strict';
    
    const pluginName = 'MPP_EncounterEffect';
    
    // Plugin Parameters
    const parameters = PluginManager.parameters(pluginName);
    
    const param_EffectType = Number(parameters['Effect Type'] || 2);
    const param_DefaultColor = parameters['Effect Color'].split(',').map(Number);
    
    // Dealing with other plugins
    const _importedPlugin = (...names) => {
        return names.some(name => PluginManager._scripts.includes(name));
    };
    const pluginOptions = [
        'MPP_EncounterEffect_Op1',
        'MPP_EncounterEffect_Op2'
    ];
    
    // Database
    const DATABASE = [
        null,
        {// Type 1
            'Shape Type':'square',
            'Break Duration':45,
            'Interval Duration':45,
            'Scatter Duration':0,
            'Move Duration':60,
            'Split Radial':8,
            'Radial Random Rate':90,
            'Circle Radius':96,
            'Circle Increase Rate':150,
            'Circle Random Rate':40
        },
        {// Type 2
            'Shape Type':'random',
            'Break Direction':'left',
            'Break Duration':30,
            'Interval Duration':45,
            'Scatter Direction':'left',
            'Scatter Duration':35,
            'Move Duration':70,
            'Split X':7,
            'X Random Rate':80,
            'Split Y':5,
            'Y Random Rate':80
        },
        {// Type 3
            'Shape Type':'triangle',
            'Break Direction':'inside',
            'Break Duration':35,
            'Interval Duration':40,
            'Scatter Duration':16,
            'Move Duration':100,
            'Split X':8,
            'X Random Rate':80,
            'Split Y':6,
            'Y Random Rate':80
        }
    ];
    
    //-------------------------------------------------------------------------
    // Array
    
    if (!Array.prototype.flat) {
        Array.prototype.flat = function (depth) {
            const rec = (flattend, array, depth) => {
                for (const elem of array) {
                    if (Array.isArray(elem) && depth > 0) {
                        rec(flattend, elem, depth - 1);
                    } else {
                        flattend.push(elem);
                    }
                }
                return flattend;
            };
            return rec([], this, depth !== undefined ? Math.floor(depth) : 1);
        };
    }

    //-------------------------------------------------------------------------
    // EncounterEffect

    function EncounterEffect() {
        throw new Error('This is a static class');
    }
    
    if (_importedPlugin(...pluginOptions)) {
        window.EncounterEffect = EncounterEffect;
    }
    
    EncounterEffect.snap = null;
    EncounterEffect.center = new Point();
    EncounterEffect.opacity = 0;
    EncounterEffect._fragments = null;
    EncounterEffect._type = param_EffectType;
    EncounterEffect._character = null;
    EncounterEffect._color = param_DefaultColor;
    EncounterEffect._effectDelay = 0;

    EncounterEffect.clear = function() {
        this.snap = null;
        this.opacity = 0;
        this._fragments = null;
        this._type = param_EffectType;
        this._character = null;
        this._color = param_DefaultColor;
        this._effectDelay = 0;
    };

    EncounterEffect.destroy = function() {
        this.snap.destroy();
        this._fragments.forEach(fragment => fragment.destroy());
    };

    EncounterEffect.params = function() {
        return DATABASE[this._type];
    };

    EncounterEffect.zoom = function() {
        return Utils.isMobileDevice() ? 4 : 2;
    };

    EncounterEffect.setup = function(snap) {
        this.snap = snap;
        this.opacity = 255;
        this._opacityDuration = 0;
        this._fragments = [];
        this._params = this.params();
        this.setupwindowBounds();
        this.setupCenter();
        this.createPolygons();
        this.startEffectDelay();
    };

    EncounterEffect.setupwindowBounds = function() {
        this.windowBounds = {
            minX:0,
            maxX:Graphics.width,
            minY:0,
            maxY:Graphics.height
        };
    };

    EncounterEffect.setupCenter = function() {
        const bounds = this.windowBounds;
        const char = this.centerCharacter();
        const cx = Math.round(char.screenX()).clamp(bounds.minX, bounds.maxX);
        const cy = Math.round(char.screenY() - 24)
                       .clamp(bounds.minY, bounds.maxY);
        this.center.set(cx, cy);
    };

    EncounterEffect.createPolygons = function() {
        switch (this._type) {
            case 1:
                this.createPolygonsT1();
                break;
            case 2:
            case 3:
                this.createPolygonsT2();
                break;
        }
    };

    EncounterEffect.setType = function(type) {
        this._type = type;
    };

    EncounterEffect.type = function() {
        return this._type;
    };

    EncounterEffect.setColor = function(...color) {
        this._color = color;
    };

    EncounterEffect.color = function() {
        return this._color;
    };

    EncounterEffect.setCharacter = function(character) {
        this._character = character;
    };

    EncounterEffect.centerCharacter = function() {
        return this._character
            ? this._character
            : $gameMap.getInterpreterCharacter(0) || $gamePlayer;
    };

    EncounterEffect.createPolygonsT1 = function() {
        const sType = this._params['Shape Type'] || 'square';
        const allPoints = this.createAllPointsT1();
        
        this.iteratePointT1(allPoints, (p1, p2, p3, p4) => {
            const polygon = [];
            if (p2) {
                if (this.isOutPolygon(p1, p2)) return;
                if (sType === 'square') {
                    polygon.push(p1, p2);
                } else {
                    this.addPolygon(this.uniqPolygon([p1, p2, p3]));
                    polygon.push(p1);
                }
            } else {
                polygon.push(p1);
            }
            if (this.setCorrectPoints(polygon, p3, p4)) {
                this.addPolygon(this.uniqPolygon(polygon));
            }
        });
    };
    
    EncounterEffect.iteratePointT1 = function(allPoints, callback) {
        const splitX = this._params['Split Radial'] || 8;
        this._cornerFlag = [null, null, null, null];
        for (let i = 0; i < allPoints.length; i++) {
            const inPoints = i > 0 ? allPoints[i - 1] : null;
            const outPoints = allPoints[i];
            for (let j = 0; j < splitX; j++) {
                const point3 = outPoints[j];
                const point4 = outPoints[(j + 1) % splitX];
                if (i > 0) {
                    const point1 = inPoints[(j + 1) % splitX];
                    const point2 = inPoints[j];
                    callback(point1, point2, point3, point4);
                } else {
                    callback(this.center, null, point3, point4);
                }
            }
            if (!this._cornerFlag.includes(null)) break;
        }
    };

    EncounterEffect.uniqPolygon = function(polygon) {
        return polygon.filter((point1, i) => {
            return polygon.findIndex( point2 => point2.equals(point1) ) === i;
        });
    };

    EncounterEffect.createAllPointsT1 = function() {
        const {
            'Shape Type': sType = 'square',
            'Split Radial': splitX = 8,
            'Circle Radius': baseHeight = 96,
            'Circle Increase Rate': circleRate = 150
        } = this._params;
        const maxRadius = this.maxRadius();

        const allPoints = [];
        let startR = Math.random() * 2;
        let baseR = 0;
        let height = baseHeight;
        for (let i = 0; i < 8; i++) {
            if (sType === 'triangle') startR += 1 / splitX;
            if (i === 7) baseR = 1000000;
            allPoints[i] = this.createRoundPoints(startR, baseR, height);
            if (baseR > maxRadius) break;
            baseR += height;
            height *= circleRate / 100;
        }
        return allPoints;
    };

    EncounterEffect.createRoundPoints = function(startR, baseR, height) {
        const {
            'Split Radial': splitX = 8,
            'Radial Random Rate': rRate = 90,
            'Circle Random Rate': cRate = 40
        } = this._params;
        const baseAngle = 2 / splitX;
        
        const randomAngle = (n) => {
            return startR + baseAngle * n +
                    baseAngle * rRate * Math.random() / 100;
        };
        const randomRadius = () => {
            return baseR + height * (1 - cRate * (0.75 - Math.random()) / 100);
        };
        
        const points = [];
        for (let i = 0; i < splitX; i++) {
            points[i] = this.calculatePoint(randomAngle(i) % 2, randomRadius());
        }
        return points;
    };

    EncounterEffect.calculatePoint = function(angle, radius) {
        const bounds = this.windowBounds;
        const { x: cx, y: cy } = this.center;
        let sx = radius * Math.cos(angle * Math.PI);
        let sy = radius * Math.sin(angle * Math.PI);
        if (sx !== 0 && cx + sx < bounds.minX) {
            sy *= (bounds.minX - cx) / sx;
            sx = bounds.minX - cx;
        }
        if (sx !== 0 && cx + sx > bounds.maxX) {
            sy *= (bounds.maxX - cx) / sx;
            sx = bounds.maxX - cx;
        }
        if (sy !== 0 && cy + sy < bounds.minY) {
            sx *= (bounds.minY - cy) / sy;
            sy = bounds.minY - cy;
        }
        if (sy !== 0 && cy + sy > bounds.maxY) {
            sx *= (bounds.maxY - cy) / sy;
            sy = bounds.maxY - cy;
        }
        return new Point(Math.round(cx + sx), Math.round(cy + sy));
    };

    EncounterEffect.maxRadius = function() {
        const { width, height } = Graphics;
        const { x: cx, y: cy } = this.center;
        const deltaX = cx < width / 2 ? width - cx : cx;
        const deltaY = cy < height / 2 ? height - cy : cy;
        return Math.hypot(deltaX, deltaY);
    };

    EncounterEffect.createPolygonsT2 = function() {
        const {
            'Shape Type': sType = 'square',
            'Break Direction': direction = 'left'
        } = this._params;
        switch (sType) {
            case 'square':
                this.createPolygonsT2Square();
                break;
            case 'triangle':
                this.createPolygonsT2Triangle();
                break;
            case 'random':
                this.createPolygonsT2Random();
                break;
        }
        this.sortFragment(direction);
    };

    EncounterEffect.createPolygonsT2Square = function() {
        const allPoints = this.createAllPointsT2Square();
        this.iteratePointT2(allPoints, (...points) => {
            this.addPolygon(points);
        });
    };

    EncounterEffect.iteratePointT2 = function(allPoints, callback) {
        const {
            'Split X': splitX = 12,
            'Split Y': splitY = 9
        } = this._params;
        for (let i = 0; i < splitX; i++) {
            const leftPoints = allPoints[i];
            const rightPoints = allPoints[i + 1];
            for (let j = 0; j < splitY; j++) {
                const point1 = leftPoints[j + 1];
                const point2 = leftPoints[j];
                const point3 = rightPoints[j];
                const point4 = rightPoints[j + 1];
                callback(point1, point2, point3, point4);
            }
        }
    };

    EncounterEffect.createAllPointsT2Square = function() {
        const {
            'Split X': splitX = 12,
            'Split Y': splitY = 9,
            'X Random Rate': xRate = 80,
            'Y Random Rate': yRate = 80
        } = this._params;
        const bounds = this.windowBounds;
        const width = Graphics.width / Math.max(splitX - xRate / 100, 1);
        const height = Graphics.height / Math.max(splitY - yRate / 100, 1);
        
        const pointX = (i) => {
            if (i === 0) return bounds.minX;
            if (i === splitX) return bounds.maxX;
            return this.randomValue(width, i, xRate);
        };
        const pointY = (j) => {
            if (j === 0) return bounds.minY;
            if (j === splitY) return bounds.maxY;
            return this.randomValue(height, j, yRate);
        };
        
        const allPoints = [];
        for (let i = 0; i <= splitX; i++) {
            const points = [];
            for (let j = 0; j <= splitY; j++) {
                points[j] = new Point(pointX(i), pointY(j));
            }
            allPoints[i] = points;
        }
        return allPoints;
    };

    EncounterEffect.randomValue = function(value, index, rate) {
        return Math.round(value * (index - rate * Math.random() / 100));
    };

    EncounterEffect.createPolygonsT2Triangle = function() {
        const allPoints = this.createAllPointsT2Triangle();
        this.iteratePointT2(allPoints, (p1, p2, p3, p4) => {
            if (p2.y > p3.y || (p2.y === p3.y && p1.y > p4.y)) {
                this.addPolygon([p1, p2, p4]);
                this.addPolygon([p2, p3, p4]);
            } else {
                this.addPolygon([p1, p2, p3]);
                this.addPolygon([p1, p3, p4]);
            }
        });
    };

    EncounterEffect.createAllPointsT2Triangle = function() {
        const {
            'Split X': splitX = 12,
            'Split Y': splitY = 9,
            'X Random Rate': xRate = 80,
            'Y Random Rate': yRate = 80
        } = this._params;
        const bounds = this.windowBounds;
        const width = Graphics.width / Math.max(splitX - xRate / 100, 1);
        const height = Graphics.height / Math.max(splitY - 0.5, 1);
        const bottomY = height / 2 * yRate / 100;
        
        const pointX = (i) =>{
            if (i === 0) return bounds.minX;
            if (i === splitX) return bounds.maxX;
            return this.randomValue(width, i, xRate);
        };
        const pointY = (i, j) => {
            if (j === 0) return bounds.minY;
            if (j === splitY) return bounds.maxY;
            let py = bottomY;
            if (i % 2 === 1) {
                if (j === 1) return this.randomValue(height / 2, j, yRate);
                py -= height / 2;
            }
            return py + this.randomValue(height, j, yRate);
        };
        
        const allPoints = [];
        for (let i = 0; i <= splitX; i++) {
            const points = [];
            for (let j = 0; j <= splitY; j++) {
                points[j] = new Point(pointX(i), pointY(i, j));
            }
            allPoints[i] = points;
        }
        return allPoints;
    };

    EncounterEffect.createPolygonsT2Random = function() {
        const allBasePolygons = this.createBasePolygonsT2Random();
        
        const sidePolygon = (n, m, pos) => {
            const si = n + (pos === 0 || pos === 1 ? -1 : 1);
            const subPolygons = allBasePolygons[si];
            if (subPolygons) {
                const sj = Math.floor(m / 2) * 2;
                const polygon2 = subPolygons[sj];
                if (Math.floor(pos / 2) === Math.floor(polygon2.pos / 2)) {
                    return subPolygons[sj + 1];
                }
                return polygon2;
            }
            return null;
        };
        
        allBasePolygons.forEach((mainPolygons, i) => {
            mainPolygons.forEach((polygon1, j) => {
                if (polygon1.state === 'none') {
                    const d = Math.randomInt(3);
                    let polygon2;
                    if (d === 0)      polygon2 = mainPolygons[j - 1];
                    else if (d === 1) polygon2 = mainPolygons[j + 1];
                    else          polygon2 = sidePolygon(i, j, polygon1.pos);
                    if (polygon2 && polygon2.state === 'none') {
                        this.joinPolygon(polygon1, polygon2, d);
                    }
                }
            });
        });
        allBasePolygons.flat().forEach(polygon => {
            if (polygon.state === 'none' || polygon.state === 'received') {
                this.addPolygon(polygon.polygon);
            }
        });
    };

    EncounterEffect.createBasePolygonsT2Random = function() {
        const allPoints = this.createAllPointsT2Square();
        const polygons = [];
        this.iteratePointT2(allPoints, (p1, p2, p3, p4) => {
            if (Math.random() < 0.5) {
                polygons.push(
                    { polygon:[p1, p2, p3], pos:0, state:'none' },
                    { polygon:[p1, p3, p4], pos:3, state:'none' }
                );
            } else {
                polygons.push(
                    { polygon:[p2, p3, p4], pos:2, state:'none' },
                    { polygon:[p1, p2, p4], pos:1, state:'none' }
                );
            }
        });
        
        const splitY = this._params['Split Y'] || 9;
        const allPolygons = [];
        while (polygons.length > 0) {
            allPolygons.push(polygons.splice(0, splitY * 2));
        }
        return allPolygons;
    };

    EncounterEffect.joinPolygon = function(polygon1, polygon2, d) {
        const pos1 = polygon1.pos;
        const pos2 = polygon2.pos;
        let start = 0, index = 0;
        if (d === 0) {
            start = pos1 === 0 || pos1 === 1 ? 2 : 1;
            index = 1;
        } else if (d === 1) {
            start = 0;
            index = pos1 === 0 ? 2 : pos1 === 2 ? 0 : pos2 === 0 ? 0 : 2;
        } else {
            start = pos1 === 0 || pos1 === 1 ? 1 : 2;
            index = pos2 === 0 || pos2 === 1 ? 2 : 0;
        }
        polygon1.polygon.splice(start, 0, polygon2.polygon[index]);
        polygon1.state = 'received';
        polygon2.state = 'joined';
    };

    EncounterEffect.addPolygon = function(polygon) {
        const fragment = new Encounter_Fragment(polygon);
        if (fragment.isValid()) {
            fragment.setup(this.snap);
            this._fragments.push(fragment);
        }
    };

    EncounterEffect.isOutPolygon = function(pos1, pos2) {
        const bounds = this.windowBounds;
        if (pos1.x === bounds.minX && pos2.x === bounds.minX) return true;
        if (pos1.x === bounds.maxX && pos2.x === bounds.maxX) return true;
        if (pos1.y === bounds.minY && pos2.y === bounds.minY) return true;
        if (pos1.y === bounds.maxY && pos2.y === bounds.maxY) return true;
        return false;
    };
    
    EncounterEffect.setCorrectPoints = function(polygon, pos1, pos2) {
        const bounds = this.windowBounds;
        polygon.push(pos1);
        if (pos1.x === bounds.minX && pos2.y === bounds.minY) {
            if (!this.pushCorner(polygon, 0)) return false;
        } else if (pos1.y === bounds.maxY && pos2.x === bounds.minX) {
            if (!this.pushCorner(polygon, 1)) return false;
        } else if (pos1.y === bounds.minY && pos2.x === bounds.maxX) {
            if (!this.pushCorner(polygon, 2)) return false;
        } else if (pos1.x === bounds.maxX && pos2.y === bounds.maxY) {
            if (!this.pushCorner(polygon, 3)) return false;
        } else if (pos1.x === bounds.minX && pos2.x === bounds.maxX) {
            if (!this.pushCorner(polygon, 0)) return false;
            if (!this.pushCorner(polygon, 2)) return false;
        } else if (pos1.y === bounds.maxY && pos2.y === bounds.minY) {
            if (!this.pushCorner(polygon, 1)) return false;
            if (!this.pushCorner(polygon, 0)) return false;
        } else if (pos1.y === bounds.minY && pos2.y === bounds.maxY) {
            if (!this.pushCorner(polygon, 2)) return false;
            if (!this.pushCorner(polygon, 3)) return false;
        } else if (pos1.x === bounds.maxX && pos2.x === bounds.minX) {
            if (!this.pushCorner(polygon, 3)) return false;
            if (!this.pushCorner(polygon, 1)) return false;
        }
        polygon.push(pos2);
        return true;
    };
    
    EncounterEffect.pushCorner = function(polygon, c) {
        if (!this._cornerFlag[c]) {
            polygon.push(this.getCornerPoint(c));
            this._cornerFlag[c] = polygon;
            return true;
        }
        return false;
    };

    EncounterEffect.getCornerPoint = function(c) {
        const bounds = this.windowBounds;
        return new Point(
            c === 0 || c === 1 ? bounds.minX : bounds.maxX,
            c === 0 || c === 2 ? bounds.minY : bounds.maxY
        );
    };

    EncounterEffect.sortFragment = function(direction) {
        const formula = this.compareFormula(direction);
        if (formula) this._fragments.sort((a, b) => formula(a) - formula(b));
    };

    EncounterEffect.compareFormula = function(direction) {
        const cx = Graphics.width / 2;
        const cy = Graphics.height / 2;
        switch (direction) {
            case 'left':
                return (p) => {
                    return p.x;
                };
            case 'center':
                return (p) => {
                    return Math.abs(cx - p.x);
                };
            case 'right':
                return (p) => {
                    return -p.x;
                };
            case 'inside':
                return (p) => {
                    return Math.abs(cx - p.x) + Math.abs(cy - p.y) * cy / cx;
                };
            case 'outside':
                return (p) => {
                    return -Math.abs(cx - p.x) - Math.abs(cy - p.y) * cy / cx;
                };
        }
        return null;
    };

    EncounterEffect.breakFragments = function(start, end, duration) {
        this._fragments.slice(start, end).forEach(
            fragment => fragment.onBreak(this._color, duration)
        );
    };

    EncounterEffect.onClip = function(polygon, rect) {
        const context = this.snap.context;
        context.save();
        context.beginPath();
        polygon.forEach((point, i) => {
            i === 0
                ? context.moveTo(point.x, point.y)
                : context.lineTo(point.x, point.y);
        });
        context.closePath();
        context.clip();
        context.clearRect(rect.x, rect.y, rect.width, rect.height);
        context.restore();
        this.snap._baseTexture.update();
    };

    EncounterEffect.onBreakEnd = function() {
        this.snap.fillAll('black');
        this._fragments.forEach(fragment => fragment.clearClip());
    };

    EncounterEffect.onBattleStart = function() {
        const {
            'Move Duration': duration = 60,
            'Scatter Duration': delay = 0
        } = this._params;
        this.startFragments(duration);
        this.setFragmentsDelay(delay);
        this._opacityDuration = duration;
    };

    EncounterEffect.startFragments = function(duration) {
        switch (this._type) {
            case 1:
                this._fragments.forEach(f => f.onBattleStartT1(duration));
                this.opacity = 300;
                break;
            case 2: {
                const d = this._params['Scatter Direction'] || 'left';
                this.sortFragment(d);
                this._fragments.forEach(f => f.onBattleStartT2(duration, d));
                this.opacity = 640;
                break;
            }
            case 3:
                this.sortFragment('inside');
                this._fragments.forEach(f => f.onBattleStartT3(duration));
                this.opacity = 640;
                break;
        }
    };

    EncounterEffect.setFragmentsDelay = function(delay) {
        const maxItems = this.maxItems();
        this._fragments.forEach((fragment, i) => {
            fragment.setDelay(Math.floor(delay * i / maxItems ));
        });
    };

    EncounterEffect.update = function() {
        if (this.isRunning()) {
            this._fragments.forEach(fragment => fragment.update());
            this.updateOpacity();
            if (this.opacity === 0) {
                this.destroy();
                this.clear();
            }
        }
    };

    EncounterEffect.updateOpacity = function() {
        if (this._opacityDuration > 0) {
            const d = this._opacityDuration;
            this.opacity *= (d - 1) / d;
            this._opacityDuration--;
        }
    };

    EncounterEffect.maxItems = function() {
        return this._fragments ? this._fragments.length : 0;
    };

    EncounterEffect.fragments = function() {
        return this._fragments || [];
    };

    EncounterEffect.isValid = function() {
        return !!this.params();
    };

    EncounterEffect.isRunning = function() {
        return !!this._fragments;
    };

    EncounterEffect.isReady = function() {
        return !this.isRunning() || this._opacityDuration < 45;
    };

    EncounterEffect.breakDuration = function() {
        return this._params ? this._params['Break Duration'] || 45 : 45;
    };

    EncounterEffect.effectSpeed = function() {
        const d = this._params ? this._params['Interval Duration'] || 45 : 45;
        return this.breakDuration() + d;
    };

    EncounterEffect.fadeSpeed = function() {
        return this._params
            ? Math.min(this._params['Move Duration'] || 60, 60)
            : 60;
    };

    EncounterEffect.startEffectDelay = function() {
        this._effectDelay = 4;
    };

    EncounterEffect.updateEffectDelay = function() {
        if (this._effectDelay > 0) this._effectDelay--;
    };

    EncounterEffect.isEffectReady = function() {
        return this._effectDelay === 0;
    };

    //-------------------------------------------------------------------------
    // Encounter_Fragment

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

    if (_importedPlugin(...pluginOptions)) {
        window.Encounter_Fragment = Encounter_Fragment;
    }

    Encounter_Fragment.prototype.initialize = function(polygon) {
        this._polygon = polygon;
        this.initRect();
        this._flash = false;
        this._clip = false;
        this._delay = 0;
        this.visible = false;
    };

    Encounter_Fragment.prototype.initRect = function() {
        const polygon = this._polygon;
        const allX = polygon.map( point => point.x );
        const allY = polygon.map( point => point.y );
        const rect = new Rectangle();
        rect.x = Math.min(...allX);
        rect.y = Math.min(...allY);
        rect.width = Math.max(...allX) - rect.x;
        rect.height = Math.max(...allY) - rect.y;
        this._rect = rect;
    };

    Encounter_Fragment.prototype.createBitmap = function() {
        const rect = this._rect;
        const zoom = EncounterEffect.zoom();
        const bw = Math.ceil(rect.width / zoom) + 2;
        const bh = Math.ceil(rect.height / zoom) + 2;
        this.bitmap = new Bitmap(bw, bh);
        this.bitmap.smooth = true;
    };

    Encounter_Fragment.prototype.initPolygon = function() {
        const polygon = this._polygon;
        const rect = this._rect;
        const zoom = EncounterEffect.zoom();
        const context = this.bitmap.context;
        context.translate(-rect.x / zoom + 1, -rect.y / zoom + 1);
        context.beginPath();
        polygon.forEach((point, i) => {
            i === 0
                ? context.moveTo(point.x / zoom, point.y / zoom)
                : context.lineTo(point.x / zoom, point.y / zoom);
        });
        context.closePath();
    };

    Encounter_Fragment.prototype.initPosition = function() {
        const rect = this._rect;
        this.x = rect.x + Math.floor(rect.width / 2);
        this.y = rect.y + Math.floor(rect.height / 2);
        this.jumpHeight = 0;
        this._targetX = 0;
        this._targetY = 0;
        this._zoom = EncounterEffect.zoom();
        this._targetZoom = this._zoom;
        this._moveDuration = 0;
    };

    Encounter_Fragment.prototype.initRotation = function() {
        this._speed = 0;
        this._pace = 0;
        this._rotationX = 0;
        this._rotationY = 0;
        this._rotationZ = 0;
        const rate = this.rotationRate();
        if (Math.random() < 0.5) {
            this._rotationSpeedX = (Math.random() + 0.5) * 2 * rate;
            this._rotationSpeedX *= Math.random() < 0.5 ? -1 : 1;
            this._rotationSpeedY = 0;
        } else {
            this._rotationSpeedX = 0;
            this._rotationSpeedY = (Math.random() + 0.5) * 2 * rate;
            this._rotationSpeedY *= Math.random() < 0.5 ? -1 : 1;
        }
        this._rotationSpeedZ = (Math.random() - 0.5) * 2 * rate;
    };

    Encounter_Fragment.prototype.destroy = function() {
        this.bitmap.destroy();
    };

    Encounter_Fragment.prototype.isValid = function() {
        return this._rect.width > 4 && this._rect.height > 4;
    };

    Encounter_Fragment.prototype.clearFlash = function() {
        this._flash = false;
    };

    Encounter_Fragment.prototype.isFlashRequested = function() {
        return !!this._flash;
    };

    Encounter_Fragment.prototype.needClip = function() {
        return !Utils.isMobileDevice();
    };

    Encounter_Fragment.prototype.clearClip = function() {
        this._clip = false;
    };

    Encounter_Fragment.prototype.isClipRequested = function() {
        return !!this._clip;
    };

    Encounter_Fragment.prototype.lineWidth = function() {
        return 4;
    };

    Encounter_Fragment.prototype.flashOpacity = function() {
        return 255;
    };

    Encounter_Fragment.prototype.breakRate = function() {
        return 1;
    };

    Encounter_Fragment.prototype.slideRate = function() {
        return 1;
    };

    Encounter_Fragment.prototype.rotationRate = function() {
        return 1;
    };

    Encounter_Fragment.prototype.flashColor = function() {
        const color = [...EncounterEffect.color()];
        color[3] = this.flashOpacity();
        return color;
    };
    
    Encounter_Fragment.prototype.setup = function(snap) {
        this.createBitmap();
        this.initPolygon();
        this.initPosition();
        this.initRotation();
        this.setupSnap(snap);
    };
    
    Encounter_Fragment.prototype.setupSnap = function(snap) {
        const rect = this._rect;
        const zoom = EncounterEffect.zoom();
        const context = this.bitmap.context;
        context.save();
        context.clip();
        let sx = rect.x;
        let sy = rect.y;
        switch (EncounterEffect.type()) {
            case 1: {
                const center = EncounterEffect.center;
                const radian = Math.atan2(this.y - center.y, this.x - center.x);
                sx -= 20 * Math.cos(radian) * this.slideRate();
                sy -= 20 * Math.sin(radian) * this.slideRate();
                break;
            }
            case 2:
            case 3: {
                const bounds = EncounterEffect.windowBounds;
                sx += (Math.randomInt(21) - 10) * this.slideRate();
                sy += (Math.randomInt(21) - 10) * this.slideRate();
                sx = sx.clamp(bounds.minX, bounds.maxX - rect.width);
                sy = sy.clamp(bounds.minY, bounds.maxY - rect.height);
                break;
            }
        }
        const dx = rect.x / zoom;
        const dy = rect.y / zoom;
        const dw = rect.width / zoom;
        const dh = rect.height / zoom;
        context.drawImage(
            snap.canvas, sx, sy, rect.width, rect.height, dx, dy, dw, dh
        );
        context.restore();
        this.bitmap.baseTexture.update();
    };

    Encounter_Fragment.prototype.onBreak = function(color, duration) {
        this.drawLine(color);
        this.startEncounter(duration);
        this._flash = true;
        this._clip = true;
        this.visible = true;
    };
    
    Encounter_Fragment.prototype.onClip = function() {
        if (this.needClip()) EncounterEffect.onClip(this._polygon, this._rect);
    };
    
    Encounter_Fragment.prototype.drawLine = function(color) {
        const lineWidth = this.lineWidth();
        if (lineWidth > 0) {
            const context = this.bitmap.context;
            context.lineWidth = lineWidth / EncounterEffect.zoom();
            context.strokeStyle = 'rgb(%1,%2,%3)'.format(...color);
            context.globalAlpha = 0.5;
            context.stroke();
            context.globalAlpha = 1;
        }
    };

    Encounter_Fragment.prototype.update = function() {
        if (this.visible && this.updateDelay()) {
            this.updateMove();
            this.updateRotation();
        }
    };

    Encounter_Fragment.prototype.updateDelay = function() {
        if (this._delay > 0) {
            this._delay--;
            return false;
        }
        return true;
    };

    Encounter_Fragment.prototype.updateMove = function() {
        if (this._moveDuration > 0) {
            const d = this._moveDuration;
            const triangular = n => n * (n + 1) / 2;
            switch (EncounterEffect.type()) {
                case 1:
                    this.updateRateMove(d / triangular(d));
                    break;
                case 2: {
                    const n = this._moveMax - d;
                    const max = triangular(this._moveMax) - triangular(n);
                    this.updateRateMove((n + 1) / max);
                    break;
                }
                case 3: {
                    const rate = d / triangular(d);
                    this.updateRateMove(rate);
                    this._zoom += (this._targetZoom - this._zoom) * rate;
                    const m = 4 * (d - 1) / this._moveMax;
                    this.jumpHeight = (2 * 2 - Math.pow(m - 2, 2)) * 96;
                    this.visible = this._zoom < 8;
                    break;
                }
            }
            this._moveDuration--;
        }
    };
    
    Encounter_Fragment.prototype.updateRateMove = function(rate) {
        this.x += (this._targetX - this.x) * rate;
        this.y += (this._targetY - this.y) * rate;
    };
    
    Encounter_Fragment.prototype.updateRotation = function() {
        if (this._speed > 0) {
            if (this._pace > 0)      this._speed *= 1.18;
            else if (this._pace < 0) this._speed *= 0.96;
            this._rotationX += this._rotationSpeedX * this._speed;
            this._rotationY += this._rotationSpeedY * this._speed;
            this._rotationZ += this._rotationSpeedZ * this._speed;
        }
    };

    Encounter_Fragment.prototype.startEncounter = function(duration) {
        const breakRate = this.breakRate();
        switch (EncounterEffect.type()) {
            case 1:
                this._moveDuration = duration;
                this._speed = this.hypotSpeed(1.5);
                this._pace = -1;
                this.setMoveT1(breakRate / 100, breakRate / 100);
                break;
            case 2:
            case 3:
                this._moveDuration = 0;
                this._speed = 0.2;
                this._pace = -1;
                break;
        }
        this._speed *= breakRate;
    };

    Encounter_Fragment.prototype.onBattleStartT1 = function(d) {
        this._moveDuration = d;
        this._speed = this.hypotSpeed(40);
        this._pace = -1;
        this.setMoveT1(0.75 + Math.random() / 2, 0.75 + Math.random() / 2);
    };

    Encounter_Fragment.prototype.hypotSpeed = function(baseSpeed) {
        const { x: cx, y: cy } = EncounterEffect.center;
        return baseSpeed / Math.sqrt(Math.hypot(this.x - cx, this.y - cy));
    };

    Encounter_Fragment.prototype.setMoveT1 = function(rateX, rateY) {
        const bounds = EncounterEffect.windowBounds;
        const { x: cx, y: cy } = EncounterEffect.center;
        const sx = this.x - cx;
        const sy = this.y - cy;
        let ox = sx;
        let oy = sy;
        if (ox < 0) {
            oy *= (bounds.minX - cx) / ox;
            ox = bounds.minX - cx;
        } else if (ox > 0) {
            oy *= (bounds.maxX - cx) / ox;
            ox = bounds.maxX - cx;
        }
        if (oy < bounds.minY - cy) {
            ox *= (bounds.minY - cy) / oy;
            oy = bounds.minY - cy;
        } else if (oy > bounds.maxY - cy) {
            ox *= (bounds.maxY - cy) / oy;
            oy = bounds.maxY - cy;
        }
        this._targetX = this.x + (ox - sx) * rateX;
        this._targetY = this.y + (oy - sy) * rateY;
    };

    Encounter_Fragment.prototype.onBattleStartT2 = function(duration, direction) {
        this._moveDuration = Math.floor(duration / 2);
        this._moveMax = this._moveDuration;
        this._speed = 1.6;
        this._pace = -1;
        this.setMoveT2(direction);
    };

    Encounter_Fragment.prototype.setMoveT2 = function(direction) {
        const cx = Graphics.width / 2;
        const cy = Graphics.height / 2;
        switch (direction) {
            case 'left':
                this._targetX = this.x - Graphics.width;
                this._targetY = this.y + (this.y - cy) * 1.0;
                break;
            case 'right':
                this._targetX = this.x + Graphics.width;
                this._targetY = this.y + (this.y - cy) * 1.0;
                break;
            case 'outside': {
                const radian = Math.atan2(this.y - cy, this.x - cx);
                const d = (cx + cy) / 2;
                this._targetX = this.x + d * Math.cos(radian);
                this._targetY = this.y + d * Math.sin(radian);
                break;
            }
        }
    };

    Encounter_Fragment.prototype.onBattleStartT3 = function(duration) {
        this._moveDuration = duration;
        this._moveMax = duration;
        this._speed = 2;
        this._pace = 0;
        this.setMoveT3();
    };

    Encounter_Fragment.prototype.setMoveT3 = function() {
        const cx = Graphics.width / 2;
        const cy = Graphics.height / 2;
        const sx = this.x - cx;
        const sy = this.y - cy;
        const rate = 3;
        this._targetX = (sx + 80 * (Math.random() - 0.5)) * rate + cx;
        this._targetY = (sy + 32 * (Math.random() - 0.5)) * rate + cy * 3;
        const delta = Math.abs(sx) + Math.abs(sy);
        this._targetZoom = delta < 128 && Math.random() < 0.4 ? 12 : 1;
        this._targetZoom += 3 * Math.random();
    };

    Encounter_Fragment.prototype.setDelay = function(delay) {
        this._delay = delay;
    };

    Encounter_Fragment.prototype.scaleX = function() {
        return Math.cos(this._rotationX * Math.PI / 180) * this._zoom;
    };

    Encounter_Fragment.prototype.scaleY = function() {
        return Math.cos(this._rotationY * Math.PI / 180) * this._zoom;
    };

    Encounter_Fragment.prototype.rotation = function() {
        return this._rotationZ * Math.PI / 180;
    };

    //-----------------------------------------------------------------------------
    // Bitmap
    
    if (Utils.RPGMAKER_NAME === 'MV') {
        
        Bitmap.prototype.destroy = function() {
            if (this._baseTexture) {
                this._baseTexture.destroy();
                this.__baseTexture = null;
            }
            this._destroyCanvas();
        };
        
        Bitmap.prototype._destroyCanvas = function() {
            if (this._canvas) {
                this._canvas.width = 0;
                this._canvas.height = 0;
                this.__canvas = null;
            }
        };
        
    }

    //-----------------------------------------------------------------------------
    // SceneManager
    
    const _SceneManager_update = SceneManager.update;
    SceneManager.update = function() {
        _SceneManager_update.apply(this, arguments);
        EncounterEffect.updateEffectDelay();
    };

    const _SceneManager_determineRepeatNumber = SceneManager.determineRepeatNumber;
    SceneManager.determineRepeatNumber = function(deltaTime) {
        if (EncounterEffect.isRunning()) {
            this._smoothDeltaTime *= 0.8;
            this._smoothDeltaTime += Math.min(deltaTime, 5) * 0.2; //2to5
            if (this._smoothDeltaTime >= 0.9) {
                this._elapsedTime = 0;
                return Math.round(this._smoothDeltaTime);
            } else {
                this._elapsedTime += deltaTime;
                if (this._elapsedTime >= 1) {
                    this._elapsedTime -= 1;
                    return 1;
                }
                return 0;
            }
        }
        return _SceneManager_determineRepeatNumber.apply(this, arguments);
    };

    //-------------------------------------------------------------------------
    // PluginManager
    
    PluginManager._commands = PluginManager._commands || {};
    
    if (!PluginManager.registerCommand) {
        PluginManager.registerCommand = function(pluginName, commandName, func) {
            const key = pluginName + ":" + commandName;
            this._commands[key] = func;
        };
    }

    if (!PluginManager.callCommand) {
        PluginManager.callCommand = function(self, pluginName, commandName, args) {
            const key = pluginName + ":" + commandName;
            const func = this._commands[key];
            if (typeof func === "function") {
                func.bind(self)(args);
            }
        };
    }

    PluginManager.registerCommand(pluginName, 'setType', args => {
        EncounterEffect.setType(PluginManager.mppValue(args.type));
    });

    PluginManager.registerCommand(pluginName, 'setCharacter', function(args) {
        const eId = PluginManager.mppValue(args.character);
        EncounterEffect.setCharacter(this.character(eId));
    });

    PluginManager.registerCommand(pluginName, 'setColor', args => {
        const color = args.color.split(',');
        const r = PluginManager.mppValue(color[0]);
        const g = PluginManager.mppValue(color[1]);
        const b = PluginManager.mppValue(color[2]);
        EncounterEffect.setColor(r, g, b);
    });

    PluginManager.mppValue = function(value) {
        const match = /^V\[(\d+)\]$/i.exec(value);
        return match ? $gameVariables.value(+match[1]) : +value;
    };
        
    //-----------------------------------------------------------------------------
    // Game_Map

    Game_Map.prototype.getInterpreterCharacter = function(param) {
        const interpreter = this._interpreter;
        return interpreter.isRunning() ? interpreter.character(param) : null;
    };

    //-----------------------------------------------------------------------------
    // Game_Interpreter

    const _mzCommands = {
        EncEffType: { name:'setType', keys:['type'] },
        EncEffChar: { name:'setCharacter', keys:['character'] },
        EncEffColor: { name:'setColor', keys:[], join:'color' }
    };
    Object.assign(_mzCommands, {
        'エフェクトタイプ変更': _mzCommands.EncEffType,
        '中心キャラ指定': _mzCommands.EncEffChar,
        'エフェクト色指定': _mzCommands.EncEffColor
    });

    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command, args) {
        _Game_Interpreter_pluginCommand.apply(this, arguments);
        const mzCommand = _mzCommands[command];
        if (mzCommand) {
            const args2 = Object.assign(...mzCommand.keys.map((k,i) => ({[k]:args[i]})));
            if (mzCommand.join) args2[mzCommand.join] = args.join();
            PluginManager.callCommand(this, pluginName, mzCommand.name, args2);
        }
    };
    
    //-----------------------------------------------------------------------------
    // Sprite
    
    if (Utils.RPGMAKER_NAME === 'MZ' && !_importedPlugin('MPP_PatchMZ')) {
        
        Sprite.prototype._removeColorFilter = function() {
            if (this._colorFilter) {
                this.filters.remove(this._colorFilter);
                this._colorFilter = null;
            }
        };

        const _Sprite__updateColorFilter = Sprite.prototype._updateColorFilter;
        Sprite.prototype._updateColorFilter = function() {
            if (this._hue === 0 && this._blendColor[3] === 0 &&
                    this._colorTone.equals([0, 0, 0, 0])) {
                this._removeColorFilter();
            } else {
                _Sprite__updateColorFilter.apply(this, arguments);
            }
        };
        
    }

    //-----------------------------------------------------------------------------
    // Sprite_Fragment

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

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

    Sprite_Fragment.prototype.initialize = function(fragment) {
        this._fragment = fragment;
        Sprite.prototype.initialize.call(this, fragment.bitmap);
        this.anchor.x = 0.5;
        this.anchor.y = 0.5;
        this.clearFlash();
        this.setupRate();
        this.update();
    };

    Sprite_Fragment.prototype.clearFlash = function() {
        this._flashColor = [0, 0, 0, 0];
        this._flashDuration = 0;
    };

    Sprite_Fragment.prototype.setupRate = function() {
        this._rate = Utils.isMobileDevice() ? 6 : 8;
    };

    Sprite_Fragment.prototype.update = function() {
        Sprite.prototype.update.call(this);
        this.updatePosition();
        this.updateScale();
        this.updateRotation();
        this.updateFlash();
        this.updateClip();
        this.updateVisibility();
    };

    Sprite_Fragment.prototype.updatePosition = function() {
        const fragment = this._fragment;
        this.x = fragment.x;
        this.y = fragment.y - fragment.jumpHeight;
    };

    Sprite_Fragment.prototype.updateScale = function() {
        const fragment = this._fragment;
        this.scale.x = fragment.scaleX();
        this.scale.y = fragment.scaleY();
    };

    Sprite_Fragment.prototype.updateRotation = function() {
        this.rotation = this._fragment.rotation();
    };

    Sprite_Fragment.prototype.updateFlash = function() {
        this.setupFlash();
        if (this._flashDuration > 0) {
            const d = this._flashDuration--;
            this._flashColor[3] *= (d - 1) / d;
            if (this._flashDuration % this._rate === 0) {
                this.setBlendColor(this._flashColor);
            }
        }
    };

    Sprite_Fragment.prototype.setupFlash = function() {
        if (this._fragment.isFlashRequested()) {
            this._flashDuration = Utils.isMobileDevice() ? 13 : 25;
            this._flashColor = this._fragment.flashColor();
            this._fragment.clearFlash();
        }
    };

    Sprite_Fragment.prototype.updateClip = function() {
        if (this._fragment.isClipRequested()) {
            this._fragment.onClip();
            this._fragment.clearClip();
        }
    };

    Sprite_Fragment.prototype.updateVisibility = function() {
        this.visible = this._fragment.visible;
    };

    //-----------------------------------------------------------------------------
    // Sprite_EncounterEffect

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

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

    Sprite_EncounterEffect.prototype.initialize = function() {
        Sprite.prototype.initialize.call(this);
        this.createBackSprite();
        this.createFragments();
        this._fadeDelay = 0;
        this._fadeDuration = 0;
    };

    Sprite_EncounterEffect.prototype.createBackSprite = function() {
        this._backSprite = new Sprite(EncounterEffect.snap);
        this.addChild(this._backSprite);
    };

    Sprite_EncounterEffect.prototype.createFragments = function() {
        this._fragmentSprites = [];
        const fragments = EncounterEffect.fragments();
        for (let i = 0; i < fragments.length; i++) {
            const sprite = new Sprite_Fragment(fragments[i]);
            this._fragmentSprites[i] = sprite;
            this.addChild(sprite);
        }
    };

    Sprite_EncounterEffect.prototype.startFadeIn = function(duration) {
        const rate = 3;//Utils.isMobileDevice() ? 2 : 3;
        this._fadeDelay = Math.floor(duration / rate);
        this._fadeDuration = duration - this._fadeDelay;
    };

    Sprite_EncounterEffect.prototype.update = function() {
        Sprite.prototype.update.call(this);
        this.opacity = EncounterEffect.opacity;
        this.updateFadeIn();
    };

    Sprite_EncounterEffect.prototype.updateFadeIn = function() {
        if (this._fadeDuration > 0 && this.updateFadeDelay()) {
            const d = this._fadeDuration;
            this._backSprite.opacity -= this._backSprite.opacity / d;
            this._fadeDuration--;
        }
    };

    Sprite_EncounterEffect.prototype.updateFadeDelay = function() {
        if (this._fadeDelay > 0) {
            this._fadeDelay--;
            return false;
        }
        return true;
    };

    Sprite_EncounterEffect.prototype.isSpritesetVisible = function() {
        return this.opacity < 255 || this._backSprite.opacity < 255;
    };

    //-----------------------------------------------------------------------------
    // Sprite_Actor

    const _Sprite_Actor_updateMove = Sprite_Actor.prototype.updateMove;
    Sprite_Actor.prototype.updateMove = function() {
        if (EncounterEffect.isReady()) {
            _Sprite_Actor_updateMove.apply(this, arguments);
        }
    };

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

    Spriteset_Map.prototype.temporaryHideDestination = function() {
        this._destinationSprite.visible = false;
    };

    //-----------------------------------------------------------------------------
    // Scene_Base

    Scene_Base.prototype.createEncEffSprite = function() {
        this._encEffSprite = new Sprite_EncounterEffect();
        this.addChild(this._encEffSprite);
    };

    Scene_Base.prototype.destroyEncEffSprite = function() {
        this.removeChild(this._encEffSprite);
        if (Utils.RPGMAKER_NAME === 'MZ') this._encEffSprite.destroy();
        delete this._encEffSprite;
    };

    Scene_Base.prototype.clearFade = function() {
        this._fadeSign = 0;
        this._fadeDuration = 0;
        this._fadeWhite = 0;
        this._fadeOpacity = 0;
        if (Utils.RPGMAKER_NAME === 'MV') {
            this._fadeSprite.opacity = 0;
        } else {
            this.updateColorFilter();
        }
    };

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

    const _Scene_Map_launchBattle = Scene_Map.prototype.launchBattle;
    Scene_Map.prototype.launchBattle = function() {
        if (EncounterEffect.isValid()) {
            this.snapForBattleBackground();
            this._windowLayer.visible = false;
            this._spriteset.temporaryHideDestination();
            EncounterEffect.setup(SceneManager.snap());
            _Scene_Map_launchBattle.apply(this, arguments);
            this.createEncEffSprite();
            this._spriteset.visible = false;
        } else {
            EncounterEffect.clear();
            _Scene_Map_launchBattle.apply(this, arguments);
        }
    };

    const _Scene_Map_updateEncounterEffect = Scene_Map.prototype.updateEncounterEffect
    Scene_Map.prototype.updateEncounterEffect = function() {
        if (EncounterEffect.isRunning()) {
            if (!EncounterEffect.isEffectReady()) return;
            if (this._encounterEffectDuration > 0) {
                EncounterEffect.update();
                this._encounterEffectDuration--;
                const speed = this.encounterEffectSpeed();
                const n = speed - this._encounterEffectDuration;
                const breakEnd = EncounterEffect.breakDuration();
                if (n <= breakEnd) {
                    const maxItems = EncounterEffect.maxItems();
                    const start = Math.floor(maxItems * (n - 1) / breakEnd);
                    const end = Math.floor(maxItems * n / breakEnd);
                    EncounterEffect.breakFragments(start, end, speed);
                }
                if (n === breakEnd) {
                    EncounterEffect.onBreakEnd();
                }
                if (n === Math.floor(speed / 5)) {
                    BattleManager.playBattleBgm();
                }
                if (n === speed && !ImageManager.isReady()) {
                    this._encounterEffectDuration = 1;
                }
            }
        } else {
            _Scene_Map_updateEncounterEffect.apply(this, arguments);
        }
    };
    
    const _Scene_Map_encounterEffectSpeed = Scene_Map.prototype.encounterEffectSpeed;
    Scene_Map.prototype.encounterEffectSpeed = function() {
        return EncounterEffect.isRunning()
            ? EncounterEffect.effectSpeed()
            : _Scene_Map_encounterEffectSpeed.apply(this, arguments);
    };

    //-----------------------------------------------------------------------------
    // Scene_Battle

    const _Scene_Battle_create = Scene_Battle.prototype.create;
    Scene_Battle.prototype.create = function() {
        _Scene_Battle_create.apply(this, arguments);
        if (EncounterEffect.isRunning()) {
            EncounterEffect.startEffectDelay();
        }
    };

    const _Scene_Battle_start = Scene_Battle.prototype.start;
    Scene_Battle.prototype.start = function() {
        _Scene_Battle_start.apply(this, arguments);
        if (EncounterEffect.isRunning()) {
            this.clearFade();
            EncounterEffect.onBattleStart();
            this.createEncEffSprite();
            this._encEffSprite.startFadeIn(EncounterEffect.fadeSpeed());
        }
    };

    const _Scene_Battle_update = Scene_Battle.prototype.update;
    Scene_Battle.prototype.update = function() {
        _Scene_Battle_update.apply(this, arguments);
        if (EncounterEffect.isRunning() && EncounterEffect.isEffectReady()) {
            EncounterEffect.update();
            this._spriteset.visible = this._encEffSprite.isSpritesetVisible();
            if (!EncounterEffect.isRunning()) {
                this.destroyEncEffSprite();
            }
        }
    };

})();
