//=============================================================================
// DhoomParticleLightingSystem.js
//=============================================================================
var Imported = Imported || {};
Imported.Dhoom_ParticleLightingSystem = "2.0";

var Dhoom = Dhoom || {};
Dhoom.ParticleLightingSystem = Dhoom.ParticleLightingSystem || {};
/*:
 * @plugindesc Dhoom ParticleLightingSystem v2.0 - 11/06/2020
 * @author DrDhoom - drd-workshop.blogspot.com | DrDhoom#8315
 * 
 * @param Light Switch
 * @desc Lightings will only be enabled when this switch is ON. 0/None = Always ON.
 * @type switch
 * @default 0
 * 
 * @param Light Max Switch
 * @type switch
 * @default 0
 * 
 * @param Particle Switch
 * @desc Particles will only be enabled when this switch is ON. 0/None = Always ON.
 * @type switch
 * @default 0
 * 
 * @param Option Lightings Term
 * @default Lightings
 * 
 * @param Option Particles Term
 * @default Particles
 * 
 * @param Particle Limit
 * @type number
 * @min 0
 * @default 1000
 * 
 * @param Particle Settings
 * @type struct<particleSetting>[]
 * @default ["{\"name\":\"Smoke\",\"opacitySteps\":\"[128, 0]\",\"opacityEase\":\"Linear\",\"scaleSteps\":\"[10, 100]\",\"scaleEase\":\"Linear\",\"scaleMultiplier\":\"1.00\",\"colorStart\":\"#85888d\",\"colorEnd\":\"#100f0c\",\"speedStart\":\"300\",\"speedEnd\":\"700\",\"speedMultiplier\":\"1.00\",\"accelerationX\":\"0\",\"accelerationY\":\"0\",\"speedMax\":\"0\",\"rotationMin\":\"250\",\"rotationMax\":\"290\",\"noRotate\":\"false\",\"rotationSpeedMin\":\"0\",\"rotationSpeedMax\":\"0\",\"lifetimeMin\":\"0.500\",\"lifetimeMax\":\"1\",\"blend\":\"Normal\",\"images\":\"[\\\"smokeparticle\\\"]\"}","{\"name\":\"Gas\",\"opacitySteps\":\"[102, 0]\",\"opacityEase\":\"Linear\",\"scaleSteps\":\"[200, 40]\",\"scaleEase\":\"Linear\",\"scaleMultiplier\":\"1.00\",\"colorStart\":\"#6bff61\",\"colorEnd\":\"#d8ff4a\",\"speedStart\":\"10\",\"speedEnd\":\"10\",\"speedMultiplier\":\"1.00\",\"accelerationX\":\"0\",\"accelerationY\":\"0\",\"speedMax\":\"0\",\"rotationMin\":\"0\",\"rotationMax\":\"360\",\"noRotate\":\"false\",\"rotationSpeedMin\":\"0\",\"rotationSpeedMax\":\"0\",\"lifetimeMin\":\"6.000\",\"lifetimeMax\":\"1.800\",\"blend\":\"Additive\",\"images\":\"[\\\"particle\\\",\\\"smokeparticle\\\"]\"}","{\"name\":\"Steam Left\",\"opacitySteps\":\"[80, 0]\",\"opacityEase\":\"Linear\",\"scaleSteps\":\"[5, 50]\",\"scaleEase\":\"Linear\",\"scaleMultiplier\":\"0.50\",\"colorStart\":\"#949494\",\"colorEnd\":\"#ffffff\",\"speedStart\":\"100\",\"speedEnd\":\"700\",\"speedMultiplier\":\"0.50\",\"accelerationX\":\"0\",\"accelerationY\":\"0\",\"speedMax\":\"0\",\"rotationMin\":\"200\",\"rotationMax\":\"220\",\"noRotate\":\"false\",\"rotationSpeedMin\":\"0\",\"rotationSpeedMax\":\"0\",\"lifetimeMin\":\"0.200\",\"lifetimeMax\":\"0.500\",\"blend\":\"Additive\",\"images\":\"[\\\"smokeparticle\\\"]\"}","{\"name\":\"Steam Right\",\"opacitySteps\":\"[80, 0]\",\"opacityEase\":\"Linear\",\"scaleSteps\":\"[5, 20]\",\"scaleEase\":\"Linear\",\"scaleMultiplier\":\"0.50\",\"colorStart\":\"#949494\",\"colorEnd\":\"#ffffff\",\"speedStart\":\"100\",\"speedEnd\":\"700\",\"speedMultiplier\":\"0.50\",\"accelerationX\":\"0\",\"accelerationY\":\"0\",\"speedMax\":\"0\",\"rotationMin\":\"320\",\"rotationMax\":\"320\",\"noRotate\":\"false\",\"rotationSpeedMin\":\"0\",\"rotationSpeedMax\":\"0\",\"lifetimeMin\":\"0.200\",\"lifetimeMax\":\"0.500\",\"blend\":\"Additive\",\"images\":\"[\\\"smokeparticle\\\"]\"}","{\"name\":\"Glitter\",\"opacitySteps\":\"[0, 255, 0]\",\"opacityEase\":\"Linear\",\"scaleSteps\":\"[25, 0]\",\"scaleEase\":\"Linear\",\"scaleMultiplier\":\"0.50\",\"colorStart\":\"#fff566\",\"colorEnd\":\"#ffa200\",\"speedStart\":\"8\",\"speedEnd\":\"8\",\"speedMultiplier\":\"1.00\",\"accelerationX\":\"0\",\"accelerationY\":\"0\",\"speedMax\":\"0\",\"rotationMin\":\"0\",\"rotationMax\":\"0\",\"noRotate\":\"true\",\"rotationSpeedMin\":\"0\",\"rotationSpeedMax\":\"0\",\"lifetimeMin\":\"2.000\",\"lifetimeMax\":\"8.000\",\"blend\":\"Additive\",\"images\":\"[\\\"Pixel25px\\\",\\\"Pixel50px\\\",\\\"Pixel100px\\\"]\"}"]
 * 
 * @param Particle Emitter Settings
 * @type struct<emitterSetting>[]
 * @default ["{\"name\":\"Smoke\",\"particles\":\"[\\\"Smoke\\\"]\",\"rate\":\"14.00\",\"max\":\"500\",\"toBack\":\"true\",\"minDuration\":\"1\",\"maxDuration\":\"1\",\"minWait\":\"0\",\"maxWait\":\"0\",\"type\":\"Point\",\"x\":\"0\",\"y\":\"0\",\"width\":\"1\",\"height\":\"1\",\"minWidth\":\"1\",\"minHeight\":\"1\"}","{\"name\":\"Gas\",\"particles\":\"[\\\"Gas\\\"]\",\"rate\":\"0.50\",\"max\":\"100\",\"toBack\":\"true\",\"minDuration\":\"1\",\"maxDuration\":\"1\",\"minWait\":\"0\",\"maxWait\":\"0\",\"type\":\"Circle\",\"x\":\"-150\",\"y\":\"-150\",\"width\":\"300\",\"height\":\"300\",\"minWidth\":\"250\",\"minHeight\":\"150\"}","{\"name\":\"Cartoon Smoke\",\"particles\":\"[\\\"Cartoon Smoke\\\"]\",\"rate\":\"100.00\",\"max\":\"100\",\"toBack\":\"true\",\"minDuration\":\"1\",\"maxDuration\":\"1\",\"minWait\":\"0\",\"maxWait\":\"0\",\"type\":\"Point\",\"x\":\"0\",\"y\":\"0\",\"width\":\"1\",\"height\":\"1\",\"minWidth\":\"1\",\"minHeight\":\"1\"}","{\"name\":\"Steam Left\",\"particles\":\"[\\\"Steam Left\\\"]\",\"rate\":\"5.00\",\"max\":\"200\",\"toBack\":\"true\",\"minDuration\":\"30\",\"maxDuration\":\"60\",\"minWait\":\"0\",\"maxWait\":\"10\",\"type\":\"Point\",\"x\":\"0\",\"y\":\"0\",\"width\":\"1\",\"height\":\"1\",\"minWidth\":\"1\",\"minHeight\":\"1\"}","{\"name\":\"Steam Right\",\"particles\":\"[\\\"Steam Right\\\"]\",\"rate\":\"5.00\",\"max\":\"200\",\"toBack\":\"true\",\"minDuration\":\"30\",\"maxDuration\":\"90\",\"minWait\":\"90\",\"maxWait\":\"180\",\"type\":\"Point\",\"x\":\"0\",\"y\":\"0\",\"width\":\"1\",\"height\":\"1\",\"minWidth\":\"1\",\"minHeight\":\"1\"}","{\"name\":\"Glitter\",\"particles\":\"[\\\"Glitter\\\"]\",\"rate\":\"0.50\",\"max\":\"100\",\"toBack\":\"true\",\"minDuration\":\"1\",\"maxDuration\":\"1\",\"minWait\":\"0\",\"maxWait\":\"0\",\"type\":\"Rectangle\",\"x\":\"-24\",\"y\":\"-48\",\"width\":\"48\",\"height\":\"48\",\"minWidth\":\"1\",\"minHeight\":\"1\"}","{\"name\":\"Glitter 2\",\"particles\":\"[\\\"Glitter\\\"]\",\"rate\":\"0.50\",\"max\":\"20\",\"toBack\":\"true\",\"minDuration\":\"1\",\"maxDuration\":\"1\",\"minWait\":\"0\",\"maxWait\":\"0\",\"type\":\"Rectangle\",\"x\":\"-24\",\"y\":\"-48\",\"width\":\"48\",\"height\":\"48\",\"minWidth\":\"1\",\"minHeight\":\"1\"}"]
 * 
 * @param Lighting Parallax
 * @desc Parallax names in here will be used as lightings.
 * @type text[]
 * @default []
 * 
 * @param Parallax Bypass Mask
 * @type text[]
 * @default ["Above"]
 * 
 * @param Blocked Light Terrain Tags
 * @type number[]
 * @min 0
 * @max 7
 * @default []
 * 
 * @param Blocked Light Region IDs
 * @type number[]
 * @min 1
 * @max 255
 * @default []
 * 
 * @param Light Settings
 * @desc The first setting is the default when setting isn't specified.
 * @type struct<lightSetting>[]
 * @default []
 * 
 * @param Flashlight Setting
 * @type struct<lightSetting>
 * @default {"name":"","texture":"lightcirclefade","ox":"0.50","oy":"0.50","random":"false","colors":"[]","scales":"[\"{\\\"x\\\":\\\"150\\\",\\\"y\\\":\\\"150\\\",\\\"proportions\\\":\\\"false\\\",\\\"duration\\\":\\\"1\\\",\\\"interpolate\\\":\\\"false\\\"}\"]","rotations":"[]","positions":"[]"}
 * 
 * @param Player Light Setting
 * @type struct<lightSetting>
 * @default {"name":"","texture":"lightcirclefade","ox":"0.5","oy":"0.50","random":"false","colors":"[]","scales":"[\"{\\\"x\\\":\\\"150\\\",\\\"y\\\":\\\"150\\\",\\\"proportions\\\":\\\"false\\\",\\\"duration\\\":\\\"1\\\",\\\"interpolate\\\":\\\"false\\\"}\"]","rotations":"[]","positions":"[]"}
 *
 * @help =============================================================================
 * • !DISCLAIMER!
 * =============================================================================
 *   This plugin was made for Cursed Atelier and can only be used by Cursed 
 *   Atelier; or with Cursed Atelier written permission.
 * 
 * =============================================================================
 * • Event Note Tags
 * =============================================================================
 *   <ParticleEmitter: EmitterName, x, y, LAYER>
 *   LAYER: below, same, above, or abovepar
 */
/*~struct~lightSetting:
@param name
@text Name
@desc Must be unique.

@param texture
@text Lighting Texture
@type file
@dir img/lights/
@default 

@param castShadow
@text Cast Shadow
@type boolean
@default false

@param ox
@text Anchor X
@desc Anchor X position. 0.5 = Center
@type number
@min -10
@max 10
@decimals 2
@default 0.5

@param oy
@text Anchor Y
@desc Anchor Y position. 0.5 = Center
@type number
@min -10
@max 10
@decimals 2
@default 0.5

@param random
@text Randomize Start Steps
@desc Randomize steps start index.
@type boolean
@default false

@param colors
@text Color Steps
@type struct<colorOpacitySetting>[]
@default []

@param scales
@text Scale Steps
@type struct<scaleSetting>[]
@default []

@param rotations
@text Rotation Steps
@type struct<rotationSetting>[]
@default []

@param positions
@text Position Steps
@type struct<positionSetting>[]
@default []
*/

/*~struct~positionSetting:
@param x
@text X Value
@desc -9999 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 0

@param y
@text Y Value
@desc -9999 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 0

@param duration
@text Duration
@desc Duration in frame.
@type number
@min 0
@default 60

@param interpolate
@text Interpolate?
@desc Interpolate previous positon to this positon?
@type boolean
@default true
*/

/*~struct~rotationSetting:
@param rotation
@text Rotation Value
@desc In degree. -9999 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 0

@param duration
@text Duration
@desc Duration in frame.
@type number
@min 0
@default 60

@param interpolate
@text Interpolate?
@desc Interpolate previous rotation to this rotation?
@type boolean
@default true
*/

/*~struct~scaleSetting:
@param x
@text X Scale Value
@desc In percent. -9999 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 100

@param y
@text Y Scale Value
@desc In percent. -9999 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 100

@param proportions
@text Constrain Proportions
@desc When this is turned on, the highest value between x and y will be used for both.
@type boolean
@default false

@param duration
@text Duration
@desc Duration in frame.
@type number
@min 0
@default 60

@param interpolate
@text Interpolate?
@desc Interpolate previous scale to this scale?
@type boolean
@default true
*/

/*~struct~colorOpacitySetting:
@param red
@text Red Value
@desc -1 = No change, N1 ~ N2 = Random number between N1 and N2.
@default 255

@param green
@text Green Value
@type number
@desc -1 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 255

@param blue
@text Blue Value
@type number
@desc -1 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 255

@param opacity
@text Opacity Value
@desc -1 = No change. N1 ~ N2 = Random number between N1 and N2.
@default 255

@param duration
@text Duration
@desc Duration in frame.
@type number
@min 0
@default 60

@param interpolate
@text Interpolate Color?
@desc Interpolate previous color to this color?
@type boolean
@default false
*/

/*~struct~particleSetting:
@param name
@text Particle Name

@param light
@text Set as Light?
@type boolean
@default false

@param opacitySteps
@text Opacity Steps
@desc Opacity steps from start to finish.
@type number[]
@min 0
@max 255
@default [255, 0]

@param opacityEase
@text Opacity Ease
@type select
@option Linear
@option EaseInQuad
@option EaseOutQuad
@option EaseInOutQuad
@option EaseInCubic
@option EaseOutCubic
@option EaseInOutCubic
@option EaseInQuart
@option EaseOutQuart
@option EaseInOutQuart
@option EaseInQuint
@option EaseOutQuint
@option EaseInOutQuint
@option EaseInSine
@option EaseOutSine
@option EaseInOutSine
@option EaseInExpo
@option EaseOutExpo
@option EaseInOutExpo
@option EaseInCirc
@option EaseOutCirc
@option EaseInOutCirc
@option EaseInElastic
@option EaseOutElastic
@option EaseInOutElastic
@option EaseInBack
@option EaseOutBack
@option EaseInOutBack
@option EaseInBounce
@option EaseOutBounce
@option EaseInOutBounce
@default Linear

@param scaleSteps
@text Scale Steps
@desc Scale steps from start to finish.
@type number[]
@min 0
@max 999999
@default [0, 100]

@param scaleEase
@text Scale Ease
@type select
@option Linear
@option EaseInQuad
@option EaseOutQuad
@option EaseInOutQuad
@option EaseInCubic
@option EaseOutCubic
@option EaseInOutCubic
@option EaseInQuart
@option EaseOutQuart
@option EaseInOutQuart
@option EaseInQuint
@option EaseOutQuint
@option EaseInOutQuint
@option EaseInSine
@option EaseOutSine
@option EaseInOutSine
@option EaseInExpo
@option EaseOutExpo
@option EaseInOutExpo
@option EaseInCirc
@option EaseOutCirc
@option EaseInOutCirc
@option EaseInElastic
@option EaseOutElastic
@option EaseInOutElastic
@option EaseInBack
@option EaseOutBack
@option EaseInOutBack
@option EaseInBounce
@option EaseOutBounce
@option EaseInOutBounce
@default Linear

@param scaleMultiplier
@text Minimum Scale Multiplier
@desc Random number between this and 1 will be multiplied to the Scale Steps.
@type number
@decimals 2
@min 0.01
@max 1.00
@default 1.00

@param colorStart
@text Color Start
@default #ffffff

@param colorEnd
@text Color End
@default #000000

@param speedStart
@text Speed Start
@type number
@min 0
@max 999999
@default 0

@param speedEnd
@text Speed End
@type number
@min 0
@max 999999
@default 0

@param speedMultiplier
@text Minimum Speed Multiplier
@desc Random number between this and 1 will be multiplied to the Speed start and end.
@type number
@min 0.01
@max 1.00
@decimals 2
@default 1.00

@param accelerationX
@text Acceleration X
@type number
@min -9999999
@default 0

@param accelerationY
@text Acceleration Y
@type number
@min -9999999
@default 0

@param speedMax
@text Maximum Speed
@type number
@min -999999
@default 0

@param rotationMin
@text Minimum Start Rotation
@type number
@min -360
@max 360
@default 0

@param rotationMax
@text Maximum Start Rotation
@type number
@min -360
@max 360
@default 0

@param noRotate
@text No Particle Rotation
@type boolean
@default false

@param rotationSpeedMin
@text Minimum Rotation Speed
@type number
@min -99999999
@default 0

@param rotationSpeedMax
@text Maximum Rotation Speed
@type number
@min -99999999
@default 0

@param lifetimeMin
@text Minimum Lifetime
@type number
@decimals 3
@min 0.001
@default 1

@param lifetimeMax
@text Maximum Lifetime
@type number
@decimals 3
@min 0.001
@default 1

@param blend
@text Blend Mode
@type select
@option Normal
@option Additive
@option Multiply
@option Screen
@default Normal

@param images
@text Particle Images
@type file[]
@dir img/particles/
@default []
*/

/*~struct~emitterSetting:
@param name
@text Emitter Name

@param particles
@text Particles
@type text[]
@default []

@param rate
@text Spawn Rate
@desc 
@type number
@min 0.01
@decimals 2
@default 1

@param max
@text Max Particles
@type number
@min 1
@default 1

@param toBack
@text Add to Back
@desc Add new particle at the back of other particles?
@type boolean
@default false

@param minDuration
@text Minimum Duration
@type number
@min 1
@default 1

@param maxDuration
@text Maximum Duration
@type number
@min 1
@default 1

@param minWait
@text Minimum Wait
@type number
@min 0
@default 0

@param maxWait
@text Maximum Wait
@type number
@min 0
@default 0

@param type
@text Position Type
@type select
@option Point
@option Rectangle
@option Circle
@option Ring
@default Point

@param x
@text Area X Position
@type number
@min -99999999999
@default 0

@param y
@text Area Y Position
@type number
@min -99999999999
@default 0

@param width
@text Area Width
@type number
@min 1
@default 1

@param height
@text Area Height
@type number
@min 1
@default 1

@param minWidth
@text Minimum Area Width
@desc Only used for Ring type.
@type number
@min 1
@default 1

@param minHeight
@text Minimum Area Height
@desc Only used for Ring type.
@type number
@min 1
@default 1
*/

Dhoom.Parameters = $plugins.filter(function (obj) { return obj.description.match(/Dhoom ParticleLightingSystem/) })[0].parameters;
if (!Dhoom.jsonParse) {
    Dhoom.jsonParse = function (string) {
        if (typeof string === 'string') {
            try {
                return JSON.parse(string, function (key, value) {
                    if (typeof value === 'string') {
                        try {
                            return this.jsonParse(value);
                        } catch (e) {
                            return value;
                        }
                    } else {
                        return value;
                    }
                }.bind(this))
            } catch (e) {
                return string;
            }
        } else {
            return string;
        }
    };
}
if (!Dhoom.loadParam) {
    Dhoom.loadParam = function (sym) {
        return Dhoom.jsonParse(Dhoom.Parameters[sym]);
    };
}

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Dhoom.ParticleLightingSystem
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.lightSwitch = Dhoom.loadParam('Light Switch');
Dhoom.ParticleLightingSystem.lightMaxSwitch = Dhoom.loadParam('Light Max Switch');
Dhoom.ParticleLightingSystem.particleSwitch = Dhoom.loadParam('Particle Switch');
Dhoom.ParticleLightingSystem.lightTerm = Dhoom.loadParam('Option Lightings Term');
Dhoom.ParticleLightingSystem.particleTerm = Dhoom.loadParam('Option Particles Term');
Dhoom.ParticleLightingSystem.limit = Dhoom.loadParam('Particle Limit');
Dhoom.ParticleLightingSystem.particles = Dhoom.loadParam('Particle Settings');
Dhoom.ParticleLightingSystem.emitters = Dhoom.loadParam('Particle Emitter Settings');
Dhoom.ParticleLightingSystem.parallaxLights = Dhoom.loadParam('Lighting Parallax');
Dhoom.ParticleLightingSystem.parallaxBypassMasks = Dhoom.loadParam('Parallax Bypass Mask');
Dhoom.ParticleLightingSystem.lightBlockTerrain = Dhoom.loadParam('Blocked Light Terrain Tags');
Dhoom.ParticleLightingSystem.lightBlockRegion = Dhoom.loadParam('Blocked Light Region IDs');
Dhoom.ParticleLightingSystem.lightingSettings = Dhoom.loadParam('Light Settings');
Dhoom.ParticleLightingSystem.flashlightSetting = Dhoom.loadParam('Flashlight Setting');
Dhoom.ParticleLightingSystem.playerLightSetting = Dhoom.loadParam('Player Light Setting');

Dhoom.ParticleLightingSystem.getParticle = function (name) {
    return Dhoom.Utils.getSettingFromName(this.particles, name);
};

Dhoom.ParticleLightingSystem.getEmitter = function (name) {
    return Dhoom.Utils.getSettingFromName(this.emitters, name);
};

Dhoom.ParticleLightingSystem.getLighting = function (name) {
    return Dhoom.Utils.getSettingFromName(this.lightingSettings, name);
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Dhoom.Utils
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.Utils = Dhoom.Utils || {};

Dhoom.Utils.getSettingFromName = function (array, name) {
    for (var i = 0; i < array.length; i++) {
        if (array[i].name === name) return array[i];
    }
};

Dhoom.Utils.componentToHex = function (c) {
    c = Math.round(c);
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
};

Dhoom.Utils.hexToRGB = function (hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
};

Dhoom.Utils.rgbToHex = function (rgb) {
    return parseInt("0x" + this.componentToHex(rgb[0]) + this.componentToHex(rgb[1]) + this.componentToHex(rgb[2]));
};

Dhoom.Utils.rgbToCSSHex = function (rgb) {
    return "#" + this.componentToHex(rgb[0]) + this.componentToHex(rgb[1]) + this.componentToHex(rgb[2]);
};

Dhoom.Utils.ease = function (type, start, end, percent, elapsed, totalDuration) {
    switch (type.toLowerCase()) {
        case 'linear':
            return start + (end - start) * percent;
        case 'easeinquad':
            return (end - start) * (elapsed /= totalDuration) * elapsed + start;
        case 'easeoutquad':
            return -(end - start) * (elapsed /= totalDuration) * (elapsed - 2) + start;
        case 'easeinoutquad':
            if ((elapsed /= totalDuration / 2) < 1) return (end - start) / 2 * elapsed * elapsed + start;
            return -(end - start) / 2 * ((--elapsed) * (elapsed - 2) - 1) + start;
        case 'easeincubic':
            return (end - start) * (elapsed /= totalDuration) * elapsed * elapsed + start;
        case 'easeoutcubic':
            return (end - start) * ((elapsed = elapsed / totalDuration - 1) * elapsed * elapsed + 1) + start;
        case 'easeinoutcubic':
            if ((elapsed /= totalDuration / 2) < 1) return (end - start) / 2 * elapsed * elapsed * elapsed + start;
            return (end - start) / 2 * ((elapsed -= 2) * elapsed * elapsed + 2) + start;
        case 'easeinquart':
            return (end - start) * (elapsed /= totalDuration) * elapsed * elapsed * elapsed + start;
        case 'easeoutquart':
            return -(end - start) * ((elapsed = elapsed / totalDuration - 1) * elapsed * elapsed * elapsed - 1) + start;
        case 'easeinoutquart':
            if ((elapsed /= totalDuration / 2) < 1) return (end - start) / 2 * elapsed * elapsed * elapsed * elapsed + start;
            return -(end - start) / 2 * ((elapsed -= 2) * elapsed * elapsed * elapsed - 2) + start;
        case 'easeinquint':
            return (end - start) * (elapsed /= totalDuration) * elapsed * elapsed * elapsed * elapsed + start;
        case 'easeoutquint':
            return (end - start) * ((elapsed = elapsed / totalDuration - 1) * elapsed * elapsed * elapsed * elapsed + 1) + start;
        case 'easeinoutquint':
            if ((elapsed /= totalDuration / 2) < 1) return (end - start) / 2 * elapsed * elapsed * elapsed * elapsed * elapsed + start;
            return (end - start) / 2 * ((elapsed -= 2) * elapsed * elapsed * elapsed * elapsed + 2) + start;
        case 'easeinsine':
            return -(end - start) * Math.cos(elapsed / totalDuration * (Math.PI / 2)) + (end - start) + start;
        case 'easeoutsine':
            return (end - start) * Math.sin(elapsed / totalDuration * (Math.PI / 2)) + start;
        case 'easeinoutsine':
            return -(end - start) / 2 * (Math.cos(Math.PI * elapsed / totalDuration) - 1) + start;
        case 'easeinexpo':
            return (elapsed == 0) ? start : (end - start) * Math.pow(2, 10 * (elapsed / totalDuration - 1)) + start;
        case 'easeoutexpo':
            return (elapsed == totalDuration) ? start + (end - start) : (end - start) * (-Math.pow(2, -10 * elapsed / totalDuration) + 1) + start;
        case 'easeinoutexpo':
            if (elapsed == 0) return start;
            if (elapsed == totalDuration) return start + (end - start);
            if ((elapsed /= totalDuration / 2) < 1) return (end - start) / 2 * Math.pow(2, 10 * (elapsed - 1)) + start;
            return (end - start) / 2 * (-Math.pow(2, -10 * --elapsed) + 2) + start;
        case 'easeincirc':
            return -(end - start) * (Math.sqrt(1 - (elapsed /= totalDuration) * elapsed) - 1) + start;
        case 'easeoutcirc':
            return (end - start) * Math.sqrt(1 - (elapsed = elapsed / totalDuration - 1) * elapsed) + start;
        case 'easeinoutcirc':
            if ((elapsed /= totalDuration / 2) < 1) return -(end - start) / 2 * (Math.sqrt(1 - elapsed * elapsed) - 1) + start;
            return (end - start) / 2 * (Math.sqrt(1 - (elapsed -= 2) * elapsed) + 1) + start;
        case 'easeinelastic':
            var s = 1.70158;
            var p = 0;
            var a = end - start;
            if (elapsed === 0) return start;
            if ((elapsed /= totalDuration) === 1) return end;
            if (!p) p = totalDuration * 0.3;
            if (a < Math.abs(end - start)) {
                a = end - start;
                var s = p / 4;
            } else {
                var s = p / (2 * Math.PI) * Math.asin((end - start) / a);
            }
            return -(a * Math.pow(2, 10 * (elapsed -= 1)) * Math.sin((elapsed * totalDuration - s) * (2 * Math.PI) / p)) + start;
        case 'easeoutelastic':
            var s = 1.70158; var p = 0; var a = (end - start);
            if (elapsed == 0) return start; if ((elapsed /= totalDuration) == 1) return start + (end - start); if (!p) p = totalDuration * .3;
            if (a < Math.abs((end - start))) { a = (end - start); var s = p / 4; }
            else var s = p / (2 * Math.PI) * Math.asin((end - start) / a);
            return a * Math.pow(2, -10 * elapsed) * Math.sin((elapsed * totalDuration - s) * (2 * Math.PI) / p) + (end - start) + start;
        case 'easeinoutelastic':
            var s = 1.70158; var p = 0; var a = (end - start);
            if (elapsed == 0) return start; if ((elapsed /= totalDuration / 2) == 2) return start + (end - start); if (!p) p = totalDuration * (.3 * 1.5);
            if (a < Math.abs((end - start))) { a = (end - start); var s = p / 4; }
            else var s = p / (2 * Math.PI) * Math.asin((end - start) / a);
            if (elapsed < 1) return -.5 * (a * Math.pow(2, 10 * (elapsed -= 1)) * Math.sin((elapsed * totalDuration - s) * (2 * Math.PI) / p)) + start;
            return a * Math.pow(2, -10 * (elapsed -= 1)) * Math.sin((elapsed * totalDuration - s) * (2 * Math.PI) / p) * .5 + (end - start) + start;
        case 'easeinback':
            var s = 1.70158;
            return (end - start) * (elapsed /= totalDuration) * elapsed * ((s + 1) * elapsed - s) + start;
        case 'easeoutback':
            var s = 1.70158;
            return (end - start) * ((elapsed = elapsed / totalDuration - 1) * elapsed * ((s + 1) * elapsed + s) + 1) + start;
        case 'easeinoutback':
            var s = 1.70158;
            if ((elapsed /= totalDuration / 2) < 1) return (end - start) / 2 * (elapsed * elapsed * (((s *= (1.525)) + 1) * elapsed - s)) + start;
            return (end - start) / 2 * ((elapsed -= 2) * elapsed * (((s *= (1.525)) + 1) * elapsed + s) + 2) + start;
        case 'easeinbounce':
            return (end - start) - this.ease('easeoutbounce', 0, (end - start), percent, totalDuration - elapsed, totalDuration) + start;
        case 'easeoutbounce':
            if ((elapsed /= totalDuration) < (1 / 2.75)) {
                return (end - start) * (7.5625 * elapsed * elapsed) + start;
            } else if (elapsed < (2 / 2.75)) {
                return (end - start) * (7.5625 * (elapsed -= (1.5 / 2.75)) * elapsed + .75) + start;
            } else if (elapsed < (2.5 / 2.75)) {
                return (end - start) * (7.5625 * (elapsed -= (2.25 / 2.75)) * elapsed + .9375) + start;
            } else {
                return (end - start) * (7.5625 * (elapsed -= (2.625 / 2.75)) * elapsed + .984375) + start;
            }
        case 'easeinoutbounce':
            if (elapsed < totalDuration / 2) return this.ease('easeinbounce', 0, (end - start), percent, elapsed * 2, totalDuration) * .5 + start;
            return this.ease('easeoutbounce', 0, (end - start), percent, elapsed * 2 - totalDuration, totalDuration) * .5 + (end - start) * .5 + start;
        default:
            return end;
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Bitmap
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Bitmap.prototype.invert = function () {
    if (this.width > 0 && this.height > 0) {
        var context = this._context;
        var imageData = context.getImageData(0, 0, this.width, this.height);
        var pixels = imageData.data;
        for (var i = 0; i < pixels.length; i += 4) {
            pixels[i + 0] = 255 - pixels[i + 0];
            pixels[i + 1] = 255 - pixels[i + 1];
            pixels[i + 2] = 255 - pixels[i + 2];
        }
        context.putImageData(imageData, 0, 0);
        this._setDirty();
    }
};

Bitmap.prototype.grayscale = function () {
    if (this.width > 0 && this.height > 0) {
        var context = this._context;
        var imageData = context.getImageData(0, 0, this.width, this.height);
        var pixels = imageData.data;
        for (var i = 0; i < pixels.length; i += 4) {
            var max = Math.max(Math.max(pixels[i + 0], pixels[i + 1]), pixels[i + 2]);
            var min = Math.min(Math.min(pixels[i + 0], pixels[i + 1]), pixels[i + 2]);
            var result = (max + min) / 2;
            pixels[i + 0] = result;
            pixels[i + 1] = result;
            pixels[i + 2] = result;
        }
        context.putImageData(imageData, 0, 0);
        this._setDirty();
    }
};

Bitmap.prototype.blackWhite = function () {
    if (this.width > 0 && this.height > 0) {
        var context = this._context;
        var imageData = context.getImageData(0, 0, this.width, this.height);
        var pixels = imageData.data;
        for (var i = 0; i < pixels.length; i += 4) {
            if (pixels[i + 3] >= 1) {
                pixels[i + 0] = 255;
                pixels[i + 1] = 255;
                pixels[i + 2] = 255;
            } else {
                pixels[i + 0] = 0;
                pixels[i + 1] = 0;
                pixels[i + 2] = 0;
            }
            pixels[i + 3] = 255;
        }
        context.putImageData(imageData, 0, 0);
        this._setDirty();
    }
};

Bitmap.prototype.testBlur = function () {
    for (var i = 0; i < 2; i++) {
        var off = 3;
        var w = this.width;
        var h = this.height;
        var canvas = this._canvas;
        var context = this._context;
        var tempCanvas = document.createElement('canvas');
        var tempContext = tempCanvas.getContext('2d');
        tempCanvas.width = w + off * 2;
        tempCanvas.height = h + off * 2;
        tempContext.drawImage(canvas, 0, 0, w, h, off, off, w, h);
        tempContext.drawImage(canvas, 0, 0, w, off, off, 0, w, off);
        tempContext.drawImage(canvas, 0, 0, off, h, 0, off, off, h);
        tempContext.drawImage(canvas, 0, h - off, w, off, off, h + off, w, off);
        tempContext.drawImage(canvas, w - off, 0, off, h, w + off, off, off, h);
        context.save();
        context.fillStyle = 'black';
        context.fillRect(0, 0, w, h);
        context.globalCompositeOperation = 'lighter';
        context.globalAlpha = 1 / Math.pow(off * 3, 2);
        for (var y = 0; y < off * 3; y++) {
            for (var x = 0; x < off * 3; x++) {
                context.drawImage(tempCanvas, x, y, w, h, 0, 0, w, h);
            }
        }
        context.restore();
    }
    this._setDirty();
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// ImageManager
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
ImageManager.loadLights = function (filename, color) {
    if (filename) {
        var path = 'img/lights/' + encodeURIComponent(filename) + '.png';
        var bitmap = this.loadLightsBitmap(path, color || '#000000');
        bitmap.smooth = false;
        return bitmap;
    } else {
        return this.loadEmptyBitmap();
    }
};

ImageManager.loadLightsBitmap = function (path, color) {
    var key = this._generateCacheKey(path, color);
    var bitmap = this._imageCache.get(key);
    if (!bitmap) {
        bitmap = Bitmap.load(decodeURIComponent(path));
        bitmap.addLoadListener(function () {
            bitmap.adjustTone(-255, -255, -255);
            var rgb = Dhoom.Utils.hexToRGB(color);
            bitmap.adjustTone(rgb[0], rgb[1], rgb[2]);
        });
        this._imageCache.add(key, bitmap);
    } else if (!bitmap.isReady()) {
        bitmap.decode();
    }
    return bitmap;
};

ImageManager.getCharacterBlockPoly = function (filename, id) {
    this._charBlockPolys = this._charBlockPolys || {};
    return this._charBlockPolys[filename + ':' + id];
};

ImageManager.setCharacterBlockPoly = function (filename, id, polys) {
    this._charBlockPolys = this._charBlockPolys || {};
    this._charBlockPolys[filename + ':' + id] = polys;
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// ConfigManager
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
ConfigManager.lightings = Graphics.isWebGL() ? 2 : 0;
ConfigManager.particles = Graphics.isWebGL() ? 2 : 0;

Dhoom.ParticleLightingSystem.ConfigManager_makeData = ConfigManager.makeData;
ConfigManager.makeData = function () {
    var config = Dhoom.ParticleLightingSystem.ConfigManager_makeData.call(this);
    config.lightings = this.lightings;
    config.particles = this.particles;
    return config;
};

Dhoom.ParticleLightingSystem.ConfigManager_applyData = ConfigManager.applyData;
ConfigManager.applyData = function (config) {
    Dhoom.ParticleLightingSystem.ConfigManager_applyData.call(this, config);
    this.lightings = config.lightings !== undefined ? config.lightings : 2;
    this.particles = config.particles !== undefined ? config.particles : true;
    if (typeof this.particles !== 'number') this.particles = 2;
    if (!Graphics.isWebGL()) {
        this.lightings = 0;
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// DataManager
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.DataManager_setupNewGame = DataManager.setupNewGame;
DataManager.setupNewGame = function () {
    Dhoom.ParticleLightingSystem.DataManager_setupNewGame.call(this);
    $gameSwitches.updateParticleLightSwitches();
};

Dhoom.ParticleLightingSystem.DataManager_loadGameWithoutRescue = DataManager.loadGameWithoutRescue;
DataManager.loadGameWithoutRescue = function (savefileId) {
    var result = Dhoom.ParticleLightingSystem.DataManager_loadGameWithoutRescue.call(this, savefileId);
    if (result) $gameSwitches.updateParticleLightSwitches();
    return result;
};

Dhoom.ParticleLightingSystem.DataManager_makeSaveContents = DataManager.makeSaveContents;
DataManager.makeSaveContents = function () {
    $gameMap.lightingBeforeSave();
    var contents = Dhoom.ParticleLightingSystem.DataManager_makeSaveContents.call(this);
    return contents;
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Switches
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Game_Switches.prototype.updateParticleLightSwitches = function () {
    var light = ConfigManager.lightings;
    this.setValue(Dhoom.ParticleLightingSystem.lightSwitch, light > 0);
    this.setValue(Dhoom.ParticleLightingSystem.lightMaxSwitch, light === 2);
    this.setValue(Dhoom.ParticleLightingSystem.particleSwitch, ConfigManager.particles);
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Particle
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Game_Particle() {
    this.initialize.apply(this, arguments);
}

Game_Particle.prototype.initialize = function (setting, x, y, originX, originY, z) {
    this._setting = setting;
    this._x = x;
    this._y = y;
    this._originX = originX;
    this._originY = originY;
    this._z = z;
    this._blendMode = setting.blend;
    this._velocity = new Point();
    this.initOpacity();
    this.initScale();
    this.initColor();
    this.initSpeed();
    this.initRotation();
    this.initDuration();
    this.initImage();
};

Game_Particle.prototype.setting = function () {
    return this._setting;
};

Game_Particle.prototype.initOpacity = function () {
    this._opacitySteps = this.setting().opacitySteps.clone();
    this._opacity = this._opacitySteps[0];
};

Game_Particle.prototype.initScale = function () {
    this._scaleMultiplier = (Math.randomInt(101 - this.setting().scaleMultiplier * 100) + this.setting().scaleMultiplier * 100) / 100;
    this._scaleSteps = this.setting().scaleSteps.clone().map(function (n) {
        return n * this._scaleMultiplier;
    }, this);
    this._scale = this._scaleSteps[0];
};

Game_Particle.prototype.initColor = function () {
    this._color = Dhoom.Utils.hexToRGB(this.setting().colorStart);
    this._colorEnd = Dhoom.Utils.hexToRGB(this.setting().colorEnd);
};

Game_Particle.prototype.initSpeed = function () {
    this._speedMultiplier = (Math.randomInt(101 - this.setting().speedMultiplier * 100) + this.setting().speedMultiplier * 100) / 100;
    this._speed = this._speedMultiplier * this.setting().speedStart;
    this._velocity.x = this._speed;
    this._velocity.y = 0;
};

Game_Particle.prototype.initRotation = function () {
    this._rotation = (Math.randomInt(this.setting().rotationMax - this.setting().rotationMin + 1) + this.setting().rotationMin) * Math.PI / 180;;
    this._noRotation = this.setting().noRotate;
    if (this._rotation) {
        var angle = this._rotation;
        var s = Math.sin(angle);
        var c = Math.cos(angle);
        var xnew = (this._velocity.x * c) - (this._velocity.y * s);
        var ynew = (this._velocity.x * s) + (this._velocity.y * c);

        this._velocity.x = xnew;
        this._velocity.y = ynew;
    }
};

Game_Particle.prototype.initDuration = function () {
    var min = this.setting().lifetimeMin * 1000;
    var max = this.setting().lifetimeMax * 1000;
    this._maxDuration = (Math.randomInt(max - min + 1) + min) / 1000 * 60;
    this._duration = this._maxDuration;
};

Game_Particle.prototype.initImage = function () {
    this._imageFilename = this.setting().images[Math.randomInt(this.setting().images.length)];
};

Game_Particle.prototype.update = function () {
    if (!this.isExpired()) {
        this.updateOpacity();
        this.updateScale();
        this.updateColor();
        this.updateRotation();
        this.updatePosition();
        this.updateDuration();
    }
};

Game_Particle.prototype.updateOpacity = function () {
    if (this.setting().opacitySteps.length > 1) {
        var index = Math.floor((this._maxDuration - this._duration) / (this._maxDuration / (this._opacitySteps.length - 1)));
        var start = this._opacitySteps[index];
        var end = this._opacitySteps[index + 1];
        var e = this._maxDuration - this._duration;
        var p = (this._maxDuration - this._duration) / this._maxDuration;
        this._opacity = Dhoom.Utils.ease(this.setting().opacityEase, start, end, p, e, this._maxDuration);
        // this._opacity = (this._opacity * (this._duration - 1) + this._opacitySteps[1]) / this._duration;
    }
};

Game_Particle.prototype.updateScale = function () {
    if (this.setting().scaleSteps.length > 1) {
        var index = Math.floor((this._maxDuration - this._duration) / (this._maxDuration / (this._scaleSteps.length - 1)));
        var start = this._scaleSteps[index];
        var end = this._scaleSteps[index + 1];
        var e = this._maxDuration - this._duration;
        var p = (this._maxDuration - this._duration) / this._maxDuration;
        this._scale = Dhoom.Utils.ease(this.setting().scaleEase, start, end, p, e, this._maxDuration);
        // this._scale = (this._scale * (this._duration - 1) + this._scaleSteps[1]) / this._duration;
    }
};

Game_Particle.prototype.updateColor = function () {
    for (var i = 0; i < this._color.length; i++) {
        this._color[i] = (this._color[i] * (this._duration - 1) + this._colorEnd[i]) / this._duration;
    }
};

Game_Particle.prototype.updateRotation = function () { };

Game_Particle.prototype.updatePosition = function () {
    this._speed = (this._speed * (this._duration - 1) + this._speedMultiplier * this.setting().speedEnd) / this._duration;
    var length = 1 / Math.sqrt((this._velocity.x * this._velocity.x) + (this._velocity.y * this._velocity.y)) * this._speed;
    this._velocity.x *= length;
    this._velocity.y *= length;
    this._x += this._velocity.x / 60;
    this._y += this._velocity.y / 60;
};

Game_Particle.prototype.updateDuration = function () {
    this._duration--;
};

Game_Particle.prototype.isExpired = function () {
    return this._duration <= 0;
};

Game_Particle.prototype.screenX = function () {
    return Math.round(this._x);
};

Game_Particle.prototype.screenY = function () {
    return Math.round(this._y);
};

Game_Particle.prototype.isLighting = function () {
    return !!this._setting.light && $gameSystem.isLightingEnabled();
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_ParticleEmitter
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Game_ParticleEmitter() {
    this.initialize.apply(this, arguments);
}

Game_ParticleEmitter.prototype.initialize = function (setting, x, y, layer, character) {
    this._setting = setting;
    this._character = character;
    this._x = x;
    this._y = y;
    this._layer = layer;
    this._active = true;
    this._particles = [];
    this._spawnCounter = 0;
    this._particleSettings = setting.particles.clone();
    this._activeDuration = this.activeDuration();
    this._waitDuration = this.waitDuration();
    this._isWaiting = false;
};

Game_ParticleEmitter.prototype.setting = function () {
    return this._setting;
};

Game_ParticleEmitter.prototype.maxParticle = function () {
    var result = this.setting().max;
    if (ConfigManager.particles === 1) result = Math.floor(result / 2);
    return result;
};

Game_ParticleEmitter.prototype.areaRect = function () {
    return new Rectangle(this.setting().x, this.setting().y, this.setting().width, this.setting().height);
};

Game_ParticleEmitter.prototype.rectType = function () {
    return ['Point', 'Rectangle', 'Circle', 'Ring'].indexOf(this.setting().type);
};

Game_ParticleEmitter.prototype.spawnRate = function () {
    return this.setting().rate;
};

Game_ParticleEmitter.prototype.activeDuration = function () {
    var min = this.setting().minDuration;
    var max = this.setting().maxDuration;
    if (min && max) {
        return Math.randomInt(max - min + 1) + min;
    }
    return 1;
};

Game_ParticleEmitter.prototype.waitDuration = function () {
    var result = 0;
    var min = this.setting().minWait;
    var max = this.setting().maxWait;
    if (min && max) {
        result = Math.randomInt(max - min + 1) + min;
        if (ConfigManager.particles === 1) result *= 2;
    }
    return result;
};

Game_ParticleEmitter.prototype.screenX = function () {
    return this._x + (this._character ? this._character.screenX() + $gameMap.displayX() * $gameMap.tileWidth() : 0);
};

Game_ParticleEmitter.prototype.screenY = function () {
    return this._y + (this._character ? this._character.screenY() + $gameMap.displayY() * $gameMap.tileHeight() : 0);
};

Game_ParticleEmitter.prototype.screenZ = function () {
    if (this._layer === 'abovepar') return 1000;
    if (['below', 'same', 'above'].contains(this._layer)) return ['below', 'same', 'above'].indexOf(this._layer) * 2 + 1;
    return 0;
};

Game_ParticleEmitter.prototype.isActive = function () {
    return this._active && !this._isWaiting && this._activeDuration > 0;
};

Game_ParticleEmitter.prototype.update = function () {
    if (this.isNearTheScreen()) {
        this.updateTimer();
        this.updateSpawn();
    }
    this.updateParticles();
};

Game_ParticleEmitter.prototype.isNearTheScreen = function () {
    var gw = Graphics.width;
    var gh = Graphics.height;
    var tw = $gameMap.tileWidth();
    var th = $gameMap.tileHeight();
    var px = $gameMap.adjustX(this._x / tw + this._character._realX) * tw + tw / 2 - gw / 2;
    var py = $gameMap.adjustY(this._y / th + this._character._realY) * th + th / 2 - gh / 2;
    return px >= -gw && px <= gw && py >= -gh && py <= gh;
};

Game_ParticleEmitter.prototype.updateTimer = function () {
    if (this._isWaiting) {
        this._waitDuration--;
        if (this._waitDuration <= 0) {
            this._activeDuration = this.activeDuration();
            this._isWaiting = false;
        }
    } else {
        this._activeDuration--;
        if (this._activeDuration <= 0) {
            this._waitDuration = this.waitDuration();
            if (this._waitDuration) {
                this._isWaiting = true;
            } else {
                this._activeDuration = this.activeDuration();
            }
        }
    }
};

Game_ParticleEmitter.prototype.updateParticles = function () {
    for (var i = 0; i < this._particles.length; i++) {
        this._particles[i].update();
        if (this._particles[i].isExpired()) {
            this._particles.splice(i, 1);
            i--;
            $gameMap._particleCounter--;
        }
    }
};

Game_ParticleEmitter.prototype.particleLimit = function () {
    var result = Dhoom.ParticleLightingSystem.limit;
    if (ConfigManager.particles === 1) result = Math.floor(result / 2);
    if (!Graphics.isWebGL()) result = 50;
    return result;
};

Game_ParticleEmitter.prototype.updateSpawn = function () {
    if (!$gameSystem.isParticleEnabled() || !this.isActive()) return;
    if ($gameMap.particleCounter() < this.particleLimit() && this._particles.length < this.maxParticle()) {
        this._spawnCounter += this.spawnRate();
        while (this._spawnCounter >= 1 && this._particles.length < this.maxParticle() && $gameMap.particleCounter() < this.particleLimit()) {
            this._spawnCounter--;
            var name = this._particleSettings[Math.randomInt(this._particleSettings.length)];
            var particle = Dhoom.ParticleLightingSystem.getParticle(name);
            var rect = this.areaRect();
            var x = rect.x;
            var y = rect.y;
            var z = this.screenZ();
            switch (this.rectType()) {
                case 1:
                    x += Math.randomInt(rect.width + 1);
                    y += Math.randomInt(rect.height + 1);
                    break;
                case 2:
                    var radius = Math.max(rect.width, rect.height) / 2;
                    var ax = Math.randomInt(radius + 1);
                    var ay = 0;
                    var angle = Math.randomInt(360) * Math.PI / 180;
                    var s = Math.sin(angle);
                    var c = Math.cos(angle);
                    x += ((ax * c) - (ay * s)) * rect.width / (radius * 2) + radius;
                    y += ((ax * s) + (ay * c)) * rect.height / (radius * 2) + radius;
                    break;
                case 3:
                    var radius = Math.max(rect.width, rect.height) / 2;
                    var minRadius = Math.max(this.setting().minWidth, this.setting().minHeight) / 2;
                    var ax = Math.randomInt(radius - minRadius + 1) + minRadius;
                    var ay = 0;
                    var angle = Math.randomInt(360) * Math.PI / 180;
                    var s = Math.sin(angle);
                    var c = Math.cos(angle);
                    x += ((ax * c) - (ay * s)) * rect.width / (radius * 2) + radius;
                    y += ((ax * s) + (ay * c)) * rect.height / (radius * 2) + radius;
                    break;
            }
            this._particles.unshift(new Game_Particle(particle, x + this._x, y + this._y, this.screenX() - this._x, this.screenY() - this._y, z));
            $gameMap._particleCounter++;
        }
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Lighting
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Game_Lighting() {
    this.initialize.apply(this, arguments);
}

Game_Lighting.prototype.initialize = function (x, y, radius, color, eventId, settingName, shiftX, shiftY, direction, brightness, id) {
    this._id = id;
    this._x = x;
    this._y = y;
    this._radius = radius;
    this._color = color instanceof Array ? color : [0, 0, 0];
    this._opacity = 255;
    this._active = true;
    this._eventId = eventId;
    this._settingName = settingName;
    this._shiftX = shiftX;
    this._shiftY = shiftY;
    this._seed = Math.random();
    this._ox = 0.5;
    this._oy = 0.5;
    this._scaleX = 100;
    this._scaleY = 100;
    this._rotation = 0;
    this._direction = direction || 0;
    this._brightness = brightness || 1;
    var d = this.setting().random ? 1000 : 0;
    this._colorDuration = this._seed * d;
    this._scaleDuration = this._seed * d;
    this._rotationDuration = this._seed * d;
    this._positionDuration = this._seed * d;
    this._nextColor = [0, 0, 0];
    this._nextOpacity = 255;
    this._nextColorDuration = 0;
    this._castShadow = !!this.setting().castShadow;
    if (this.setting()) {
        this.setupAnchor();
        this.setupColor();
        this.setupScale();
        this.setupRotation();
        this.setupPosition();
    }
};

Game_Lighting.prototype.setting = function () {
    return typeof this._settingName === 'string' ? Dhoom.ParticleLightingSystem.getLighting(this._settingName) : this._settingName;
};

Game_Lighting.prototype.isEnabled = function () {
    return $gameSystem.isLightingEnabled(this._id) && this._active;
};

Game_Lighting.prototype.texture = function () {
    return this.setting().texture;
};

Game_Lighting.prototype.color = function () {
    return this._color.map(function (col) { return Math.round(col); });
};

Game_Lighting.prototype.cssColor = function () {
    return Dhoom.Utils.rgbToCSSHex(this.color());
};

Game_Lighting.prototype.colorSetting = function (index) {
    return this.setting().colors[index];
};

Game_Lighting.prototype.currentColorSetting = function () {
    return this.colorSetting(this._colorIndex);
};

Game_Lighting.prototype.nextColorSetting = function () {
    return this.colorSetting(this._colorIndex + 1 >= this.setting().colors.length ? 0 : this._colorIndex + 1);
};

Game_Lighting.prototype.hasColorSetting = function () {
    return !!this.setting().colors.length;
};

Game_Lighting.prototype.scaleSetting = function (index) {
    return this.setting().scales[index];
};

Game_Lighting.prototype.currentScaleSetting = function () {
    return this.scaleSetting(this._scaleIndex);
};

Game_Lighting.prototype.nextScaleSetting = function () {
    return this.scaleSetting(this._scaleIndex + 1 >= this.setting().scales.length ? 0 : this._scaleIndex + 1);
};

Game_Lighting.prototype.hasScaleSetting = function () {
    return !!this.setting().scales.length;
};

Game_Lighting.prototype.scaleX = function () {
    return this._scaleX / 100;
};

Game_Lighting.prototype.scaleY = function () {
    return this._scaleY / 100;
};

Game_Lighting.prototype.positionSetting = function (index) {
    return this.setting().positions[index];
};

Game_Lighting.prototype.currentPositionSetting = function () {
    return this.positionSetting(this._positionIndex);
};

Game_Lighting.prototype.nextPositionSetting = function () {
    return this.positionSetting(this._positionIndex + 1 >= this.setting().positions.length ? 0 : this._positionIndex + 1);
};

Game_Lighting.prototype.hasPositionSetting = function () {
    return !!this.setting().positions.length;
};

Game_Lighting.prototype.x = function () {
    return this._x + this._shiftX;
};

Game_Lighting.prototype.y = function () {
    return this._y + this._shiftY;
};

Game_Lighting.prototype.opacity = function () {
    return this._opacity * this._brightness;
};

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

Game_Lighting.prototype.hasRotationSetting = function () {
    return !!this.setting().rotations.length;
};

Game_Lighting.prototype.rotationSetting = function (index) {
    return this.setting().rotations[index];
};

Game_Lighting.prototype.currentRotationSetting = function () {
    return this.rotationSetting(this._rotationIndex);
};

Game_Lighting.prototype.nextRotationSetting = function () {
    return this.rotationSetting(this._rotationIndex + 1 >= this.setting().rotations.length ? 0 : this._rotationIndex + 1);
};

Game_Lighting.prototype.setupAnchor = function () {
    this._ox = this.setting().ox;
    this._oy = this.setting().oy;
};

Game_Lighting.prototype.setupColor = function () {
    if (!this.hasColorSetting()) return;
    this._colorIndex = 0;
    var setting = this.currentColorSetting();
    this._color = [0, 0, 0];
    var colors = ['red', 'green', 'blue'];
    colors.forEach(function (sym, i) {
        if (typeof setting[sym] === "string") {
            var nums = setting[sym].split('~').map(function (t) { return Number(t.trim()) });
            this._color[i] = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
        } else if (setting[sym] >= 0) {
            this._color[i] = setting[sym];
        }
    }, this);
    this._opacity = setting.opacity >= 0 ? setting.opacity : Math.randomInt(256);
    this.setNextColor();
};

Game_Lighting.prototype.setNextColor = function () {
    var setting = this.nextColorSetting();
    var colors = ['red', 'green', 'blue'];
    colors.forEach(function (sym, i) {
        if (setting[sym] === -1) {
            this._nextColor[i] = this._color[i];
        } else if (typeof setting[sym] === "string") {
            var nums = setting[sym].split('~').map(function (t) { return Number(t.trim()) });
            this._nextColor[i] = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
        } else if (setting[sym] >= 0) {
            this._nextColor[i] = setting[sym];
        }
    }, this);
    if (setting.opacity === -1) {
        this._nextOpacity = this._opacity;
    } else if (typeof setting.opacity === "string") {
        var nums = setting.opacity.split('~').map(function (t) { return Number(t.trim()) });
        this._nextOpacity = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else if (setting.opacity >= 0) {
        this._nextOpacity = setting.opacity;
    }
};

Game_Lighting.prototype.setupScale = function () {
    if (!this.hasScaleSetting()) return;
    this._scaleIndex = 0;
    var setting = this.currentScaleSetting();
    if (typeof setting.x === "string") {
        var nums = setting.x.split('~').map(function (t) { return Number(t.trim()) });
        this._scaleX = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else if (setting.x > -9999) {
        this._scaleX = setting.x;
    }
    if (typeof setting.y === "string") {
        var nums = setting.y.split('~').map(function (t) { return Number(t.trim()) });
        this._scaleY = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else if (setting.y > -9999) {
        this._scaleY = setting.y;
    }
    if (setting.proportions) {
        this._scaleX = Math.max(this._scaleX, this._scaleY);
        this._scaleY = this._scaleX;
    }
    this.setNextScale();
};

Game_Lighting.prototype.setNextScale = function () {
    var setting = this.nextScaleSetting();
    if (setting.x === -9999) {
        this._nextScaleX = this._scaleX;
    } else if (typeof setting.x === "string") {
        var nums = setting.x.split('~').map(function (t) { return Number(t.trim()) });
        this._nextScaleX = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else {
        this._nextScaleX = setting.x;
    }
    if (setting.y === -9999) {
        this._nextScaleY = this._scaleY;
    } else if (typeof setting.y === "string") {
        var nums = setting.y.split('~').map(function (t) { return Number(t.trim()) });
        this._nextScaleY = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else {
        this._nextScaleY = setting.y;
    }
    if (setting.proportions) {
        this._nextScaleX = Math.max(this._nextScaleX, this._nextScaleY);
        this._nextScaleY = this._nextScaleX;
    }
};

Game_Lighting.prototype.setupRotation = function () {
    if (!this.hasRotationSetting()) return;
    this._rotationIndex = 0;
    var setting = this.currentRotationSetting();
    if (typeof setting.rotation === "string") {
        var nums = setting.rotation.split('~').map(function (t) { return Number(t.trim()) });
        this._rotation = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else if (setting.rotation > -9999) {
        this._rotation = setting.rotation;
    }
    this.setNextRotation();
};

Game_Lighting.prototype.setNextRotation = function () {
    var setting = this.nextRotationSetting();
    if (setting.rotation === -9999) {
        this._nextRotation = this._rotation;
    } else if (typeof setting.rotation === "string") {
        var nums = setting.rotation.split('~').map(function (t) { return Number(t.trim()) });
        this._nextRotation = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else {
        this._nextRotation = setting.rotation;
    }
};

Game_Lighting.prototype.setupPosition = function () {
    if (!this.hasPositionSetting()) return;
    this._positionIndex = 0;
    var setting = this.currentPositionSetting();
    if (typeof setting.x === "string") {
        var nums = setting.x.split('~').map(function (t) { return Number(t.trim()) });
        this._x = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else if (setting.x > -9999) {
        this._x = setting.x;
    }
    if (typeof setting.y === "string") {
        var nums = setting.y.split('~').map(function (t) { return Number(t.trim()) });
        this._y = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else if (setting.y > -9999) {
        this._y = setting.y;
    }
    this.setNextPosition();
};

Game_Lighting.prototype.setNextPosition = function () {
    var setting = this.nextPositionSetting();
    if (setting.x === -9999) {
        this._nextX = this._x;
    } else if (typeof setting.x === "string") {
        var nums = setting.x.split('~').map(function (t) { return Number(t.trim()) });
        this._nextX = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else {
        this._nextX = setting.x;
    }
    if (setting.y === -9999) {
        this._nextY = this._y;
    } else if (typeof setting.y === "string") {
        var nums = setting.y.split('~').map(function (t) { return Number(t.trim()) });
        this._nextY = Math.randomInt(nums[1] - nums[0] + 1) + nums[0];
    } else {
        this._nextY = setting.y;
    }
};

Game_Lighting.prototype.update = function () {
    if (!this.isEnabled()) return;
    this.updateColor();
    this.updateScale();
    this.updateRotation();
    this.updatePosition();
};

Game_Lighting.prototype.updateColor = function () {
    if (this.hasColorSetting()) {
        this._colorDuration++;
        var setting = this.nextColorSetting();
        var colors = ['red', 'green', 'blue'];
        if (this._colorDuration >= setting.duration) {
            this._colorIndex++;
            if (this._colorIndex >= this.setting().colors.length) this._colorIndex = 0;
            this._opacity = this._nextOpacity;
            colors.forEach(function (sym, i) {
                this._color[i] = this._nextColor[i];
            }, this);
            this._colorDuration -= setting.duration;
            this.setNextColor();
        } else if (setting.interpolate) {
            var d = setting.duration - this._colorDuration;
            this._opacity = (this._opacity * (d - 1) + this._nextOpacity) / d;
            colors.forEach(function (sym, i) {
                this._color[i] = (this._color[i] * (d - 1) + this._nextColor[i]) / d;
            }, this);
        }
    }
};

Game_Lighting.prototype.updateScale = function () {
    if (this.hasScaleSetting()) {
        this._scaleDuration++;
        var setting = this.nextScaleSetting();
        if (this._scaleDuration >= setting.duration) {
            this._scaleIndex++;
            if (this._scaleIndex >= this.setting().scales.length) this._scaleIndex = 0;
            this._scaleX = this._nextScaleX;
            this._scaleY = this._nextScaleY;
            this._scaleDuration -= setting.duration;
            this.setNextScale();
        } else if (setting.interpolate) {
            var d = setting.duration - this._scaleDuration;
            this._scaleX = (this._scaleX * (d - 1) + this._nextScaleX) / d;
            this._scaleY = (this._scaleY * (d - 1) + this._nextScaleY) / d;
        }
    }
};

Game_Lighting.prototype.updateRotation = function () {
    if (this.hasRotationSetting()) {
        this._rotationDuration++;
        var setting = this.nextRotationSetting();
        if (this._rotationDuration >= setting.duration) {
            this._rotationIndex++;
            if (this._rotationIndex >= this.setting().rotations.length) this._rotationIndex = 0;
            this._rotation = this._nextRotation;
            this._rotationDuration -= setting.duration;
            this.setNextRotation();
        } else if (setting.interpolate) {
            var d = setting.duration - this._rotationDuration;
            this._rotation = (this._rotation * (d - 1) + this._nextRotation) / d;
        }
    }
};

Game_Lighting.prototype.updatePosition = function () {
    if (this.hasPositionSetting()) {
        this._positionDuration++;
        var setting = this.nextPositionSetting();
        if (this._positionDuration >= setting.duration) {
            this._positionIndex++;
            if (this._positionIndex >= this.setting().positions.length) this._positionIndex = 0;
            this._x = this._nextX;
            this._y = this._nextY;
            this._positionDuration -= setting.duration;
            this.setNextPosition();
        } else if (setting.interpolate) {
            var d = setting.duration - this._positionDuration;
            this._x = (this._x * (d - 1) + this._nextX) / d;
            this._y = (this._y * (d - 1) + this._nextY) / d;
        }
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Event
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Game_Event_setupPageSettings = Game_Event.prototype.setupPageSettings;
Game_Event.prototype.setupPageSettings = function () {
    Dhoom.ParticleLightingSystem.Game_Event_setupPageSettings.call(this);

    if (this._lastParLightSystemPage !== this.page()) {
        this._lastParLightSystemPage = null;
        $gameMap.clearParticleEmitters(this.eventId());
				this._lightings = [];
				this._flashlightActive = false;

				// Make sure the flashlight is always lightings[0] if enabled
				this.eventFlashlightScanNote();
        this.scanParticleEmitters();
        this.scanLightings();
    }
};

Dhoom.ParticleLightingSystem.Game_Event_clearPageSettings = Game_Event.prototype.clearPageSettings;
Game_Event.prototype.clearPageSettings = function () {
    Dhoom.ParticleLightingSystem.Game_Event_clearPageSettings.call(this);
    this._lastParLightSystemPage = null;
    $gameMap.clearParticleEmitters(this.eventId());
    if ($gameMap._lightingEvents.contains(this.eventId()))
        $gameMap._lightingEvents.splice($gameMap._lightingEvents.indexOf(this.eventId()), 1);
    if ($gameMap._blockLightEvents.contains(this.eventId()))
        $gameMap._blockLightEvents.splice($gameMap._blockLightEvents.indexOf(this.eventId()), 1);
    this._lightings = [];
    this._blockLight = undefined;
		this._flashlightActive = false;
};

Game_Event.prototype.scanParticleEmitters = function () {
    this._lastParLightSystemPage = this.page();
    var page = this.page();
    for (var i = 0; i < page.list.length; i++) {
        var command = page.list[i];
        if (command.code === 108 || command.code === 408) {
            if (command.parameters[0].match(/<particleemitter:\s*(.+),\s*(-?\d+),\s*(-?\d+),\s*(below|same|above|abovepar)>/i)) {
                $gameMap.addParticleEmitter(this.eventId(), this, RegExp.$1, Number(RegExp.$2), Number(RegExp.$3), RegExp.$4);
            }
        }
    }
};

Game_Event.prototype.scanLightings = function () {
    if ($gameMap._lightingEvents.contains(this.eventId()))
        $gameMap._lightingEvents.splice($gameMap._lightingEvents.indexOf(this.eventId()), 1);
    if ($gameMap._blockLightEvents.contains(this.eventId()))
        $gameMap._blockLightEvents.splice($gameMap._blockLightEvents.indexOf(this.eventId()), 1);
		if(this._lightings === undefined)
			this._lightings = [];
    this._blockLight = undefined;
    var notes = this.event().note;
    if (notes.toLowerCase().split(" ")[0] === "light") {
        this.addLighting(notes.split(" ").slice(1).join(' '));
    }
    var list = this.page().list;
    for (var i = 0; i < list.length; i++) {
        if (list[i].code === 108 || list[i].code === 408) {
            var notes = list[i].parameters[0].split(/[\r\n]+/);
            for (var j = 0; j < notes.length; j++) {
                if (notes[j].match(/light\s+(\d+)\s+(.+)/i)) {
                    this.addLighting(notes[j].split(" ").slice(1).join(' '));
                }
                if (notes[j].match(/<blocklight:\s*auto>/i)) {
                    if (!$gameMap._blockLightEvents.contains(this.eventId())) $gameMap._blockLightEvents.push(this.eventId());
                    this._blockLight = 'auto';
                } else if (notes[j].match(/<blocklight:((?:\s*-?\d+:-?\d+\s*)+)>/i)) {
                    if (!$gameMap._blockLightEvents.contains(this.eventId())) $gameMap._blockLightEvents.push(this.eventId());
                    this._blockLight = RegExp.$1.trim().split(" ").map(function (set) {
                        return set.split(':').map(function (a) { return Number(a) });
                    });
                } else if (notes[j].match(/<blocklight>/i)) {
                    if (!$gameMap._blockLightEvents.contains(this.eventId())) $gameMap._blockLightEvents.push(this.eventId());
                    this._blockLight = [[0, 0], [0, 48], [48, 48], [48, 0]];
                }
            }
        }
    }
};

Game_Event.prototype.addLighting = function (param) {
    if (param.contains('#')) {
        var args = param.toLowerCase().split(" ");
        var radius = Number(args.shift());
        var color = Dhoom.Utils.hexToRGB(args.shift());
        var dir = 0;
        var bright = 1;
        var id;
        while (args.length) {
            if (args[0].contains('d')) {
                dir = Number(args[0].substring(1));
            } else if (args[0].contains('b')) {
                bright = Number(args[0].substring(1)) / 100;
            } else if (!isNaN(Number(args[0]))) {
                id = Number(args[0]);
            }
            args.shift();
        }
        var setting = Dhoom.ParticleLightingSystem.lightingSettings[0];
        var obj = new Game_Lighting(0, 0, radius, color, this.eventId(), setting.name, 0, 0, dir, bright, id);
        if (!$gameMap._lightingEvents.contains(this.eventId()))
            $gameMap._lightingEvents.push(this.eventId());
        this._lightings.push(obj);
    } else if (param.match(/(\d+)\s+(.+)/i)) {
        var radius = Number(RegExp.$1);
        var color = RegExp.$2.trim();
        var id = 0;
        if (param.match(/(\d+)\s+(.+)(?:\s+(\d+))/i)) {
            color = RegExp.$2.trim();
            id = Number(RegExp.$3);
        }
        var setting = Dhoom.ParticleLightingSystem.getLighting(color);
        if (!setting) {
            if (id) {
                setting = Dhoom.ParticleLightingSystem.getLighting(color + ' ' + id);
            }
            if (!setting) {
                setting = Dhoom.ParticleLightingSystem.lightingSettings[0];
                color = Dhoom.Utils.hexToRGB(color);
            } else {
                id = 0;
            }
        }
        var obj = new Game_Lighting(0, 0, radius, color, this.eventId(), setting.name, 0, 0, 0, 0, id);
        if (!$gameMap._lightingEvents.contains(this.eventId()))
            $gameMap._lightingEvents.push(this.eventId());
        this._lightings.push(obj);
    }
};

Game_Event.prototype.getLightBlock = function () {
    if (this._blockLight === 'auto') {
        if (SceneManager._scene._spriteset) {
            var filename = this.tileId() > 0 ? $gameMap.tileset().tilesetNames[5 + Math.floor(this.tileId() / 256)] : this.characterName();
            var id = this.tileId() > 0 ? this.tileId() : this.characterIndex() + ':' + this.pattern();
            if (ImageManager.getCharacterBlockPoly(filename, id)) {
                return ImageManager.getCharacterBlockPoly(filename, id);
            }
            var result = [];
            var sprite = SceneManager._scene._spriteset.getCharacterSprite(this);
            var bitmap = sprite.bitmap;
            var bx = sprite._frame.x;
            var by = sprite._frame.y;
            var bw = sprite._frame.width;
            var bh = sprite._frame.height;
            var sx = 0;
            var sy = 0;
            var ex = bw;
            var ey = bh;
            var threshold = 1;
            var found = false;
            for (var y = 0; y < bh; y++) {
                for (var x = 0; x < bw; x++) {
                    if (bitmap.getAlphaPixel(x + bx, y + by) >= threshold) {
                        sy = y;
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
            found = false;
            for (var y = bh - 1; y >= 0; y--) {
                for (var x = 0; x < bw; x++) {
                    if (bitmap.getAlphaPixel(x + bx, y + by) >= threshold) {
                        ey = y;
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
            found = false;
            for (var x = 0; x < bw; x++) {
                for (var y = 0; y < bh; y++) {
                    if (bitmap.getAlphaPixel(x + bx, y + by) >= threshold) {
                        sx = x;
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
            found = false;
            for (var x = bw - 1; x >= 0; x--) {
                for (var y = 0; y < bh; y++) {
                    if (bitmap.getAlphaPixel(x + bx, y + by) >= threshold) {
                        ex = x;
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
            result.push([sx, sy]);
            result.push([ex + 1, sy]);
            result.push([ex + 1, ey + 1]);
            result.push([sx, ey + 1]);
            ImageManager.setCharacterBlockPoly(filename, id, result);
            return result;
        } else {
            return [];
        }
    }
    return this._blockLight;
};

Game_Event.prototype.eventFlashlightScanNote = function()
{
	if(this._lightings !== undefined && this._lightings.length > 0) return;

	var page = this.page();
	this.page().list.forEach(function (c) {
		if (c.code === 108 || c.code === 408)
		{
			var match = c.parameters[0].match(/<flashlight:\s*(\d+),\s*(\d+)>/i);
			if (match)
				this.initFlashlight(parseInt(match[1]), parseInt(match[2]));
		}
	}, this);
};

Game_Event.prototype.initFlashlight = function (length, width) {
		if(this._lightings === undefined) this._lightings = [];
    this._lightings.push(new Game_Lighting(0, 0, 200, [0, 0, 0], 0, Dhoom.ParticleLightingSystem.flashlightSetting, 0, 0, 0, 1, 0));
    this._flashlightActive = true;
    this._flashlightColor = [0xFF, 0xFF, 0xFF];
    this._flashlightLength = length;
    this._flashlightWidth = width;
    this._flashlightDensity = 3;
};

Game_Event.prototype.isFlashlightActive = function () {
    return !!this._flashlightActive;
};

Game_Event.prototype.flashlightColor = function () {
    return this._flashlightColor;
};

Game_Event.prototype.flashlightLength = function () {
    return this._flashlightLength;
};

Game_Event.prototype.flashlightWidth = function () {
    return this._flashlightWidth;
};


Dhoom.ParticleLightingSystem.Game_Event_canMove = Game_Event.prototype.canMove;
Game_Event.prototype.canMove = function () {
    if (SceneManager._scene instanceof Scene_Map && SceneManager._scene._lightingSliderWindow && SceneManager._scene._lightingSliderWindow.active) return false;
    return Dhoom.ParticleLightingSystem.Game_Event_canMove.call(this);
};

Dhoom.ParticleLightingSystem.Game_Event_update = Game_Event.prototype.update;
Game_Event.prototype.update = function () {
    Dhoom.ParticleLightingSystem.Game_Event_update.call(this);
    this.updateFlashlight();
};

Game_Event.prototype.updateFlashlight = function () {
	if(!this._flashlightActive || !this._lightings) return;

    set = this._lightings[0];

		if(!set) return;

    set._color = this.flashlightColor();
    set._active = this.isFlashlightActive();
    set._opacity = this.isFlashlightActive() ? 190 : 0;
    set._scaleX = 15 * this.flashlightWidth();
    set._scaleY = 22 * this.flashlightLength();
    set._castShadow = true;
    switch (this._direction) {
        case 2:
            set._rotation = 0;
            break;
        case 4:
            set._rotation = 90;
            break;
        case 6:
            set._rotation = 270;
            break;
        case 8:
            set._rotation = 180;
            break;
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Player
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Game_Player_initMembers = Game_Player.prototype.initMembers;
Game_Player.prototype.initMembers = function () {
    Dhoom.ParticleLightingSystem.Game_Player_initMembers.call(this);
    this.initLighting();
};

Game_Player.prototype.initLighting = function () {
    this._lightings = [];
    this._lightings.push(new Game_Lighting(0, 0, 0, [255, 255, 255], 0, Dhoom.ParticleLightingSystem.playerLightSetting, 0, 0, 0, 1, 0));
    this._lightings.push(new Game_Lighting(0, 0, 200, [0, 0, 0], 0, Dhoom.ParticleLightingSystem.flashlightSetting, 0, 0, 0, 1, 0));
    this._flashlightActive = false;
		this._flashlightForceToggle = false;
    this._flashlightColor = [0, 0, 0];
    this._flashlightLength = 8;
    this._flashlightWidth = 12;
    this._flashlightDensity = 3;
};

Game_Player.prototype.isFlashlightActive = function () {
    return ((!!this._flashlightActive) && !this._flashlightForceToggle);
};

Game_Player.prototype.flashlightColor = function () {
    return this._flashlightColor;
};

Game_Player.prototype.flashlightLength = function () {
    return this._flashlightLength;
};

Game_Player.prototype.flashlightWidth = function () {
    return this._flashlightWidth;
};

Game_Player.prototype.flashlightDensity = function () {
    return this._flashlightDensity;
};

Game_Player.prototype.isFlashlightHit = function (x, y) {
    if (Dhoom.ParticleLightingSystem.lightBlockTerrain.contains($gameMap.terrainTag(x, y)) ||
        Dhoom.ParticleLightingSystem.lightBlockRegion.contains($gameMap.regionId(x, y))) return true;
    if (!$gamePlayer.canPass(x, y) && !$gamePlayer.isCollidedWithCharacters(x, y)) return true;
    return false;
};

Dhoom.ParticleLightingSystem.Game_Player_canMove = Game_Player.prototype.canMove;
Game_Player.prototype.canMove = function () {
    if (SceneManager._scene instanceof Scene_Map && SceneManager._scene._lightingSliderWindow && SceneManager._scene._lightingSliderWindow.active) return false;
    return Dhoom.ParticleLightingSystem.Game_Player_canMove.call(this);
};

Dhoom.ParticleLightingSystem.Game_Player_update = Game_Player.prototype.update;
Game_Player.prototype.update = function (sceneActive) {
    Dhoom.ParticleLightingSystem.Game_Player_update.call(this, sceneActive);
    this.updateLighting();
		this.updateFlashlightToggle();
};

Game_Player.prototype.updateFlashlightToggle = function ()
{
		if(Input.isTriggered("flashlight"))
			this._flashlightForceToggle = !this._flashlightForceToggle;
};

Game_Player.prototype.updateLighting = function () {
    var set = this._lightings[0];
    if (!(set instanceof Game_Lighting)) {
        var rad = set._radius > 1 ? set._radius : 0;
        var tar = set.target > 1 ? set.target : 0;
        var prev = set.prevRadius;
        var tarDur = set.targetDuration;
        set = new Game_Lighting(0, 0, rad, [255, 255, 255], 0, Dhoom.ParticleLightingSystem.playerLightSetting, 0, 0, 0, 1, 0);
        set.target = tar;
        set.prevRadius = prev;
        set.targetDuration = tarDur;
    }
    if (set._radius !== set.target) {
        var speed = (Math.abs(set.target - set.prevRadius)) / set.targetDuration;
        if (set._radius > set.target) {
            set._radius -= speed;
            if (set._radius < set.target) set._radius = set.target;
        } else if (set._radius < set.target) {
            set._radius += speed;
            if (set._radius > set.target) set._radius = set.target;
        }
        if (set._radius === set.target) set.prevRadius = set._radius;
    }
    set._active = set._radius > 1;
    set._opacity = set._radius ? 255 : 0;
    if (!this._lightings[1]) {
        this._lightings[1] = new Game_Lighting(0, 0, 200, [0, 0, 0], 0, Dhoom.ParticleLightingSystem.flashlightSetting, 0, 0, 0, 1, 0);
    }
    set = this._lightings[1];
    set._color = this.flashlightColor();
    set._active = this.isFlashlightActive();
    set._opacity = this.isFlashlightActive() ? 190 : 0;
    set._scaleX = 15 * this.flashlightWidth();
    set._scaleY = 22 * this.flashlightLength();
    set._castShadow = true;
    switch (this._direction) {
        case 2:
            set._rotation = 0;
            break;
        case 4:
            set._rotation = 90;
            break;
        case 6:
            set._rotation = 270;
            break;
        case 8:
            set._rotation = 180;
            break;
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_System
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Game_System_initialize = Game_System.prototype.initialize;
Game_System.prototype.initialize = function () {
    Dhoom.ParticleLightingSystem.Game_System_initialize.call(this);
    this._lightEnabledIds = {};
};

Game_System.prototype.isLightingEnabled = function (id) {
    if (!Graphics.isWebGL()) return false;
    if (Dhoom.ParticleLightingSystem.lightSwitch && !$gameSwitches.value(Dhoom.ParticleLightingSystem.lightSwitch)) return false;
    if (!id) return true;
    if (this._lightEnabledIds[id] === undefined) return false;
    if (this._lightEnabledIds[id]) return true;
    return false;
};

Game_System.prototype.isParticleEnabled = function () {
    return !Dhoom.ParticleLightingSystem.particleSwitch || $gameSwitches.value(Dhoom.ParticleLightingSystem.particleSwitch);
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Map
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Game_Map_initialize = Game_Map.prototype.initialize;
Game_Map.prototype.initialize = function () {
    Dhoom.ParticleLightingSystem.Game_Map_initialize.call(this);
	this._setupDone = false;
    this._particleEmitters = {};
    this._particleCounter = 0;
    this.clearParticleEmitters();
    this.clearLightings();
};

Game_Map.prototype.clearParticleEmitters = function (id) {
    this._particleEmitters = this._particleEmitters || {};
    this._particleCounter = this._particleCounter || 0;
    if (isNaN(id)) {
        this._particleEmitters = {};
        this._particleCounter = 0;
    } else {
        if (this._particleEmitters[id]) {
            this._particleEmitters[id].forEach(function (emitter) {
                this._particleCounter -= emitter._particles.length;
            }, this);
        }
        delete this._particleEmitters[id];
    }
};

Game_Map.prototype.clearLightings = function () {
    this._lightingEvents = [];
    this._lightingRegions = {};
    this._blockLightEvents = [];
};

Dhoom.ParticleLightingSystem.Game_Map_setup = Game_Map.prototype.setup;
Game_Map.prototype.setup = function (mapId) {
    this.clearParticleEmitters();
    this.clearLightings();
    this.clearLightingPolys();
    Dhoom.ParticleLightingSystem.Game_Map_setup.call(this, mapId);
    this.setupLightingPolys();
	this._setupDone = true;
};


Dhoom.ParticleLightingSystem.Game_Map_refresh = Game_Map.prototype.refresh;
Game_Map.prototype.refresh = function () {
    Dhoom.ParticleLightingSystem.Game_Map_refresh.call(this);
    if (!$gameTemp._terrainBlockBitmap || $gameTemp._terrainBlockBitmap._mapId !== this.mapId()) this.setupLightingPolys();;
};

Dhoom.ParticleLightingSystem.Game_Map_update = Game_Map.prototype.update;
Game_Map.prototype.update = function (sceneActive) {
    Dhoom.ParticleLightingSystem.Game_Map_update.call(this, sceneActive);
    this.updateParticleEmitters();
    this.updateLightings();
};

Game_Map.prototype.updateParticleEmitters = function () {
    var keys = Object.keys(this._particleEmitters);
    keys.forEach(function (id) {
        if (this._particleEmitters[id]) {
            this._particleEmitters[id].forEach(function (emitter) {
                emitter.update();
            });
        } else {
            delete this._particleEmitters[id];
        }
    }, this);
};

Game_Map.prototype.addParticleEmitter = function (id, character, name, x, y, layer) {
    var emitter = Dhoom.ParticleLightingSystem.getEmitter(name);
    if (emitter) {
        this._particleEmitters[id] = this._particleEmitters[id] || [];
        this._particleEmitters[id].push(new Game_ParticleEmitter(emitter, x, y, layer, character));
    }
};

Game_Map.prototype.particleCounter = function () {
    return this._particleCounter;
};

Game_Map.prototype.lightingShaderUniforms = function () {
    if (!this._lightingShaderUniforms) this.initLightingShaderUniforms();
    if (!this._lightingShaderUniforms.u_ambient_wall)
        this._lightingShaderUniforms.u_ambient_wall = {
            type: "1f",
            value: 0.5
        };
    return this._lightingShaderUniforms;
};

Game_Map.prototype.initLightingShaderUniforms = function () {
    this._lightingShaderUniforms = {
        u_time: {
            type: "1f",
            value: 0.0
        },
        u_resolution: {
            type: "fv1",
            value: [Graphics.width, Graphics.height]
        },
        u_lights: {
            type: "sampler2D",
            value: 0
        },
        u_col: {
            type: "1f",
            value: 0.5
        },
        u_ambient: {
            type: "1f",
            value: 0.5
        },
        u_ambient_wall: {
            type: "1f",
            value: 0.5
        },
        u_ambientColor: {
            type: "vec3",
            value: [0.0, 0.0, 0.0]
        },
        u_contrast: {
            type: "1f",
            value: 1.0
        },
        u_saturation: {
            type: "1f",
            value: 1.0
        },
        u_pop_strength: {
            type: "1f",
            value: 0.0
        },
        u_pop_threshold: {
            type: "1f",
            value: 0.0
        },
        u_brightness: {
            type: "1f",
            value: 0.0
        },
        u_color_contrast: {
            type: "1f",
            value: 3.0
        },
        enabled: false,
    };
};

Game_Map.prototype.updateLightingShader = function () {
    var uniforms = this._lightingShaderUniforms;
    uniforms.u_time.value += 0.01;
    if (uniforms.ambientDuration) {
        var d = uniforms.ambientDuration;
        if (uniforms.u_ambient.target !== undefined) uniforms.u_ambient.value = (uniforms.u_ambient.value * (d - 1) + uniforms.u_ambient.target) / d;
        if (uniforms.u_ambient_wall.target !== undefined) uniforms.u_ambient_wall.value = (uniforms.u_ambient_wall.value * (d - 1) + uniforms.u_ambient_wall.target) / d;
        if (uniforms.u_ambientColor.target !== undefined) {
            uniforms.u_ambientColor.value[0] = (uniforms.u_ambientColor.value[0] * (d - 1) + uniforms.u_ambientColor.target[0]) / d;
            uniforms.u_ambientColor.value[1] = (uniforms.u_ambientColor.value[1] * (d - 1) + uniforms.u_ambientColor.target[1]) / d;
            uniforms.u_ambientColor.value[2] = (uniforms.u_ambientColor.value[2] * (d - 1) + uniforms.u_ambientColor.target[2]) / d;
        }
        uniforms.ambientDuration--;
    }
    $gameTemp._lightingShaderRefreshRequested = true;
};

Game_Map.prototype.addRegionLighting = function (regionId, color, radius) {
    this._lightingRegions[regionId] = [];
    var setting = Dhoom.ParticleLightingSystem.getLighting(color) || Dhoom.ParticleLightingSystem.lightingSettings[0];
    for (var x = 0; x < this.width(); x++) {
        for (var y = 0; y < this.height(); y++) {
            if (this.regionId(x, y) === regionId) {
                this._lightingRegions[regionId].push(new Game_Lighting(x, y, radius, color, 0, setting.name, 0, 0));
            }
        }
    }
};

Game_Map.prototype.removeRegionLighting = function (regionId) {
    delete this._lightingRegions[regionId];
};

Game_Map.prototype.updateLightings = function () {
    var ids = Object.keys(this._lightingRegions);
    for (var i = 0; i < ids.length; i++) {
        this._lightingRegions[ids[i]].forEach(function (obj) {
            obj.update();
        }, this);
    }
    this._lightingEvents.forEach(function (eventId) {
        this.event(eventId)._lightings.forEach(function (obj) {
            obj.update();
        }, this);
    }, this);
    for (var i = 1; i < $gamePlayer._lightings.length; i++) {
        $gamePlayer._lightings[i].update();
    }
};

Game_Map.prototype.lightingBeforeSave = function () {
    this._terrainBlockPos = [];
    this._terrainBlocks = [];
    this._cachedScreenEdges = {};
    this._cachedTerrainPolys = {};
    this._cachedTerrainEdges = {};
    this._terrainBlockBitmap = null;
};


Dhoom.ParticleLightingSystem.Game_Event_processMoveCommand = Game_Event.prototype.processMoveCommand;
Game_Event.prototype.processMoveCommand = function (command) {
    this._inProcessMoveCommand = true;
    Dhoom.ParticleLightingSystem.Game_Event_processMoveCommand.call(this, command);
    this._inProcessMoveCommand = false;
};

Dhoom.ParticleLightingSystem.Game_Event_setThrough = Game_Event.prototype.setThrough;
Game_Event.prototype.setThrough = function(through)
{
	Dhoom.ParticleLightingSystem.Game_Event_setThrough.call(this, through);
	if($gameMap._mapId !== this._mapId) return;
	if(this._inProcessMoveCommand) return;

	if(Dhoom.ParticleLightingSystem.lightBlockRegion.contains($gameMap.regionId(this.x, this.y)))
		$gameMap.recalcVisibilty();
};


Game_Map.prototype.recalcVisibilty = function()
{
	if(!this._setupDone) return;
	console.log("recalcVisibilty");
	this.clearLightingPolys();
	this.setupLightingPolys();
}

Game_Map.prototype.clearLightingPolys = function () {
    $gameTemp._terrainBlockPos = [];
    $gameTemp._terrainBlocks = [];
    $gameTemp._cachedScreenEdges = {};
    $gameTemp._cachedTerrainPolys = {};
    $gameTemp._cachedTerrainEdges = {};
    $gameTemp._terrainBlockBitmap = null;
};

Game_Map.prototype.setupLightingPolys = function () {
    if (!$gameTemp._terrainBlockPos) {
        this.clearLightingPolys();
    }
    var gw = this.width();
    var gh = this.height();
    var tw = this.tileWidth();
    var th = this.tileHeight();
    $gameTemp._terrainBlockBitmap = new Bitmap(gw * tw, gh * th);
    $gameTemp._terrainBlockBitmap._mapId = this.mapId();
    for (var x = 0; x < gw; x++) {
        for (var y = 0; y < gh; y++) {
            if (Dhoom.ParticleLightingSystem.lightBlockTerrain.contains(this.terrainTag(x, y)) ||
                (Dhoom.ParticleLightingSystem.lightBlockRegion.contains(this.regionId(x, y)) && (!$gamePlayer.canPass(x, y) || this.layeredTiles(x,y).some(function (tile) {return Tilemap.isWallTile(tile);}, this) ))) {
                // $gameTemp._terrainBlockBitmap.fillRect(x * tw, y * th, tw, th, '#000000');
                // if (!$gameTemp._terrainBlockPos.contains(x + ':' + y)) $gameTemp._terrainBlockPos.push(x + ':' + y);
                var id = y * gw + x;
                if (!$gameTemp._terrainBlocks[id]) {
                    $gameTemp._terrainBlocks[id] = {};
                    $gameTemp._terrainBlocks[id].edgeExists = [false, false, false, false];
                    $gameTemp._terrainBlocks[id].edgeIds = [0, 0, 0, 0];
                }
            }
        }
    }
    this._terrainEdges = [null];
    var north = 0;
    var south = 1;
    var east = 2;
    var west = 3;
    for (var x = 0; x < gw; x++) {
        for (var y = 0; y < gh; y++) {
            var id = y * gw + x;
            if ($gameTemp._terrainBlocks[id]) {
                var i = [x, y];
                var n = [x, y - 1];
                var s = [x, y + 1];
                var w = [x - 1, y];
                var e = [x + 1, y];
                var ni = n[1] * gw + n[0];
                var si = s[1] * gw + s[0];
                var wi = w[1] * gw + w[0];
                var ei = e[1] * gw + e[0];
                if (!this.isValid(w[0], w[1]) || !$gameTemp._terrainBlocks[wi]) {
                    if (this.isValid(n[0], n[1]) && $gameTemp._terrainBlocks[ni] && $gameTemp._terrainBlocks[ni].edgeExists[west]) {
                        this._terrainEdges[$gameTemp._terrainBlocks[ni].edgeIds[west]].ey += th;
                        $gameTemp._terrainBlocks[id].edgeIds[west] = $gameTemp._terrainBlocks[ni].edgeIds[west];
                        $gameTemp._terrainBlocks[id].edgeExists[west] = true;
                    } else {
                        var edge = {};
                        edge.sx = x * tw;
                        edge.sy = y * th;
                        edge.ex = edge.sx;
                        edge.ey = edge.sy + th;
                        $gameTemp._terrainBlocks[id].edgeIds[west] = this._terrainEdges.length;
                        this._terrainEdges.push(edge);
                        $gameTemp._terrainBlocks[id].edgeExists[west] = true;
                    }
                }
                if (!this.isValid(e[0], e[1]) || !$gameTemp._terrainBlocks[ei]) {
                    if (this.isValid(n[0], n[1]) && $gameTemp._terrainBlocks[ni] && $gameTemp._terrainBlocks[ni].edgeExists[east]) {
                        this._terrainEdges[$gameTemp._terrainBlocks[ni].edgeIds[east]].ey += th;
                        $gameTemp._terrainBlocks[id].edgeIds[east] = $gameTemp._terrainBlocks[ni].edgeIds[east];
                        $gameTemp._terrainBlocks[id].edgeExists[east] = true;
                    } else {
                        var edge = {};
                        edge.sx = (x + 1) * tw;
                        edge.sy = y * th;
                        edge.ex = edge.sx;
                        edge.ey = edge.sy + th;
                        $gameTemp._terrainBlocks[id].edgeIds[east] = this._terrainEdges.length;
                        this._terrainEdges.push(edge);
                        $gameTemp._terrainBlocks[id].edgeExists[east] = true;
                    }
                }
                if (!this.isValid(n[0], n[1]) || !$gameTemp._terrainBlocks[ni]) {
                    if (this.isValid(w[0], w[1]) && $gameTemp._terrainBlocks[wi] && $gameTemp._terrainBlocks[wi].edgeExists[north]) {
                        this._terrainEdges[$gameTemp._terrainBlocks[wi].edgeIds[north]].ex += tw;
                        $gameTemp._terrainBlocks[id].edgeIds[north] = $gameTemp._terrainBlocks[wi].edgeIds[north];
                        $gameTemp._terrainBlocks[id].edgeExists[north] = true;
                    } else {
                        var edge = {};
                        edge.sx = x * tw;
                        edge.sy = y * th;
                        edge.ex = edge.sx + tw;
                        edge.ey = edge.sy;
                        $gameTemp._terrainBlocks[id].edgeIds[north] = this._terrainEdges.length;
                        this._terrainEdges.push(edge);
                        $gameTemp._terrainBlocks[id].edgeExists[north] = true;
                    }
                }
                if (!this.isValid(s[0], s[1]) || !$gameTemp._terrainBlocks[si]) {
                    if (this.isValid(w[0], w[1]) && $gameTemp._terrainBlocks[wi] && $gameTemp._terrainBlocks[wi].edgeExists[south]) {
                        this._terrainEdges[$gameTemp._terrainBlocks[wi].edgeIds[south]].ex += tw;
                        $gameTemp._terrainBlocks[id].edgeIds[south] = $gameTemp._terrainBlocks[wi].edgeIds[south];
                        $gameTemp._terrainBlocks[id].edgeExists[south] = true;
                    } else {
                        var edge = {};
                        edge.sx = x * tw;
                        edge.sy = (y + 1) * th;
                        edge.ex = edge.sx + tw;
                        edge.ey = edge.sy;
                        $gameTemp._terrainBlocks[id].edgeIds[south] = this._terrainEdges.length;
                        this._terrainEdges.push(edge);
                        $gameTemp._terrainBlocks[id].edgeExists[south] = true;
                    }
                }
            }
        }
    }
};

Game_Map.prototype.calculateVisibilityPolygon = function (x, y, radius, rectArea) {
    if (!$gameTemp._terrainBlocks) return null;
    var result = [];
    var edges = this._terrainEdges.clone();
    const TW = this.tileWidth();
    const TH = this.tileHeight();
    const NORTH = 0;
    const SOUTH = 1;
    const EAST = 2;
    const WEST = 3;
    if (rectArea) {
        var edgeContainer = [];
        var rx = rectArea[0];
        var ry = rectArea[1];
        var rex = rx + rectArea[2];
        var rey = ry + rectArea[3];
        var sx = Math.floor(rx / TW);
        var sy = Math.floor(ry / TH);
        var ex = Math.ceil(rex / TW);
        var ey = Math.ceil(rey / TH);

        // NORTH-SOUTH
        // NORTH
        var edg = { cleared: true };
        var id = sy * this.width() + sx;
        var id2 = sy > 0 ? (sy - 1) * this.width() + sx : -1;
        if ((!$gameTemp._terrainBlocks[id] || !$gameTemp._terrainBlocks[id].edgeExists[NORTH]) && (!$gameTemp._terrainBlocks[id2] || !$gameTemp._terrainBlocks[id2].edgeExists[SOUTH])) {
            edg = { sx: rx, sy: ry, ex: rx + TW, ey: ry, edge: true };
            if ($gameTemp._terrainBlocks[id] && $gameTemp._terrainBlocks[id].edgeExists[EAST]) {
                edgeContainer.push(edg);
                edg = { cleared: true };
            }
        }
        // SOUTH
        var edgB = { cleared: true };
        var id = ey * this.width() + sx;
        var id2 = ey > 0 ? (ey - 1) * this.width() + sx : -1;
        if ((!$gameTemp._terrainBlocks[id] || !$gameTemp._terrainBlocks[id].edgeExists[NORTH]) && (!$gameTemp._terrainBlocks[id2] || !$gameTemp._terrainBlocks[id2].edgeExists[SOUTH])) {
            edgB = { sx: rx, sy: rey, ex: rx + TW, ey: rey, edge: true };
            if ($gameTemp._terrainBlocks[id] && $gameTemp._terrainBlocks[id].edgeExists[EAST]) {
                edgeContainer.push(edgB);
                edgB = { cleared: true };
            }
        }
        var tempEdge;
        for (var a = sx + 1; a < ex; a++) {
            for (var j = 0; j < 2; j++) {
                fx = a * TW;
                fex = a * TW + TW;
                if (j === 0) {
                    id = sy * this.width() + a;
                    id2 = sy > 0 ? (sy - 1) * this.width() + a : -1;
                    fy = ry;
                    fey = ry;
                    tempEdge = edg;
                } else {
                    id = ey * this.width() + a;
                    id2 = (ey - 1) * this.width() + a;
                    fy = rey;
                    fey = rey;
                    tempEdge = edgB;
                }
                var found = false;
                if ($gameTemp._terrainBlocks[id] && ($gameTemp._terrainBlocks[id].edgeExists[NORTH] || $gameTemp._terrainBlocks[id].edgeExists[WEST] || $gameTemp._terrainBlocks[id].edgeExists[EAST])) {
                    if ($gameTemp._terrainBlocks[id].edgeExists[NORTH]) {
                        if (!tempEdge.cleared) {
                            edgeContainer.push(tempEdge);
                            tempEdge.cleared = true;
                        }
                    } else if ($gameTemp._terrainBlocks[id].edgeExists[WEST]) {
                        if (!tempEdge.cleared) edgeContainer.push(tempEdge);
                        if (j === 0) {
                            edg = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edg;
                        } else {
                            edgB = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edgB;
                        }
                    } else if (tempEdge.cleared) {
                        if (j === 0) {
                            edg = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edg;
                        } else {
                            edgB = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edgB;
                        }
                    }
                    if (!tempEdge.cleared && $gameTemp._terrainBlocks[id].edgeExists[EAST]) {
                        edgeContainer.push(tempEdge);
                        tempEdge.cleared = true;
                    }
                    found = true;
                } else if ($gameTemp._terrainBlocks[id2] && $gameTemp._terrainBlocks[id2].edgeExists[SOUTH]) {
                    if (!tempEdge.cleared) {
                        edgeContainer.push(tempEdge);
                        tempEdge.cleared = true;
                    }
                    found = true;
                }
                if (!found) {
                    if (!tempEdge.cleared) {
                        tempEdge.ex += TW;
                    } else {
                        if (j === 0) {
                            edg = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edg;
                        } else {
                            edgB = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edgB;
                        }
                    }
                }
            }
        }
        if (!edg.cleared) {
            edg.ex = rex;
            edgeContainer.push(edg);
        }
        if (!edgB.cleared) {
            edgB.ex = rex;
            edgeContainer.push(edgB);
        }

        // WEST-EAST
        // WEST
        var edg = { cleared: true };
        var id = sy * this.width() + sx;
        var id2 = sx > 0 ? sy * this.width() + (sx - 1) : -1;
        if ((!$gameTemp._terrainBlocks[id] || !$gameTemp._terrainBlocks[id].edgeExists[WEST]) && (!$gameTemp._terrainBlocks[id2] || !$gameTemp._terrainBlocks[id2].edgeExists[EAST])) {
            edg = { sx: rx, sy: ry, ex: rx, ey: ry + TH, edge: true };
            if ($gameTemp._terrainBlocks[id] && $gameTemp._terrainBlocks[id].edgeExists[SOUTH]) {
                edgeContainer.push(edg);
                edg = { cleared: true };
            }
        }
        // EAST
        var edgB = { cleared: true };
        var id = sy * this.width() + ex;
        var id2 = ex > 0 ? sy * this.width() + (ex - 1) : -1;
        if ((!$gameTemp._terrainBlocks[id] || !$gameTemp._terrainBlocks[id].edgeExists[WEST]) && (!$gameTemp._terrainBlocks[id2] || !$gameTemp._terrainBlocks[id2].edgeExists[EAST])) {
            edgB = { sx: rex, sy: ry, ex: rex, ey: ry + TH, edge: true };
            if ($gameTemp._terrainBlocks[id] && $gameTemp._terrainBlocks[id].edgeExists[SOUTH]) {
                edgeContainer.push(edgB);
                edgB = { cleared: true };
            }
        }
        var tempEdge;
        for (var a = sy + 1; a < ey; a++) {
            for (var j = 0; j < 2; j++) {
                fy = a * TH;
                fey = a * TH + TH;
                if (j === 0) {
                    id = a * this.width() + sx;
                    id2 = sx > 0 ? a * this.width() + (sx - 1) : -1;
                    fx = rx;
                    fex = rx;
                    tempEdge = edg;
                } else {
                    id = a * this.width() + ex;
                    id2 = ex > 0 ? a * this.width() + (ex - 1) : -1;
                    fx = rex;
                    fex = rex;
                    tempEdge = edgB;
                }
                var found = false;
                if ($gameTemp._terrainBlocks[id] && ($gameTemp._terrainBlocks[id].edgeExists[WEST] || $gameTemp._terrainBlocks[id].edgeExists[SOUTH] || $gameTemp._terrainBlocks[id].edgeExists[NORTH])) {
                    if ($gameTemp._terrainBlocks[id].edgeExists[WEST]) {
                        if (!tempEdge.cleared) {
                            edgeContainer.push(tempEdge);
                            tempEdge.cleared = true;
                        }
                    } else if ($gameTemp._terrainBlocks[id].edgeExists[NORTH]) {
                        if (!tempEdge.cleared) edgeContainer.push(tempEdge);
                        if (j === 0) {
                            edg = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edg;
                        } else {
                            edgB = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edgB;
                        }
                    } else if (tempEdge.cleared) {
                        if (j === 0) {
                            edg = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edg;
                        } else {
                            edgB = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edgB;
                        }
                    }
                    if (!tempEdge.cleared && $gameTemp._terrainBlocks[id].edgeExists[SOUTH]) {
                        edgeContainer.push(tempEdge);
                        tempEdge.cleared = true;
                    }
                    found = true;
                } else if ($gameTemp._terrainBlocks[id2] && $gameTemp._terrainBlocks[id2].edgeExists[EAST]) {
                    if (!tempEdge.cleared) {
                        edgeContainer.push(tempEdge);
                        tempEdge.cleared = true;
                    }
                    found = true;
                }
                if (!found) {
                    if (!tempEdge.cleared) {
                        tempEdge.ey += TH;
                    } else {
                        if (j === 0) {
                            edg = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edg;
                        } else {
                            edgB = { sx: fx, sy: fy, ex: fex, ey: fey, edge: true };
                            tempEdge = edgB;
                        }
                    }
                }
            }
        }
        if (!edg.cleared) {
            edg.ey = rey;
            edgeContainer.push(edg);
        }
        if (!edgB.cleared) {
            edgB.ey = rey;
            edgeContainer.push(edgB);
        }
    }

    // BLOCK EVENTS
    var events = [];
    this._blockLightEvents.forEach(function (eventId) {
        var event = this.event(eventId);
        if (event.isNearTheScreen()) {
            var ex = event._realX * TW;
            var ey = event._realY * TH;
            var points = event.getLightBlock();
            if (points.length > 1) {
                for (var i = 0; i < points.length - 1; i++) {
                    edges.push({ sx: ex + points[i][0], sy: ey + points[i][1], ex: ex + points[i + 1][0], ey: ey + points[i + 1][1], id: eventId });
                }
                edges.push({ sx: ex + points[0][0], sy: ey + points[0][1], ex: ex + points[points.length - 1][0], ey: ey + points[points.length - 1][1], id: eventId });
            }
        }
    }, this);

    if (rectArea) {
        // REMOVE EDGES OUTSIDE RECT AREA
        edges = edges.filter(function (edge) {
            if (!edge) return false;
            var gx = this.displayX() * TW;
            var gy = this.displayY() * TH;
            gx = 0;
            gy = 0;
            if (edge.sx - gx <= rectArea[0] + rectArea[2] + TW && edge.ex - gx >= rectArea[0] - TW &&
                edge.sy - gy <= rectArea[1] + rectArea[3] + TH && edge.ey - gy >= rectArea[1] - TH) {
                if (edge.id && !events.contains(edge.id)) events.push(edge.id);
                return true;
            }
        }, this);
    }
    edges = edges.concat(edgeContainer);
    this._terrainEdgesClone = edges;
    // RAYCASTING
    edges.forEach(function (edge) {
        if (edge) {
            for (var i = 0; i < 2; i++) {
                var rdx = (i === 0 ? edge.sx : edge.ex) - x;
                var rdy = (i === 0 ? edge.sy : edge.ey) - y;
                var baseAngle = Math.atan2(rdy, rdx);
                var angle = 0;
                for (var j = 0; j < 3; j++) {
                    if (j === 0) angle = baseAngle - 0.0001;
                    if (j === 1) angle = baseAngle;
                    if (j === 2) angle = baseAngle + 0.0001;
                    rdx = radius * Math.cos(angle);
                    rdy = radius * Math.sin(angle);
                    var minT1 = Infinity;
                    var minPx = 0;
                    var minPy = 0;
                    var minAngle = 0;
                    var valid = false;
                    edges.forEach(function (edge2) {
                        if (edge2) {
                            var sdx = edge2.ex - edge2.sx;
                            var sdy = edge2.ey - edge2.sy;
                            if (Math.abs(sdx - rdx) > 0 && Math.abs(sdy - rdy) > 0) {
                                var t2 = (rdx * (edge2.sy - y) + (rdy * (x - edge2.sx))) / (sdx * rdy - sdy * rdx);
                                var t1 = (edge2.sx + sdx * t2 - x) / rdx;
                                if (t1 > 0 && t2 >= 0 && t2 <= 1) {
                                    if (t1 < minT1) {
                                        minT1 = t1;
                                        minPx = x + rdx * t1;
                                        minPy = y + rdy * t1;
                                        minAngle = Math.atan2(minPy - y, minPx - x);
                                        valid = true;
                                    }
                                }
                            }
                        }
                    }, this);
                    if (valid) {
                        result.push([minAngle, minPx, minPy]);
                    }
                }
            }
        }
    }, this);
    result.sort(function (a, b) {
        return a[0] - b[0];
    });
    var temp = [];
    // REMOVE DUPLICATE EDGES
    for (var i = 0; i < result.length; i++) {
        var dup = false;
        for (var j = 0; j < temp.length; j++) {
            if (Math.abs(result[i][1] - temp[j][1]) < 0.1 && Math.abs(result[i][2] - temp[j][2]) < 0.1) {
                dup = true;
                break;
            }
        }
        if (!dup) temp.push(result[i]);
    }
    return [temp, events];
};

Game_Map.prototype.lightingObjects = function () {
    return this.events().concat($gamePlayer);
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Game_Interpreter
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function (command, args) {
    Dhoom.ParticleLightingSystem.Game_Interpreter_pluginCommand.call(this, command, args);
    switch (command.toLowerCase()) {
        case 'shader':
            switch (args[0].toLowerCase()) {
                case 'col':
                    $gameMap.lightingShaderUniforms().u_col.value = Number(args[1]);
                    break;
                case 'contrast':
                    $gameMap.lightingShaderUniforms().u_contrast.value = Number(args[1]);
                    break;
                case 'saturation':
                    $gameMap.lightingShaderUniforms().u_saturation.value = Number(args[1]);
                    break;
                case 'brightness':
                    $gameMap.lightingShaderUniforms().u_brightness.value = Number(args[1]);
                    break;
                case 'pop_strength':
                    $gameMap.lightingShaderUniforms().u_pop_strength.value = Number(args[1]);
                    break;
                case 'pop_threshold':
                    $gameMap.lightingShaderUniforms().u_pop_threshold.value = Number(args[1]);
                    break;
                case 'ambient':
                    $gameMap.lightingShaderUniforms().u_ambient.value = Number(args[1]);
                    break;
                case 'ambient_wall':
                    $gameMap.lightingShaderUniforms().u_ambient_wall.value = Number(args[1]);
                    break;
                case 'color':
                    var colors = Dhoom.Utils.hexToRGB(args[1]);
                    $gameMap.lightingShaderUniforms().u_ambientColor.value[0] = colors[0];
                    $gameMap.lightingShaderUniforms().u_ambientColor.value[1] = colors[1];
                    $gameMap.lightingShaderUniforms().u_ambientColor.value[2] = colors[2];
                    break;
            }
            break;
        case 'particleemitter':
            var arg = args.join(' ');
            if (arg.match(/(-?\d+)\s(.+)\s(-?\d+)\s(-?\d+)\s(below|same|above|abovepar)/i)) {
                var target = this.character(Number(RegExp.$1));
                var name = RegExp.$2;
                var x = Number(RegExp.$3);
                var y = Number(RegExp.$4);
                var layer = RegExp.$5;
                if (target) {
                    var id = target === $gamePlayer ? 0 : target.eventId();
                    $gameMap.addParticleEmitter(id, target, name, x, y, layer);
                }
            }
            break;
        case 'tint':
            if (args[0].toLowerCase() === 'set' || (args[0].toLowerCase() === 'fade' && args[2] && Number(args[2]) === 0)) {
                var rgb = Dhoom.Utils.hexToRGB(args[1]).map(function (i) { return i / 255; });
                $gameMap.lightingShaderUniforms().u_ambientColor.value = rgb;
                $gameMap.lightingShaderUniforms().u_ambientColor.target = undefined;
                $gameMap.lightingShaderUniforms().u_ambient.value = args[2] ? Number(args[2]) / 255 : 0.75;
                $gameMap.lightingShaderUniforms().u_ambient.target = undefined;
                if (args[3]) {
                    $gameMap.lightingShaderUniforms().u_ambient_wall.value = Number(args[3]);
                    $gameMap.lightingShaderUniforms().u_ambient_wall.target = undefined;
                }
                $gameMap.lightingShaderUniforms().ambientDuration = 0;
            } else if (args[0].toLowerCase() === 'fade') {
                if ($gameMap.lightingShaderUniforms().enabled) {
                    var rgb = Dhoom.Utils.hexToRGB(args[1]).map(function (i) { return i / 255; });
                    $gameMap.lightingShaderUniforms().u_ambientColor.target = rgb;
                    $gameMap.lightingShaderUniforms().ambientDuration = args[2] ? Number(args[2]) : 60;
                    if (args[3]) $gameMap.lightingShaderUniforms().u_ambient.target = Number(args[3]);
                    if (args[4]) $gameMap.lightingShaderUniforms().u_ambient_wall.target = Number(args[4]);
                } else {
                    var rgb = Dhoom.Utils.hexToRGB(args[1]).map(function (i) { return i / 255; });
                    $gameMap.lightingShaderUniforms().u_ambientColor.value = rgb;
                    $gameMap.lightingShaderUniforms().u_ambientColor.target = undefined;
                    $gameMap.lightingShaderUniforms().u_ambient.value = 0;
                    $gameMap.lightingShaderUniforms().ambientDuration = args[2] ? Number(args[2]) : 60;
                    $gameMap.lightingShaderUniforms().u_ambient.target = args[3] ? Number(args[3]) : 0.75;
                }
            }
            $gameMap.lightingShaderUniforms().enabled = true;
            break;
        case 'regionlight':
            var regionId = Number(args[0]);
            var add = args[1].toLowerCase() === 'on';
            var color = args[2];
            var radius = Number(args[3]);
            if (add) {
                $gameMap.addRegionLighting(regionId, color, radius);
            } else {
                $gameMap.removeRegionLighting(regionId);
            }
            break;
        case 'light':
            switch (args[0].toLowerCase()) {
                case 'on':
                case 'off':
                    var id = Number(args[1]);
                    var value = args[0].toLowerCase() === 'on';
                    $gameSystem._lightEnabledIds[id] = value;
                    break;
                default:
                    var light = $gamePlayer._lightings[0];
                    var radius = light._radius > 1 ? light._radius : 0;
                    var target = light.target > 1 ? light.target : 0;
                    var color = light._color;
                    var brightness = light._brightness;
                    var duration = light.targetDuration;
                    switch (args[0].toLowerCase()) {
                        case 'radius':
                            radius = Number(args[1]);
                            if (radius <= 1) radius = 0;
                            target = radius;
                            color = Dhoom.Utils.hexToRGB(args[2]);
                            if (args[3]) brightness = Number(args[3].substring(1)) / 100;
                            break;
                        case 'radiusgrow':
                            target = Number(args[1]);
                            if (radius <= 1) radius = 0;
                            radius = light.radius || 0;
                            color = Dhoom.Utils.hexToRGB(args[2]);
                            if (args[3]) brightness = Number(args[3].substring(1)) / 100;
                            if (args[4]) duration = Number(args[4]);
                            break;
                    }
                    light.prevRadius = radius;
                    light._radius = radius;
                    light.target = target;
                    light._color = color;
                    light._brightness = brightness;
                    light.targetDuration = duration;
            }
            break;
        case 'flashlight':
            var value = args[0] === 'on';
            $gamePlayer._flashlightActive = value;
            if (value) {
                if (args[1]) $gamePlayer._flashlightLength = Number(args[1]);
                if (args[2]) $gamePlayer._flashlightWidth = Number(args[2]);
                if (args[3]) $gamePlayer._flashlightColor = Dhoom.Utils.hexToRGB(args[3]);
                if (args[4]) $gamePlayer._flashlightDensity = Number(args[4]);
            }
            break;
        case 'tilelight':
            break;
        case 'regionlight':
            break;
        case 'tileblock':
            break;
        case 'regionblock':
            break;
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Sprite_Particle
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Sprite_Particle() {
    this.initialize.apply(this, arguments);
}

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

Sprite_Particle.prototype.initialize = function (index, emitter) {
    Sprite.prototype.initialize.call(this);
    this._index = index;
    this._emitter = emitter;
    this.anchor.x = 0.5;
    this.anchor.y = 0.5;
    this.updateParticle();
};

Sprite_Particle.prototype.setup = function (particle) {
    if (this._particle !== particle) {
        this._particle = particle;
        if (particle) {
            this.blendMode = ['Normal', 'Additive', 'Multiply', 'Screen'].indexOf(particle._blendMode);
            this.refreshBitmap();
            this.update();
        } else {
            this.clear();
        }
    }
};

Sprite_Particle.prototype.clear = function () {
    this._particle = undefined;
    this.bitmap = undefined;
    this.rotation = 0;
    this.tint = 0xffffff;
    this.opacity = 0;
    this._originX = undefined;
    this._originY = undefined;
};

Sprite_Particle.prototype.refreshBitmap = function () {
    this.bitmap = ImageManager.loadBitmap('img/particles/', this._particle._imageFilename);
};

Sprite_Particle.prototype.update = function () {
    Sprite.prototype.update.call(this);
    this.updateParticle();
    this.updateParams();
};

Sprite_Particle.prototype.updateParticle = function () {
    this.setup(this._emitter._particles[this._index]);
    if (this._particle && this._particle.isExpired()) this.clear();
};

Sprite_Particle.prototype.updateParams = function () {
    if (this._particle) {
        this.x = this._particle.screenX();
        this.y = this._particle.screenY();
        this.z = this._particle._z;
        this._originX = this._particle._originX;
        this._originY = this._particle._originY;
        this.opacity = this._particle._opacity;
        this.scale.x = this._particle._scale / 100;
        this.scale.y = this._particle._scale / 100;
        this.tint = Dhoom.Utils.rgbToHex(this._particle._color);
        if (!this._particle._noRotation) this.rotation = this._particle._rotation;
    }
};

Sprite_Particle.prototype.isLighting = function () {
    return this._particle && this._particle.isLighting();
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Sprite_ParticleContainer
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Sprite_ParticleContainer() {
    this.initialize.apply(this, arguments);
}

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

Sprite_ParticleContainer.prototype.initialize = function (x, y, z) {
    Sprite.prototype.initialize.call(this);
    this._ox = x;
    this._oy = y;
    this.z = z;
};

Sprite_ParticleContainer.prototype.update = function () {
    Sprite.prototype.update.call(this);
    this.x = this._ox - $gameMap.displayX() * $gameMap.tileWidth();
    this.y = this._oy - $gameMap.displayY() * $gameMap.tileHeight();
};

Sprite_ParticleContainer.prototype.tileY = function () {
    return Math.floor(this._oy / $gameMap.tileHeight());
};

Sprite_ParticleContainer.prototype.screenZ = function () {
    return this.z;
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Sprite_Lighting
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Sprite_Lighting() {
    this.initialize.apply(this, arguments);
}

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

Sprite_Lighting.prototype.initialize = function (light, character) {
    Sprite.prototype.initialize.call(this);
    this._light = light;
    this._character = character;
    this.blendMode = 4;
    this._tempEventPositions = {};
    this.initBitmap();
};

Sprite_Lighting.prototype.initBitmap = function () {
    this._sourceBitmap = ImageManager.loadBitmap('img/lights/', this._light.texture(), 0, true);
    this._bitmapInitialized = false;
};

Sprite_Lighting.prototype.update = function () {
    Sprite.prototype.update.call(this);
    this.updateBitmap();
    this.updateVisibility();
    if (this.visible) {
        this.updatePosition();
        this.updateScale();
        this.updateColor();
        this.updateRotation();
        this.castShadow();
    }
};

Sprite_Lighting.prototype.updateBitmap = function () {
    if (!this._bitmapInitialized && this._sourceBitmap.isReady()) {
        this._bitmapInitialized = true;
        var bw = this._sourceBitmap.width;
        var bh = this._sourceBitmap.height;
        var rate = Math.max(bw, bh);
        // if (rate === this._light._radius) {
        //     this.bitmap = this._sourceBitmap;
        // } else {
        //     var pw = bw / rate * this._light._radius;
        //     var ph = bh / rate * this._light._radius;
        //     this.bitmap = new Bitmap(pw, ph);
        //     this.bitmap.blt(this._sourceBitmap, 0, 0, bw, bh, 0, 0, pw, ph);
        // }
        this.bitmap = this._sourceBitmap;
        this._sourceBitmap = undefined;
    }
};

Sprite_Lighting.prototype.updateVisibility = function () {
    this.visible = this._light.isEnabled();
};

Sprite_Lighting.prototype.updatePosition = function () {
    this.anchor.x = this._light._ox;
    this.anchor.y = this._light._oy;
    if (this._character) {
        this.x = this._character.screenX() + this._light.x();
        this.y = this._character.screenY() + this._character.shiftY() - $gameMap.tileHeight() / 2 + this._light.y();
    } else {
        this.x = $gameMap.adjustX(this._light.x()) * $gameMap.tileWidth();
        this.y = $gameMap.adjustY(this._light.y()) * $gameMap.tileHeight();
    }
    // this.x += SceneManager._scene._spriteset.x;
    // this.y += SceneManager._scene._spriteset.y;
    // this.scale.x = SceneManager._scene._spriteset.scale.x;
    // this.scale.y = SceneManager._scene._spriteset.scale.y;
    if (this.bitmap && this._light._direction) {
        switch (this._light._direction) {
            case 1:
                var bh = Math.floor(this.bitmap.height / 2);
                this.setFrame(0, bh, this.bitmap.width, bh);
                this.y += $gameMap.tileHeight() / 2;
                break;
            case 2:
                var bw = Math.floor(this.bitmap.width / 2);
                this.setFrame(0, 0, bw, this.bitmap.height);
                this.x -= $gameMap.tileWidth() / 2;
                break;
            case 3:
                var bh = Math.floor(this.bitmap.height / 2);
                this.setFrame(0, 0, this.bitmap.width, bh);
                this.y -= $gameMap.tileHeight() / 2;
                break;
            case 4:
                var bw = Math.floor(this.bitmap.width / 2);
                this.setFrame(bw, 0, bw, this.bitmap.height);
                this.x += $gameMap.tileWidth() / 2;
                break;
        }
    }
};

Sprite_Lighting.prototype.updateScale = function () {
    if (this.bitmap && this.bitmap.isReady() && (this._tempRadius !== this._light._radius || this._tempSX !== this._light.scaleX() || this._tempSY !== this._light.scaleY())) {
        this._tempSX = this._light.scaleX();
        this._tempSY = this._light.scaleY();
        this._tempRadius = this._light._radius;
        var r = Math.max(this.bitmap.width, this.bitmap.height);
        this.scale.x = (this._light._radius * this._light.scaleX() * (this.bitmap.width / r)) / this.bitmap.width;
        this.scale.y = (this._light._radius * this._light.scaleY() * (this.bitmap.height / r)) / this.bitmap.height;
    }
};

Sprite_Lighting.prototype.updateColor = function () {
    if (this.bitmap) {
        this.tint = Dhoom.Utils.rgbToHex(this._light.color());
        this.opacity = this._light.opacity();
    }
};

Sprite_Lighting.prototype.updateRotation = function () {
    this.rotation = this._light.rotation();
};

Sprite_Lighting.prototype.isDeleted = function () {
    return this._character && !this._character._lightings.contains(this._light);
};

Sprite_Lighting.prototype.castShadow = function () {
    if (!this._light._castShadow || !this.opacity || !this._light._radius) return;
    if (!this.bitmap || !this.bitmap.isReady()) return;
    this.mask = undefined;
    if (!this._shadowMask) {
        this._shadowMask = new PIXI.Graphics();
    }
    if (!this._shadowMask.parent) {
        this.parent.addChild(this._shadowMask);
    }
    var tw = this.width * this.scale.x;
    var th = this.height * this.scale.y;
    var tx = this.x - this.anchor.x * tw;
    var ty = this.y - this.anchor.y * th;
    var pw = this.x - tx;
    var ph = this.y - ty;
    var sx = tx;
    var sy = ty;
    var ex = tx + tw;
    var ey = ty + th;
    switch (this._light._rotation) {
        case 0:
        case 360:
            break;
        case 90:
            ex = this.x + ph;
            sy = this.y - pw;
            sx = ex - th;
            ey = sy + tw;
            break;
        case 180:
            ey = this.y + ph;
            sy = ey - th;
            break;
        case 270:
            sx = this.x - ph;
            ey = this.y + pw;
            ex = sx + th;
            sy = ey - tw;
            break;
    }
    var rect = [sx, sy, ex - sx, ey - sy];
    rect[0] = Math.floor(rect[0] / $gameMap.tileWidth() + $gameMap.displayX()) * $gameMap.tileWidth();
    rect[1] = Math.floor(rect[1] / $gameMap.tileHeight() + $gameMap.displayY()) * $gameMap.tileHeight();
    rect[2] = Math.ceil(rect[2] / $gameMap.tileWidth()) * $gameMap.tileWidth();
    rect[3] = Math.ceil(rect[3] / $gameMap.tileHeight()) * $gameMap.tileHeight();
    $gameTemp._castRectArea = rect;
    this.createLightBlockPolys(rect);
    this.mask = this._shadowMask;
};

Sprite_Lighting.prototype.createLightBlockPolys = function (rectArea) {
    var character = this._character;
    var graphics = this._shadowMask;
    var px = character.screenX();
    var py = character.screenY() - $gameMap.tileHeight() / 2;
    // px += SceneManager._scene._spriteset.x;
    // py += SceneManager._scene._spriteset.y;
    var dx = $gameMap.displayX() * $gameMap.tileWidth();
    var dy = $gameMap.displayY() * $gameMap.tileHeight();
    if (this.shadowNeedUpdate(rectArea)) {
        this._tempDisplayX = $gameMap.displayX();
        this._tempDisplayY = $gameMap.displayY();
        this._tempX = this._character._realX;
        this._tempY = this._character._realY;
        this._tempDirection = this._character._direction;
        var vis = $gameMap.calculateVisibilityPolygon(px + dx, py + dy, Dhoom._polyRadius || 1000, rectArea);
        var edges = vis[0];
        this._blockEvents = vis[1].sort();
        this._blockEvents.forEach(function (eventId) {
            var event = $gameMap.event(eventId);
            this._tempEventPositions[eventId] = [event._realX, event._realY];
        }, this);
        if (edges) {
            graphics.clear();
            graphics.lineStyle(2);
            if (edges.length > 1) {
                for (var i = 0; i < edges.length - 1; i++) {
                    graphics.beginFill(0x000000, 1);
                    graphics.moveTo(px, py);
                    graphics.lineTo(Math.round(edges[i][1]) - dx, Math.round(edges[i][2]) - dy);
                    graphics.lineTo(Math.round(edges[i + 1][1]) - dx, Math.round(edges[i + 1][2]) - dy);
                    graphics.endFill();
                }
                graphics.beginFill(0x000000, 1);
                graphics.moveTo(px, py);
                graphics.lineTo(edges[edges.length - 1][1] - dx, edges[edges.length - 1][2] - dy);
                graphics.lineTo(edges[0][1] - dx, edges[0][2] - dy);
                graphics.endFill();
            }
        }
    }
};

Sprite_Lighting.prototype.shadowNeedUpdate = function (area) {
    if (!this._character.isNearTheScreen()) return false;
    if (!this._blockEvents) return true;
    if (this._tempDisplayX !== $gameMap.displayX() || this._tempDisplayY !== $gameMap.displayY()) return true;
    if (this._tempX !== this._character._realX || this._tempY !== this._character._realY || this._tempDirection !== this._character._direction) return true;
    if (this._blockEvents.some(function (eventId) {
        var event = $gameMap.event(eventId);
        return !this._tempEventPositions[eventId] || this._tempEventPositions[eventId][0] !== event._realX || this._tempEventPositions[eventId][1] !== event._realY;
    }, this)) return true;
    var tw = $gameMap.tileWidth();
    var th = $gameMap.tileHeight();
    var events = $gameMap._blockLightEvents.filter(function (eventId) {
        var event = $gameMap.event(eventId);
        if (event === this) return false;
        if (!event.isNearTheScreen()) return false;
        var x = event._realX * tw;
        var y = event._realY * th;
        var points = event.getLightBlock();
        if (points.length > 1) {
            var gx = $gameMap.displayX() * tw;
            var gy = $gameMap.displayY() * th;
            for (var i = 0; i < points.length - 1; i++) {
                if (x + points[i][0] - gx <= area[0] + area[2] + tw && x + points[i + 1][0] - gx >= area[0] - tw &&
                    y + points[i][1] - gy <= area[1] + area[3] + th && y + points[i + 1][1] - gy >= area[1] - th) {
                    return true;
                }
            }
            if (x + points[0][0] - gx <= area[0] + area[2] + tw && x + points[points.length - 1][0] - gx >= area[0] - tw &&
                y + points[0][1] - gy <= area[1] + area[3] + th && y + points[points.length - 1][1] - gy >= area[1] - th) {
                return true;
            }
        }
        return false;
    }, this).sort();
    return !events.equals(this._blockEvents);
    return false;
};

Dhoom.ParticleLightingSystem.Sprite_Character_update = Sprite_Character.prototype.update;
Sprite_Character.prototype.update = function () {
    Dhoom.ParticleLightingSystem.Sprite_Character_update.call(this);
    // this.updateBlockSprite();
};

Sprite_Character.prototype.updateBlockSprite = function () {
    if (this._character && this._character._blockLight) {
        if (!this._blockSprite) {
            var edges = this._character.getLightBlock();
            if (edges.length > 1) {
                this._blockSprite = new Sprite();
                var x = Infinity;
                var y = Infinity;
                var ex = 0;
                var ey = 0;
                for (var i = 0; i < edges.length; i++) {
                    x = Math.min(edges[i][0], x);
                    y = Math.min(edges[i][1], y);
                    ex = Math.max(edges[i][0], ex);
                    ey = Math.max(edges[i][1], ey);
                }
                this._blockSprite.bitmap = new Bitmap(ex - x, ey - y);
                this._blockSprite.bitmap.fillAll('#ff00ff');
                this._blockSprite.opacity = 128;
                this._blockSprite.x = x - this.anchor.x * this.width;
                this._blockSprite.y = y - this.anchor.y * this.height;
                this.addChild(this._blockSprite);
            }
        }
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Spriteset_Lightings
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Spriteset_Lightings() {
    this.initialize.apply(this, arguments);
}

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

Spriteset_Lightings.prototype.initialize = function () {
    Sprite.prototype.initialize.call(this);
    this._lightMap = new PIXI.RenderTexture.create(Graphics.width, Graphics.height);
    this._container = new PIXI.Container();
    this._lightingObjects = [];
    this._lightingSprites = [];
    this._blockSprite = new Sprite();
    this._lightParallaxes = [];
    this._blockParallaxes = [];
    this._lightParallaxSprites = [];
    this._blockParallaxSprites = [];
    if (Graphics._renderer.textureGC) Graphics._renderer.textureGC.maxIdle = 60;
    var ids = Object.keys($gameMap._lightingRegions);
    for (var i = 0; i < ids.length; i++) {
        $gameMap._lightingRegions[ids[i]].forEach(function (obj) {
            this.addLighting(obj);
        }, this);
    }
};

Spriteset_Lightings.prototype.update = function () {
    Sprite.prototype.update.call(this);
    this.updateLightings();
};

Spriteset_Lightings.prototype.updateLightings = function () {
    this._container.removeChildren();
    var characters = [].concat($gameMap.lightingObjects()).concat(this.getParticleContainers());
    characters.sort(function (a, b) {
        if (a.screenZ() !== b.screenZ()) {
            return a.screenZ() - b.screenZ();
        } else {
            var ay = a instanceof Sprite_ParticleContainer ? a.tileY() : a.y;
            var by = b instanceof Sprite_ParticleContainer ? b.tileY() : b.y;
            return ay - by;
        }
    });
    characters.forEach(function (char) {
        if (char._lightings) {
            if (char === $gamePlayer) {
                this.updatePlayerBlock();
                for (var i = 0; i < char._lightings.length; i++) {
                    this.addLighting(char._lightings[i], char);
                }
            } else {
                for (var j = 0; j < char._lightings.length; j++) {
                    this.addLighting(char._lightings[j], char);
                }
            }
        } else {
            char.update();
            this._container.addChild(char);
        }
    }, this);
    this.updateParallax();
    this.updateBlockSprite();
    for (var i = 0; i < this._lightingSprites.length; i++) {
        if (this._lightingSprites[i].isDeleted()) {
            this._container.removeChild(this._lightingSprites[i]);
            this._lightingSprites.splice(i, 1);
            this._lightingObjects.splice(i, 1);
            i--;
        } else {
            this._lightingSprites[i].update();
        }
    }
    var spriteset = SceneManager._scene._spriteset;
    this._container.scale.x = spriteset.scale.x;
    this._container.scale.y = spriteset.scale.y;
    this._container.x = spriteset.x;
    this._container.y = spriteset.y;
    Graphics._renderer.render(this._container, this._lightMap);
};

Spriteset_Lightings.prototype.addLighting = function (light, character) {
    if (light instanceof Game_Lighting) {
        if (!this._lightingObjects.contains(light)) {
            this._lightingObjects.push(light);
            var sprite = new Sprite_Lighting(light, character);
            this._lightingSprites.push(sprite);
            this._container.addChild(sprite);
        } else {
            var sprite = this._lightingSprites[this._lightingObjects.indexOf(light)];
            if (!this._container.children.contains(sprite)) this._container.addChild(sprite);
        }
    }
};

Spriteset_Lightings.prototype.updatePlayerBlock = function () {
    if (!this._playerBlockSprite) {
        this._playerBlockSprite = new Sprite();
    }
    var sprite = SceneManager._scene._spriteset.getCharacterSprite($gamePlayer);
    this._playerBlockSprite.visible = sprite && sprite.height > $gameMap.tileHeight() * 2;
    if (this._playerBlockSprite.visible) {
        this._playerBlockSprite.bitmap = sprite.bitmap;
        this._playerBlockSprite.setBlendColor([0, 0, 0, 255]);
        this._playerBlockSprite.x = sprite.x;
        this._playerBlockSprite.y = sprite.y;
        this._playerBlockSprite.setFrame(sprite._frame.x, sprite._frame.y, sprite._frame.width, sprite._frame.height);
        this._playerBlockSprite.anchor.x = sprite.anchor.x;
        this._playerBlockSprite.anchor.y = sprite.anchor.y;
        this._playerBlockSprite.opacity = sprite.opacity;
        this._playerBlockSprite.visible = sprite.visible;
        this._container.addChild(this._playerBlockSprite);
    }
};

Spriteset_Lightings.prototype.updateBlockSprite = function () {
    this._blockSprite.bitmap = $gameTemp._terrainBlockBitmap;
    this._blockSprite.x = -$gameMap.displayX() * $gameMap.tileWidth();
    this._blockSprite.y = -$gameMap.displayY() * $gameMap.tileHeight();
    this._blockSprite.alpha = $gameMap.lightingShaderUniforms().u_ambient_wall.value;
    this._container.addChild(this._blockSprite);
};

Spriteset_Lightings.prototype.getParticleContainers = function () {
    var result = [];
    var containers = SceneManager._scene._spriteset._particleContainers;
    var keys = Object.keys(containers);
    for (var i = 0; i < keys.length; i++) {
        if (keys[i].split(':')[3] === 'true') {
            result.push(containers[keys[i]]);
        }
    }
    return result;
};

Spriteset_Lightings.prototype.updateParallax = function () {
    var parallaxes = $gameMap._parallaxUtils;
    for (var i = 0; i < parallaxes.length; i++) {
        var par = parallaxes[i];
        if (Dhoom.ParticleLightingSystem.parallaxLights.contains(par._presetName) && par.isEnabled() && par.opacity()) {
            if (!this._lightParallaxes.contains(par)) {
                this._lightParallaxes.push(par);
                this._lightParallaxSprites.push(new Sprite());
            }
            var sprite = this._lightParallaxSprites[this._lightParallaxes.indexOf(par)];
            sprite.blendMode = 4;
            sprite.bitmap = ImageManager.loadParallax(par.filename());
            if (sprite.bitmap && sprite.bitmap.isReady()) {
                var x = par.getX();
                var y = par.getY();
                // x -= SceneManager._scene._spriteset.x;
                // y -= SceneManager._scene._spriteset.y;
                sprite.x = -x;
                sprite.y = -y;
                sprite.opacity = par.opacity();
            }
            this._container.addChild(sprite);
        }
    }
    for (var i = 0; i < parallaxes.length; i++) {
        var par = parallaxes[i];
        if (Dhoom.ParticleLightingSystem.parallaxBypassMasks.contains(par._presetName) && par.isEnabled() && par.opacity()) {
            if (!this._blockParallaxes.contains(par)) {
                this._blockParallaxes.push(par);
                this._blockParallaxSprites.push(new Sprite());
            }
            var sprite = this._blockParallaxSprites[this._blockParallaxes.indexOf(par)];
            sprite.blendMode = 2;
            sprite.bitmap = ImageManager.loadParallax(par.filename());
            sprite.setBlendColor([0, 0, 0, 255]);
            if (sprite.bitmap && sprite.bitmap.isReady()) {
                var x = par.getX();
                var y = par.getY();
                // x -= SceneManager._scene._spriteset.x;
                // y -= SceneManager._scene._spriteset.y;
                sprite.x = -x;
                sprite.y = -y;
                sprite.opacity = par.opacity();
            }
            this._container.addChild(sprite);
        }
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Spriteset_Map
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Spriteset_Map_initialize = Spriteset_Map.prototype.initialize;
Spriteset_Map.prototype.initialize = function () {
    this._particleContainers = {};
    this._particleSprites = {};
    Dhoom.ParticleLightingSystem.Spriteset_Map_initialize.call(this);
};

Dhoom.ParticleLightingSystem.Spriteset_Map_update = Spriteset_Map.prototype.update;
Spriteset_Map.prototype.update = function () {
    Dhoom.ParticleLightingSystem.Spriteset_Map_update.call(this);
    this.updateParticleSprites();
};

Spriteset_Map.prototype.updateParticleSprites = function () {
    this.updateParticleEmitterRemoval();
    if ($gameSystem.isParticleEnabled()) {
        var keys = Object.keys($gameMap._particleEmitters);
        for (var i = 0; i < keys.length; i++) {
            this._particleSprites[keys[i]] = this._particleSprites[keys[i]] || [];
            var sprites = this._particleSprites[keys[i]];
            var emitters = $gameMap._particleEmitters[keys[i]];
            for (var j = 0; j < emitters.length; j++) {
                var emitter = emitters[j];
                sprites[j] = sprites[j] || [];
                while (emitter._particles.length > sprites[j].length) {
                    var sprite = new Sprite_Particle(sprites[j].length, emitter);
                    sprites[j].push(sprite);
                }
                for (var n = 0; n < sprites[j].length; n++) {
                    var sprite = sprites[j][n];
                    if (!sprite.parent || sprite.parent.z !== sprite.z || sprite.parent._oy !== sprite._originY || sprite.parent._ox !== sprite._originX) {
                        if (sprite.parent) sprite.parent.removeChild(sprite);
                        if (sprite._originX !== undefined && sprite._originY !== undefined) {
                            var symbol = this.particleContainerSymbol(sprite);
                            var container = this._particleContainers[symbol];
                            if (!container) {
                                this._particleContainers[symbol] = new Sprite_ParticleContainer(sprite._originX, sprite._originY, sprite.z);
                                container = this._particleContainers[symbol];
                                if (!sprite.isLighting()) {
                                    if (sprite.z === 1000) {
                                        this._baseSprite.addChild(container);
                                    } else {
                                        this._tilemap.addChild(container);
                                    }
                                }
                            }
                            container.addChild(sprite);
                        } else if (!sprite.parent) {
                            sprite.update();
                        }
                    }
                }
            }
        }
    }
    this.removeNoChildParticleContainer();
};

Spriteset_Map.prototype.updateParticleEmitterRemoval = function () {
    var keys = Object.keys(this._particleSprites);
    for (var i = 0; i < keys.length; i++) {
        for (var j = 0; j < this._particleSprites[keys[i]].length; j++) {
            if (this._particleSprites[keys[i]][j] && this._particleSprites[keys[i]][j].length) {
                var emitter = this._particleSprites[keys[i]][j][0]._emitter;
                if (!$gameMap._particleEmitters[keys[i]] || !$gameMap._particleEmitters[keys[i]].contains(emitter)) {
                    emitter._active = false;
                    emitter.update();
                    if (emitter._particles.length === 0) {
                        this._particleSprites[keys[i]][j].forEach(function (sprite) {
                            if (sprite.parent) sprite.parent.removeChild(sprite);
                        }, this);
                        this._particleSprites[keys[i]].splice(j, 1);
                        j--;
                    }
                }
            }
        }
    }
};

Spriteset_Map.prototype.removeNoChildParticleContainer = function () {
    var keys = Object.keys(this._particleContainers);
    for (var i = 0; i < keys.length; i++) {
        if (this._particleContainers[keys[i]].children.length === 0) {
            this._particleContainers[keys[i]].parent.removeChild(this._particleContainers[keys[i]]);
            delete this._particleContainers[keys[i]];
        }
    }
};

Spriteset_Map.prototype.getCharacterSprite = function (character) {
    for (var i = 0; i < this._characterSprites.length; i++) {
        if (this._characterSprites[i]._character === character) return this._characterSprites[i];
    }
};

Spriteset_Map.prototype.particleContainerSymbol = function (sprite) {
    return sprite._originX + ':' + sprite._originY + ':' + sprite.z + ':' + sprite.isLighting();
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Sprite_Battler
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Window_LightingShader
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
function Window_LightingShader() {
    this.initialize.apply(this, arguments);
}

Window_LightingShader.prototype = Object.create(Window_Command.prototype);
Window_LightingShader.prototype.constructor = Window_LightingShader;

Window_LightingShader.prototype.initialize = function () {
    this._tempValues = {};
    Window_Command.prototype.initialize.call(this, 16, 16);
};

Window_LightingShader.prototype.windowWidth = function () {
    return 300;
};

Window_LightingShader.prototype.windowHeight = function () {
    return this.fittingHeight(this.numVisibleRows());
};

Window_LightingShader.prototype.addCommand = function (symbol, min, max, inc) {
    Window_Command.prototype.addCommand.call(this, "", symbol);
    var data = this._list[this._list.length - 1];
    data.min = min;
    data.max = max;
    data.inc = inc;
};

Window_LightingShader.prototype.makeCommandList = function () {
    this.addCommand("u_col", -3, 3, 0.05);
    this.addCommand("u_contrast", -3, 3, 0.05);
    this.addCommand("u_saturation", -3, 3, 0.05);
    this.addCommand("u_brightness", -1, 1, 0.05);
    this.addCommand("u_pop_strength", -3, 3, 0.05);
    this.addCommand("u_pop_threshold", -3, 3, 0.05);
    this.addCommand("u_color_contrast", -5, 5, 0.05);
    this.addCommand("u_ambient", -3, 3, 0.05);
    this.addCommand("u_ambientColor0", 0, 1, 0.01);
    this.addCommand("u_ambientColor1", 0, 1, 0.01);
    this.addCommand("u_ambientColor2", 0, 1, 0.01);
    this.addCommand("u_ambient_wall", -3, 3, 0.05);
    this.addCommand("color");
};

Window_LightingShader.prototype.drawItem = function (index) {
    var rect = this.itemRectForText(index);
    if (this.commandSymbol(index) === 'color') {
        var rgb = $gameMap.lightingShaderUniforms().u_ambientColor.value.clone().map(function (i) { return i * 255; });
        var hex = Dhoom.Utils.rgbToCSSHex(rgb);
        this.contents.fillRect(rect.x, rect.y, rect.width, rect.height, hex);
        this.contents.fontSize = Math.round(rect.height / 2);
        this.contents.drawText(hex, rect.x, rect.y, rect.width, rect.height, 'center');
    } else {
        var value = this.value(index);
        this._tempValues[this._list[index].symbol] = value;
        var h = Math.round(rect.height / 2);
        this.contents.fillRect(rect.x, rect.y + (rect.height - h) / 2, rect.width, h, '#c4c4c4');
        var len = this._list[index].max - this._list[index].min;
        var s = (value - this._list[index].min) / len;
        this.contents.fillRect(rect.x + s * rect.width - 2, rect.y, 5, rect.height, '#ffffff');
        this.contents.fillRect(rect.x + s * rect.width - 1, rect.y + 1, 3, rect.height - 2, '#000000');
        this.contents.fontSize = h;
        this.contents.drawText(this._list[index].symbol + ' : ' + value.toFixed(2), rect.x, rect.y, rect.width, rect.height, 'center');
    }
};

Window_LightingShader.prototype.value = function (index) {
    if (this.commandSymbol(index).match(/u_ambientColor(\d)/i)) {
        return $gameMap.lightingShaderUniforms().u_ambientColor.value[Number(RegExp.$1)];
    }
    return $gameMap.lightingShaderUniforms()[this._list[index].symbol].value;
};

Window_LightingShader.prototype.update = function () {
    Window_Command.prototype.update.call(this);
    this.updateData();
};

Window_LightingShader.prototype.updateData = function () {
    for (var i = 0; i < this.maxItems(); i++) {
        var sym = this._list[i].symbol;
        if (sym !== 'color' && this._tempValues[sym] !== this.value(i)) {
            this.refresh();
            return;
        }
    }
};

Window_LightingShader.prototype.cursorRight = function (wrap) {
    var index = this.index();
    var data = this._list[index];
    var symbol = this.commandSymbol(index);
    if (symbol !== 'color') {
        var value = this.value(index);
        value = Math.min(data.max, Math.round(value * 10000 + data.inc * 10000) / 10000);
        if (this.commandSymbol(index).match(/u_ambientColor(\d)/i)) {
            $gameMap.lightingShaderUniforms().u_ambientColor.value[Number(RegExp.$1)] = value;
        } else {
            $gameMap.lightingShaderUniforms()[symbol].value = value;
        }
    }
};

Window_LightingShader.prototype.cursorLeft = function (wrap) {
    var index = this.index();
    var data = this._list[index];
    var symbol = this.commandSymbol(index);
    if (symbol !== 'color') {
        var value = this.value(index);
        value = Math.max(data.min, Math.round(value * 10000 - data.inc * 10000) / 10000);
        if (this.commandSymbol(index).match(/u_ambientColor(\d)/i)) {
            $gameMap.lightingShaderUniforms().u_ambientColor.value[Number(RegExp.$1)] = value;
        } else {
            $gameMap.lightingShaderUniforms()[symbol].value = value;
        }
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Window_Options
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Window_Options_makeCommandList = Window_Options.prototype.makeCommandList;
Window_Options.prototype.makeCommandList = function () {
    Dhoom.ParticleLightingSystem.Window_Options_makeCommandList.call(this);
    this.addLightingParticleCommand();
};

Window_Options.prototype.addLightingParticleCommand = function () {
    this.addCommand(Dhoom.ParticleLightingSystem.lightTerm, 'lightings');
    this.addCommand(Dhoom.ParticleLightingSystem.particleTerm, 'particles');
};

Dhoom.ParticleLightingSystem.Window_Options_drawItem = Window_Options.prototype.drawItem;
Window_Options.prototype.drawItem = function (index) {
    if (['lightings', 'particles'].contains(this.commandSymbol(index))) {
        var rect = this.itemRectForText(index);
        var titleWidth = this.textWidth(this.commandName(index)) + 48;
        var statusWidth = rect.width - titleWidth;
        this.resetTextColor();
        this.changePaintOpacity(this.isCommandEnabled(index));
        this.drawText(this.commandName(index), rect.x, rect.y, titleWidth, 'left');
        var stats = ['OFF', 'MIN', 'MAX'];
        var separator = '|';
        var sWidth = this.textWidth(separator);
        statusWidth -= sWidth * 2;
        statusWidth /= 3;
        var x = titleWidth;
        var value = this.getConfigValue(this.commandSymbol(index));
        for (var i = 0; i < stats.length; i++) {
            this.changePaintOpacity(value === i);
            this.drawText(stats[i], x, rect.y, statusWidth, 'center');
            x += statusWidth;
            if (i !== stats.length - 1) {
                this.changePaintOpacity(true);
                this.drawText(separator, x, rect.y, sWidth, 'center');
                x += sWidth;
            }
        }
    } else {
        Dhoom.ParticleLightingSystem.Window_Options_drawItem.call(this, index);
    }
};

Dhoom.ParticleLightingSystem.Window_Options_cursorRight = Window_Options.prototype.cursorRight;
Window_Options.prototype.cursorRight = function (wrap) {
    var index = this.index();
    var symbol = this.commandSymbol(index);
    var value = this.getConfigValue(symbol);
    if (['lightings', 'particles'].contains(symbol)) {
        if (symbol === 'lightings') {
            value = Graphics.isWebGL() ? (value + 1).clamp(0, 2) : 0;
        } else {
            value = (value + 1).clamp(0, 2);
        }
        this.changeValue(symbol, value);
    } else {
        Dhoom.ParticleLightingSystem.Window_Options_cursorRight.call(this, wrap);
    }
};

Dhoom.ParticleLightingSystem.Window_Options_cursorLeft = Window_Options.prototype.cursorLeft;
Window_Options.prototype.cursorLeft = function (wrap) {
    var index = this.index();
    var symbol = this.commandSymbol(index);
    var value = this.getConfigValue(symbol);
    if (['lightings', 'particles'].contains(symbol)) {
        if (symbol === 'lightings') {
            value = Graphics.isWebGL() ? (value - 1).clamp(0, 2) : 0;
        } else {
            value = (value - 1).clamp(0, 2);
        }
        this.changeValue(symbol, value);
    } else {
        Dhoom.ParticleLightingSystem.Window_Options_cursorLeft.call(this, wrap);
    }
};

Dhoom.ParticleLightingSystem.Window_Options_processOk = Window_Options.prototype.processOk;
Window_Options.prototype.processOk = function () {
    var index = this.index();
    var symbol = this.commandSymbol(index);
    var value = this.getConfigValue(symbol);
    if (['lightings', 'particles'].contains(symbol)) {
        if (Graphics.isWebGL()) {
            value++;
            if (value > 2) value = 0;
        } else {
            value = 0;
        }
        this.changeValue(symbol, value);
    } else {
        Dhoom.ParticleLightingSystem.Window_Options_processOk.call(this);
    }
};

Dhoom.ParticleLightingSystem.Window_Options_setConfigValue = Window_Options.prototype.setConfigValue;
Window_Options.prototype.setConfigValue = function (symbol, volume) {
    Dhoom.ParticleLightingSystem.Window_Options_setConfigValue.call(this, symbol, volume);
    if (symbol === 'lightings' || symbol === 'particles') {
        $gameSwitches.updateParticleLightSwitches();
    }
};

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Scene_Map
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
Dhoom.ParticleLightingSystem.Scene_Map_createSpriteset = Scene_Map.prototype.createSpriteset;
Scene_Map.prototype.createSpriteset = function () {
    Dhoom.ParticleLightingSystem.Scene_Map_createSpriteset.call(this);
    this.initShader();
};

Scene_Map.prototype.initShader = function () {
    var frag = `varying vec2 vTextureCoord;
    uniform sampler2D uSampler;
    uniform float u_time;
    uniform vec2 u_resolution;
    uniform sampler2D u_lights;
    uniform float u_col;
    uniform float u_ambient;
    uniform float u_ambient_wall;
    uniform vec3 u_ambientColor;
    uniform float u_contrast;
    uniform float u_saturation;
    uniform float u_pop_strength;
    uniform float u_pop_threshold;
    uniform float u_brightness;
    uniform float u_color_contrast;
    
    void main(void) {
        vec3 base_col = texture2D(uSampler, vTextureCoord).rgb;
        float grey = dot(base_col, vec3(0.299, 0.587, 0.114));
        vec3 out_col = grey > 0.5 ? 1.0 - (1.0 - 2.0 * (base_col - 0.5)) * (1.0 - u_col) : 2.0 * base_col * u_col;
        out_col = mix(out_col, u_ambientColor, u_ambient);
        out_col = mix(vec3(grey), out_col, u_saturation);
        out_col = (out_col - 0.5) * u_contrast + 0.5;
        out_col = out_col + u_pop_strength * max(grey - u_pop_threshold, 0.0);
        out_col = out_col + u_brightness;
        vec3 lights_col = texture2D(u_lights, vec2(gl_FragCoord.x / u_resolution.x, 1.0 - (gl_FragCoord.y / u_resolution.y))).rgb;
        grey = dot(lights_col, vec3(0.333));
        out_col = mix(out_col, base_col * normalize(lights_col + 0.05) * u_color_contrast, grey * u_ambient);
        gl_FragColor = vec4(out_col, 1.0);
    }`;
    var vert = '';
    this._lightingShader = new PIXI.Filter(vert, frag, $gameMap.lightingShaderUniforms());
    this._spriteset._baseSprite.filters = [this._lightingShader];
    this._lightingShader.enabled = false;
    this._lightingSpriteset = new Spriteset_Lightings();
    this._lightingSliderWindow = new Window_LightingShader();
    this._lightingSliderWindow.deactivate();
    this._lightingSliderWindow.hide();
    this.addChild(this._lightingSliderWindow);
    this._lightingBlockedPos = [];
    $gameTemp._lightingShaderRefreshRequested = true;
};

Dhoom.ParticleLightingSystem.Scene_Map_update = Scene_Map.prototype.update;
Scene_Map.prototype.update = function () {
    Dhoom.ParticleLightingSystem.Scene_Map_update.call(this);
    if (!SceneManager.isSceneChanging()) this.updateLightingShader();
    // if (Input.isPressed('control')) this.drawBlockEdges();
};

Scene_Map.prototype.updateLightingShader = function () {
    if ($gameSystem.isLightingEnabled()) {
        var uniforms = $gameMap.lightingShaderUniforms();
        this._lightingShader.enabled = uniforms.enabled;
        if (uniforms.enabled) {
            $gameMap.updateLightingShader();
            this._lightingSpriteset.update();
        }
        var keys = Object.keys(uniforms);
        for (var i = 0; i < keys.length; i++) {
            if (typeof uniforms[keys[i]] === 'object') this._lightingShader.uniforms[keys[i]] = uniforms[keys[i]].value;
        }
        if (!this._lightingShader.uniforms.u_lights) this._lightingShader.uniforms.u_lights = this._lightingSpriteset._lightMap;
        if ($gameTemp.isPlaytest()) {
            if (Input.isTriggered('tab')) {
                this._lightingSliderWindow.visible = !this._lightingSliderWindow.visible;
                this._lightingSliderWindow.active = this._lightingSliderWindow.visible;
            }
        }
    } else {
        this._lightingShader.enabled = false;
    }
};

Scene_Map.prototype.refreshLightingBlockedBitmap = function () {
    var w = $gameMap.width();
    var h = $gameMap.height();
    var tw = $gameMap.tileWidth();
    var th = $gameMap.tileHeight();
    this._lightingBlockedBitmap = new Bitmap(w * tw, h * th);
    for (var x = 0; x < w; x++) {
        for (var y = 0; y < h; y++) {
            if (Dhoom.ParticleLightingSystem.lightBlockTerrain.contains($gameMap.terrainTag(x, y)) ||
                Dhoom.ParticleLightingSystem.lightBlockRegion.contains($gameMap.regionId(x, y))) {
                this._lightingBlockedBitmap.fillRect(x * tw, y * th, tw, th, '#000000');
                if (!this._lightingBlockedPos.contains(x + ':' + y)) this._lightingBlockedPos.push(x + ':' + y);
            }
        }
    }
};

Scene_Map.prototype.drawBlockEdges = function () {
    if (!this._drawBlockEdgesSprite) {
        this._drawBlockEdgesSprite = new PIXI.Graphics();
        this.addChild(this._drawBlockEdgesSprite);
    }
    var grp = this._drawBlockEdgesSprite;
    grp.clear();
    var dx = $gameMap.displayX() * $gameMap.tileWidth();
    var dy = $gameMap.displayY() * $gameMap.tileHeight();
    var px = $gamePlayer.screenX();
    var py = $gamePlayer.screenY() - $gameMap.tileHeight() / 2;
    var edges = $gameMap.calculateVisibilityPolygon(px + dx, py + dy, Dhoom._polyRadius || 10, $gameTemp._castRectArea)[0];
    grp.beginFill(0x0000ff, 0.5);
    grp.drawRect($gameTemp._castRectArea[0] - dx, $gameTemp._castRectArea[1] - dy, $gameTemp._castRectArea[2], $gameTemp._castRectArea[3]);
    grp.endFill();
    if ($gameMap._lastPolyRect) {
        grp.beginFill(0x0000ff, 0.5);
        grp.drawRect($gameMap._lastPolyRect[0], $gameMap._lastPolyRect[1], $gameMap._lastPolyRect[2], $gameMap._lastPolyRect[3]);
        grp.endFill();
    }
    if (edges.length > 1) {
        grp.lineStyle(1, 0x00ff00, 0.5);
        for (var i = 0; i < edges.length - 1; i++) {
            grp.beginFill(0x00ff00, 0.5);
            grp.moveTo(px, py);
            grp.lineTo(Math.max(0, edges[i][1] - dx), Math.max(0, edges[i][2] - dy));
            grp.lineTo(Math.max(0, edges[i + 1][1] - dx), Math.max(0, edges[i + 1][2] - dy));
            grp.endFill();
        }
        grp.beginFill(0x00ff00, 0.5);
        grp.moveTo(px, py);
        grp.lineTo(Math.max(0, edges[edges.length - 1][1] - dx), Math.max(0, edges[edges.length - 1][2] - dy));
        grp.lineTo(Math.max(0, edges[0][1] - dx), Math.max(0, edges[0][2] - dy));
        grp.endFill();
    }
    edges = $gameMap._terrainEdgesClone;
    for (var i = 0; i < edges.length; i++) {
        grp.lineStyle(0, 0x00ff00, 0.5);
        if (edges[i]) {
            var x = edges[i].sx - dx;
            var y = edges[i].sy - dy;
            var width = edges[i].ex - edges[i].sx + 1;
            var height = edges[i].ey - edges[i].sy + 1;
            var color = '#FF0000';
            if (edges[i].edge) color = '#00FF00';
            grp.beginFill(0xff0000, 0.5);
            grp.drawRect(x - 1, y - 1, width + 1, height + 1);
            grp.drawCircle(x, y, 8);
            grp.drawCircle(x + width, y + height, 8);
        }
    }
};

Dhoom.ParticleLightingSystem.Scene_Map_terminate = Scene_Map.prototype.terminate;
Scene_Map.prototype.terminate = function () {
    Dhoom.ParticleLightingSystem.Scene_Map_terminate.call(this);
    if (this._lightingSpriteset._lightMap) this._lightingSpriteset._lightMap.destroy({ destroyBase: true });
};