//=============================================================================
// Cyclone Engine - Advanced Maps
//=============================================================================

/*:
 * @target MZ
 * @plugindesc Adds new features to game map 1.00.00
 *
 * <pluginName:CycloneAdvancedMaps>
 * @author Hudell
 * @url
 *
 * @help
 * ===========================================================================
 *                                    88
 *                                    88
 *                                    88
 *   ,adPPYba, 8b       d8  ,adPPYba, 88  ,adPPYba,  8b,dPPYba,   ,adPPYba,
 *  a8"     "" `8b     d8' a8"     "" 88 a8"     "8a 88P'   `"8a a8P_____88
 *  8b          `8b   d8'  8b         88 8b       d8 88       88 8PP"""""""
 *  "8a,   ,aa   `8b,d8'   "8a,   ,aa 88 "8a,   ,a8" 88       88 "8b,   ,aa
 *   `"Ybbd8"'     Y88'     `"Ybbd8"' 88  `"YbbdP"'  88       88  `"Ybbd8"'
 *                 d8'
 *                d8'
 * Advanced Map Features                                             by Hudell
 * ===========================================================================
 * Terms of Use
 * ===========================================================================
 * 1. For support, feature requests or bug reports, you may contact me through
 *  any of the following channels:
 *
 *   1.a. Opening an issue on the plugin's GitHub repository:
 *      https://github.com/Hudell/cyclone-engine
 *   1.b. Opening threads on the plugin's itch.io page
 *   1.c. Tagging my user on Rpg Maker related sub-reddits, such as r/rpgmaker
 *
 * 2. This plugin is released under the Apache License 2.0 (Apache-2.0).
 *
 * 3. You can send me your own changes to this plugin if you wish to see them
 * included in an update, by registering a Pull Request on the plugin's GitHub
 * repository.
 *
 * 4. This plugin is provided as is. While I'll often read feedback and offer
 * updates to my plugins, I am in no obligation to do so.
 *
 * 5. I'm not responsible for anything created with this plugin.
 * ===========================================================================
 * Change Log
 * ===========================================================================
 * 2022-07-17 - Version 1.00.00
 *
 * ===========================================================================
 *
 * @param debug
 * @text Debug
 * @desc Generate debug logs to help identify problems
 * @type boolean
 * @default false
 *
 * @param mapChangeEventId
 * @text Map Change Event Id
 * @desc Select a Common Event to be called every time the map changes
 * @type common_event
 * @default 0
 *
 * @param Change Tile Size
 *
 * @param tileWidth
 * @text Tile Width
 * @parent Change Tile Size
 * @desc The width of each tile, in pixels
 * @type number
 * @default 48
 *
 * @param tileHeight
 * @text Tile Height
 * @parent Change Tile Size
 * @desc The height of each tile, in pixels
 * @type number
 * @default 48
 *
 * @param tilesetPath
 * @text Tileset Path
 * @parent Change Tile Size
 * @desc You can define an alternate path for loading the tilesets
 * @type string
 * @default img/tilesets/
 *
 * @param Map Settings
 *
 * @param disableAutoShadows
 * @text Disable Auto Shadows
 * @parent Map Settings
 * @desc Make the game stop rendering the map's auto shadows.
 * @type boolean
 * @default false
 *
 * @param disableTilemap
 * @text Disable Tilemap
 * @parent Map Settings
 * @desc If your entire game uses only parallax mapping, you can disable the tilemap to improve performance
 * @type boolean
 * @default false
 *
 * @param balloonZ
 * @text Balloon Z
 * @parent Map Settings
 * @type number
 * @desc Use this to change the balloon Z value when using parallaxes.
 * Recomended default = 7, with parallaxes = 30
 * @default 7
 *
 * @param animationZ
 * @text Animations Z
 * @parent Map Settings
 * @type number
 * @desc Use this to change the animations Z value when using parallaxes.
 * Recomended default = 8, with parallaxes = 31
 * @default 8
 *
 * @param Overlays
 * @text Overlays
 *
 * @param overlayEnabled
 * @text Enabled
 * @parent Overlays
 * @type boolean
 * @desc Enable the overlay features
 * @default false
 *
 * @param overlayPath
 * @text Overlay Path
 * @parent Overlays
 * @desc You can define an alternate path for loading the overlays
 * @type string
 * @default img/overlays/
 *
 * @param folders
 * @text Folders
 * @parent Overlays
 * @desc How overlay files are organized
 * @type select
 * @default none
 * @option No Folders
 * @value none
 * @option One Per Layer
 * @value perLayer
 * @option One Per Map
 * @value perMap
 *
 * @param layers
 * @text Layers
 * @parent Overlays
 * @type struct<OverlayItem>[]
 * @default ["{\"layerName\":\"Ground\",\"fileName\":\"ground\",\"tagName\":\"ground\",\"appendMapId\":\"true\",\"switchId\":\"0\",\"quickStart\":\"true\",\"z\":\"1\"}","{\"layerName\":\"Parallax\",\"fileName\":\"par\",\"tagName\":\"par\",\"appendMapId\":\"true\",\"switchId\":\"0\",\"quickStart\":\"true\",\"z\":\"20\"}","{\"layerName\":\"Shadow\",\"fileName\":\"shadow\",\"tagName\":\"shadow\",\"appendMapId\":\"true\",\"switchId\":\"0\",\"quickStart\":\"true\",\"z\":\"21\"}","{\"layerName\":\"Fog\",\"fileName\":\"fog\",\"tagName\":\"fog\",\"appendMapId\":\"true\",\"switchId\":\"0\",\"quickStart\":\"true\",\"z\":\"22\"}","{\"layerName\":\"Light\",\"fileName\":\"light\",\"tagName\":\"light\",\"appendMapId\":\"true\",\"switchId\":\"0\",\"quickStart\":\"true\",\"z\":\"23\",\"opacity\":\"185\",\"opacitySpeed\":\"180\",\"blendMode\":\"1\"}"]
 *
 * @param Regions
 *
 * @param bushRegionId
 * @text Bush Region
 * @parent Regions
 * @type number
 * @desc Configure a region id that when used will flag the tile as a bush
 * @default 0
 *
 * @param Region Movement
 * @parent Regions
 *
 * @param blockRegionId
 * @text Blocked Region
 * @parent Region Movement
 * @type number
 * @desc Configure a region id that should block passage on a tile
 * @default 0
 *
 * @param unblockRegionId
 * @text Unblocked Region
 * @parent Region Movement
 * @type number
 * @desc Configure a region id that should unblock passage on a tile
 * @default 0
 *
 * @param blockPlayerRegionId
 * @text (Player) Blocked Region
 * @parent Region Movement
 * @type number
 * @desc Configure a region id that should block the player's passage on a tile
 * @default 0
 *
 * @param unblockPlayerRegionId
 * @text (Player) Unblocked Region
 * @parent Region Movement
 * @type number
 * @desc Configure a region id that should unblock the player's passage on a tile
 * @default 0
 *
 * @param blockEventRegionId
 * @text (Events) Blocked Region
 * @parent Region Movement
 * @type number
 * @desc Configure a region id that should block event's passage on a tile
 * @default 0
 *
 * @param unblockEventRegionId
 * @text (Events) Unblocked Region
 * @parent Region Movement
 * @type number
 * @desc Configure a region id that should unblock event's passage on a tile
 * @default 0
 *
 * @param Region Actions
 * @parent Regions
 *
 * @param commonEventRegions
 * @text Region Based Common Events
 * @parent Region Actions
 * @type struct<CommonEventRegion>[]
 * @desc Configure certain regions to trigger common events when stepped on
 *
 * @param namedRegions
 * @text Named Regions
 * @parent Region Actions
 * @type struct<NamedRegion>[]
 * @desc Configure certain regions to display a name on screen
 *
 * @param regionNamesStay
 * @text Region Names Stay on Screen
 * @parent Region Actions
 * @desc if on, the names will stay on screen while the player is over the region
 * @type boolean
 * @default false
 *
 * @command newLayerOpacity
 * @text Change Layer Opacity
 * @desc
 *
 * @arg layerName
 * @text Layer Name
 * @type string
 * @desc The name of the layer that you want to change the opacity of
 *
 * @arg opacity
 * @text Opacity
 * @type number
 * @desc The new value for the layer opacity: 1 - 255
 *
 * @arg duration
 * @text Duration
 * @type number
 * @desc How long should the opacity transition last? Leave it at zero to use the map's default.
 * @default 0
 *
 * @command layer
 * @text Change layer file name
 * @desc
 *
 * @arg layerName
 * @text Layer Name
 * @type string
 * @desc The name of the layer that you want to change the file
 *
 * @arg fileName
 * @text File Name
 * @type string
 * @desc The new file name for this layer
 *
 * @command customLayer
 * @text Create a custom layer only for this map
 * @desc
 *
 * @arg layerName
 * @text Layer Name
 * @type string
 * @desc A name to identify this custom layer
 *
 * @arg fileName
 * @text File Name
 * @type string
 * @desc The file name for this layer
 *
 * @arg z
 * @text Z Index
 * @type number
 * @desc The Z index for this layer
 *
 * @arg switchId
 * @text Switch
 * @type switch
 * @desc The switch that controls this layer's visibility
 *
 * @arg x
 * @text X Position
 * @type number
 * @desc The X position of this layer
 *
 * @arg y
 * @text Y Position
 * @type number
 * @desc The Y position of this layer
 *
 * @arg unit
 * @text Position Unit
 * @type select
 * @desc The type of position
 * @option Pixels
 * @value pixels
 * @options Tiles
 * @value tiles
 *
 * @arg invertSwitch
 * @text Invert Switch Value?
 * @type boolean
 * @desc Should this layer be visible only when the switch is off?
 * @on Visible when switch is off
 * @off Visible when switch is on
 **/
/*~struct~OverlayItem:
 * @param layerName
 * @text Layer Name
 * @desc Name used to identify this layer on plugin commands
 * @default
 *
 * @param fileName
 * @text Layer Filename
 * @desc The base name of the files for this layer.
 * @default
 *
 * @param tagName
 * @text Tag Name
 * @desc If specified, this layer will only be loaded in maps that include this notetag
 * @default
 *
 * @param appendMapId
 * @text Append Map Id
 * @desc Determine if the map id should be appended to the file names
 * @type boolean
 * @default true
 *
 * @param switchId
 * @text Switch ID
 * @type switch
 * @desc A switch to control if this layer should be enabled or not
 * @default 0
 *
 * @param invertSwitch
 * @text Invert Switch
 * @type boolean
 * @desc Display this layer when the switch is off instead of on.
 * @default 0
 *
 * @param quickStart
 * @text Enable Automatically
 * @type boolean
 * @desc If this param is on, the layer switch will be turned on automatically on new game.
 * @default true
 *
 * @param z
 * @text Z value
 * @type number
 * @desc What should be the Z value of this layer?
 * @default 0
 *
 * @param opacity
 * @text Opacity
 * @type number
 * @desc The opacity level for this layer
 * @min 1
 * @max 255
 * @default 255
 *
 * @param opacitySpeed
 * @text Opacity Speed
 * @type number
 * @desc How many frames should it take for this layer to change from completely hidden to completely visible.
 * @min 1
 * @max 255
 * @default 25
 *
 * @param mapList
 * @text Map List
 * @type number[]
 * @desc A list of map ids where this layer will be active without needing tags
 * @default []
 *
 * @param blendMode
 * @text Blend Mode
 * @type number
 * @desc The blend type you want to use in this layer. Default is 0 for most layers, or 1 for lights.
 * @default 0
 *
 * @param position
 * @text Position
 * @type Struct<LayerPosition>
 * @desc The top left position of this layer.
 * @default {}
 *
 * @param fadeIn
 * @text Fade In
 * @type boolean
 * @desc Should this layer be made visible with a fade effect or instantly?
 * @on Fade In
 * @off Instantly
 * @default false
 *
*/
/*~struct~LayerPosition:
 * @param x
 * @text X Position
 * @type number
 * @default 0
 *
 * @param y
 * @text Y Position
 * @type number
 * @default 0
 *
 * @param unit
 * @text Position Unit
 * @desc Is this layer's position set in pixels or tiles?
 * @type select
 * @default tiles
 * @option Tiles
 * @value tiles
 * @option Pixels
 * @value pixels
 *
 * @param boundTo
 * @text Bound To
 * @desc Is this layer's position bound to the map or the screen?
 * @type select
 * @default map
 * @option Map
 * @value map
 * @option Screen
 * @value screen
 *
 * @param moveX
 * @text X Speed
 * @type number
 * @desc how many pixels the layer should move horizontally at a time. Use a negative value to move left.
 * @default 0
 *
 * @param moveY
 * @text Y Speed
 * @type number
 * @desc how many pixels the layer should move vertically at a time. Use a negative value to move up.
 * @default 0
 *
 * @param tiling
 * @text Tiling
 * @type boolean
 * @desc Should this layer use a tiling sprite? (Usually only enabled for fog layer)
 * @on Tiled
 * @off Not Tiled
 * @default false
 *
 */
/*~struct~CommonEventRegion:
 * @param regionId
 * @text Region Id
 * @type number
 * @desc The regionId to add an event to
 *
 * @param commonEventId
 * @text Common Event
 * @type common_event
 * @desc The common event to be executed on this region
 */
/*~struct~NamedRegion:
 * @param regionId
 * @text Region Id
 * @type number
 * @desc The regionId to display a name on
 *
 * @param name
 * @text Name
 * @type string
 * @desc The name of this region
 */
(function () {
'use strict';

globalThis.CyclonePatcher=class{static initialize(t){this.pluginName=t,this.superClasses=new Map;}static _descriptorIsProperty(t){return t.get||t.set||!t.value||"function"!=typeof t.value}static _getAllClassDescriptors(t,e=!1){if(t===Object)return {};const r=Object.getOwnPropertyDescriptors(e?t.prototype:t);let s={};if(t.prototype){const r=Object.getPrototypeOf(t.prototype).constructor;r!==Object&&(s=this._getAllClassDescriptors(r,e));}return Object.assign({},s,r)}static _assignDescriptor(t,e,r,s,a=!1){if(this._descriptorIsProperty(r))r.get||r.set?Object.defineProperty(t,s,{get:r.get,set:r.set,enumerable:r.enumerable,configurable:r.configurable}):Object.defineProperty(t,s,{value:r.value,enumerable:r.enumerable,configurable:r.configurable});else {let r=s;if(a)for(;r in t;)r=`_${r}`;t[r]=e[s];}}static _applyPatch(t,e,r,s,a=!1){const n=this._getAllClassDescriptors(t,a),i=a?t.prototype:t,o=a?e.prototype:e,l=Object.getOwnPropertyDescriptors(o);let u=!1;for(const t in l){if(s.includes(t))continue;if(t in n){u=!0;const e=n[t];this._assignDescriptor(r,i,e,t,!0);}const e=l[t];this._assignDescriptor(i,o,e,t);}return u}static patchClass(t,e){const r=this.superClasses&&this.superClasses[t.name]||{},s={},a={},n=e(a,s);if("function"!=typeof n)throw new Error(`Invalid class patch for ${t.name}`);const i=Object.getOwnPropertyNames(class{}),o=Object.getOwnPropertyNames(class{}.prototype),l=this._applyPatch(t,n,r,i),u=this._applyPatch(t,n,s,o,!0);if(l){const t=Object.getOwnPropertyDescriptors(r);for(const e in t)this._assignDescriptor(a,r,t[e],e);u&&(a.$prototype=s);}else Object.assign(a,s);this.superClasses&&(this.superClasses[t.name]=a);}};const t=Object.freeze(["TRUE","ON","1","YES","T","V"]);class e extends CyclonePatcher{static initialize(t){super.initialize(t),this.fileName=void 0,this.params={},this.structs=new Map,this.eventListeners=new Map,this.structs.set("Dictionary",{name:{type:"string",defaultValue:""},value:{type:"string",defaultValue:""}});}static register(t={}){const e=this.loadAllParams();this.params=this.loadParamMap(t,e);}static loadAllParams(){for(const t of globalThis.$plugins){if(!t||!t.status)continue;if(!t.description||!t.description.includes(`<pluginName:${this.pluginName}`))continue;this.fileName=t.name;const e=new Map;for(const r in t.parameters)r&&!r.startsWith("-")&&e.set(r,t.parameters[r]);return e}}static loadParamMap(t,e){const r={};for(const s in t)if(t.hasOwnProperty(s))try{r[s]=this.parseParam(s,t,e);}catch(t){console.error(`CycloneEngine crashed while trying to parse a parameter value (${s}). Please report the following error to Hudell:`),console.log(t);}return r}static registerEvent(t,e){this.eventListeners.has(t)||this.eventListeners.set(t,new Set);this.eventListeners.get(t).add(e);}static removeEventListener(t,e){if(!this.eventListeners.has(t))return;this.eventListeners.get(t).delete(e);}static shouldReturnCallbackResult(t,{abortOnTrue:e,abortOnFalse:r,returnOnValue:s}){return !(!1!==t||!r)||(!(!0!==t||!e)||!(void 0===t||!s))}static runEvent(t,{abortOnTrue:e=!1,abortOnFalse:r=!1,returnOnValue:s=!1}={},...a){if(!this.eventListeners.has(t))return;const n=this.eventListeners.get(t);for(const t of n){if("number"==typeof t){this.runCommonEvent(t);continue}if("function"!=typeof t){console.error("CycloneEngine: Invalid callback type:"),console.log(t);continue}const n=t(...a);if(this.shouldReturnCallbackResult(n,{abortOnTrue:e,abortOnFalse:r,returnOnValue:s}))return n}}static runCommonEvent(t){const e=globalThis.$dataCommonEvents[t];if(!e)return;const r=new Game_Interpreter(1);if(r.setup(e.list,0),!this._interpreters){this._interpreters=new Set;const t=SceneManager.updateMain;SceneManager.updateMain=()=>{t.call(SceneManager),this.update();};}this._interpreters.add(r);}static update(){if(this._interpreters)for(const t of this._interpreters)t.update(),t.isRunning()||this._interpreters.delete(t);}static getPluginFileName(){return this.fileName??this.pluginName}static isTrue(e){return "string"!=typeof e?Boolean(e):t.includes(e.toUpperCase())}static isFalse(t){return !this.isTrue(t)}static getIntParam({value:t,defaultValue:e}){try{const r=parseInt(t);return isNaN(r)?e:r}catch(r){return ""!==t&&console.error(`Cyclone Engine plugin ${this.pluginName}: Param is expected to be an integer number, but the received value was '${t}'.`),e}}static getFloatParam({value:t,defaultValue:e}){try{const r=parseFloat(t.replace(",","."));return isNaN(r)?e:r}catch(r){return ""!==t&&console.error(`Cyclone Engine plugin ${this.pluginName}: Param is expected to be a number, but the received value was '${t}'.`),e}}static getIntListParam({value:t}){return this.parseArray((t??"").trim(),(t=>{try{return parseInt(t.trim())}catch(e){return ""!==t&&console.error(`Cyclone Engine plugin ${this.pluginName}: Param is expected to be a list of integer numbers, but one of the items was '${t}'.`),0}}))}static parseStructArrayParam({data:t,type:e}){const r=[];for(const s of t){const t=this.parseStructParam({value:s,defaultValue:"",type:e});t&&r.push(t);}return r}static getFloatListParam({value:t}){return this.parseArray((t||"").trim(),(t=>{try{return parseFloat(t.trim())}catch(e){return ""!==t&&console.error(`Cyclone Engine plugin ${this.pluginName}: Param ${name} is expected to be a list of numbers, but one of the items was '${t}'.`),0}}))}static getParam({value:t,defaultValue:e,type:r}){if(r.endsWith("[]"))return this.parseArrayParam({value:t,type:r});if(r.startsWith("struct<"))return this.parseStructParam({value:t,defaultValue:e,type:r});if(void 0===t)return e;switch(r){case"int":return this.getIntParam({value:t,defaultValue:e});case"float":return this.getFloatParam({value:t,defaultValue:e});case"boolean":return "boolean"==typeof t?t:this.isTrue(String(t).trim());default:return t}}static getPluginParam(t){return this.params.get(t)}static defaultValueForType(t){switch(t){case"int":case"float":return 0;case"boolean":return !1}return ""}static parseParam(t,e,r){let s=e[t];s&&"string"==typeof s&&(s={type:s,defaultValue:this.defaultValueForType(s)});const{name:a=t,type:n="string",defaultValue:i=""}=s;let o;if(r)o=r.get(a)??i;else {o=(this.getPluginParam(a)||{}).value??i;}return this.getParam({value:o,defaultValue:i,type:n})}static parseArrayParam({value:t,type:e}){const r=this.parseArray(t);if(!r||!r.length)return r;const s=e.substr(0,e.length-2),a=[];for(const t of r){const e=this.defaultValueForType(s);a.push(this.getParam({value:t,type:s,defaultValue:e}));}return a}static getRegexMatch(t,e,r){const s=t.match(e);if(s)return s[r]}static parseStructData(t,e){for(const r in t){if(!t.hasOwnProperty(r))continue;let s=t[r];"string"==typeof s&&(s={type:s,defaultValue:this.defaultValueForType(s)}),e[r]=this.getParam({value:e[r],defaultValue:s.defaultValue,type:s.type});}return e}static parseStructParam({value:t,defaultValue:e,type:r}){let s;if(t)try{s=JSON.parse(t);}catch(e){console.error("Cyclone Engine failed to parse param structure: ",t),console.error(e);}s||(s=JSON.parse(e));const a=this.getRegexMatch(r,/struct<(.*)>/i,1);if(!a)return console.error(`Unknown plugin param type: ${r}`),s;const n=this.structs.get(a);return n?this.parseStructData(n,s):(console.error(`Unknown param structure type: ${a}`),s)}static parseList(t,e,r=","){let s=t;s.startsWith("[")&&(s=s.substr(1)),s.endsWith("]")&&(s=s.substr(0,s.length-1));const a=s.split(r||",");return e?a.map((t=>e(t))):a}static parseArray(t,e){let r;try{r=JSON.parse(t);}catch(t){return []}return r&&r.length?e?r.map((t=>e(t))):r:[]}static registerCommand(t,e,r,s=[]){return "function"==typeof e?PluginManager.registerCommand(this.getPluginFileName(),t,e):PluginManager.registerCommand(this.getPluginFileName(),t,(t=>{const s=new Map;for(const e in t)t.hasOwnProperty(e)&&s.set(e,t[e]);const a=this.loadParamMap(e,s);return Object.assign(t,a),r(t)}))}}globalThis.CyclonePlugin=e;

function getMetaObject(notes, tagName) {
  const rgx = new RegExp(`<${tagName}([^>]*)`, 'gis');
  const list = [];

  while (true) {
    const match = rgx.exec(notes);
    if (!match) {
      break;
    }

    if (match.length < 2) {
      continue;
    }

    const dataRgx = /([^=:\n\r\t]+)[=:]?(.*)/gm;
    const obj = match[1];
    const newObject = {};

    while (true) {
      const attribute = dataRgx.exec(obj);
      if (!attribute) {
        break;
      }

      if (attribute.length < 2) {
        continue;
      }

      const name = attribute[1].trim();
      if (!name) {
        continue;
      }

      if (attribute.length > 2 && attribute[0] !== attribute[1]) {
        newObject[name] = attribute[2].trim();
      } else {
        newObject[name] = true;
      }
    }

    list.push(newObject);
  }

  return list;
}

class CycloneAdvancedMaps$1 extends CyclonePlugin {
  static register() {
    super.initialize('CycloneAdvancedMaps');

    this.structs.set('CycloneCommonEventRegion', {
      regionId: 'int',
      commonEventId: 'int',
    });
    this.structs.set('CycloneNamedRegion', {
      regionId: 'int',
      name: 'string',
    });
    this.structs.set('CycloneLayerPosition', {
      x: 'int',
      y: 'int',
      unit: 'string',
      boundTo: 'string',
      moveX: 'int',
      moveY: 'int',
      tiling: 'boolean',
    });
    this.structs.set('CycloneOverlayItem', {
      layerName: 'string',
      fileName: 'string',
      tagName: 'string',
      appendMapId: {
        type: 'boolean',
        defaultValue: true,
      },
      switchId: 'int',
      invertSwitch: 'boolean',
      quickStart: {
        type: 'boolean',
        defaultValue: true,
      },
      z: 'int',
      opacity: {
        type: 'int',
        defaultValue: 255,
      },
      opacitySpeed: {
        type: 'int',
        defaultValue: 25,
      },
      blendMode: 'int',
      mapList: 'int[]',
      position: 'struct<CycloneLayerPosition>',
      fadeIn: 'boolean',
    });
    this.structs.set('CycloneCustomLayer', {
      name: 'string',
      layerName: 'string',
      file: 'string',
      fileName: 'string',
      // tag: 'string',
      switch: 'int',
      switchId: 'int',
      invertSwitch: 'boolean',
      z: 'int',
      opacity: 'int',
      opacitySpeed: 'int',
      blendMode: 'int',
      x: 'int',
      y: 'int',
      unit: 'string',
      boundTo: 'string',
      moveX: 'int',
      moveY: 'int',
      tiling: 'boolean'
    });

    super.register({
      debug: {
        name: 'debug',
        type: 'boolean',
        defaultValue: false,
      },
      mapChangeEventId: 'int',
      tileWidth: {
        type: 'int',
        defaultValue: 48,
      },
      tileHeight: {
        type: 'int',
        defaultValue: 48,
      },
      tilesetPath: {
        type: 'string',
        defaultValue: 'img/tilesets/',
      },
      disableTilemap: 'boolean',
      disableAutoShadows: 'boolean',
      balloonZ: {
        type: 'int',
        defaultValue: 7,
      },
      animationZ: {
        type: 'int',
        defaultValue: 8,
      },

      overlayEnabled: 'boolean',
      overlayPath: {
        type: 'string',
        defaultValue: 'img/overlays',
      },
      folders: 'string',

      layers: {
        type: 'struct<CycloneOverlayItem>[]',
        defaultValue: '[]',
      },

      bushRegionId: 'int',
      blockRegionId: 'int',
      unblockRegionId: 'int',
      blockPlayerRegionId: 'int',
      unblockPlayerRegionId: 'int',
      blockEventRegionId: 'int',
      unblockEventRegionId: 'int',

      commonEventRegions: {
        type: 'struct<CycloneCommonEventRegion>[]',
        defaultValue: '[]',
      },
      namedRegions: {
        type: 'struct<CycloneNamedRegion>[]',
        defaultValue: '[]',
      },
      regionNamesStay: 'boolean',
    });

    this.registerCommand('newLayerOpacity', {
      layerName: 'string',
      opacity: 'int',
      duration: 'int',
    }, ({layerName, opacity, duration}) => {
      this.changeLayerOpacity(layerName, opacity, duration);
    }, ['layerName', 'opacity', 'duration']);

    this.registerCommand('layer', {
      layerName: 'string',
      fileName: 'string',
    }, ({layerName, fileName}) => {
      if (!layerName || !fileName) {
        CycloneAdvancedMaps$1.params.debug && console.error('Invalid layer parameters', layerName, fileName);
        return;
      }

      CycloneAdvancedMaps$1.changeLayerFileName(layerName, fileName);
    }, ['layerName', 'fileName']);

    this.registerCommand('customLayer', {
      layerName: 'string',
      fileName: 'string',
      z : 'int',
      switchId: 'int',
      x: 'int',
      y: 'int',
      unit: 'string',
      invertSwitch: 'boolean',
    }, ({layerName, fileName, z, switchId = 0, x = 0, y = 0, unit = 'tiles', invertSwitch = false}) => {
      if (!layerName || !fileName || typeof z !== 'number') {
        CycloneAdvancedMaps$1.params.debug && console.error('Invalid custom layer parameters', layerName, fileName, z);
        return;
      }

      CycloneAdvancedMaps$1.addCustomLayer({
        layerName,
        fileName,
        z,
        switchId,
        invertSwitch,
        position: {
          x,
          y,
          unit: ['pixels', 'px'].includes(unit)  ? 'pixels' : 'tiles',
        },
      });
    }, ['layerName', 'fileName', 'z', 'switchId', 'x', 'y', 'unit', 'invertSwitch']);

    this.clearSettings();
  }

  static clearSettings() {
    this.params.debug && console.log('Clearing CycloneAdvancedMaps settings');

    const layers = this.params.layers;
    const commonEventRegions = this.params.commonEventRegions;
    const namedRegions = this.params.namedRegions;

    this.commonEventRegions = new Map();
    this.namedRegions = new Map();
    this.layers = [];
    for (let i = 0; i < layers.length; i++) {
      this.layers.push({
        ...layers[i],
        index: i,
        id: `cyclone_layer_${i}`,
        changed: false,
        extraX: 0,
        extraY: 0,
        opacityChange: this.opacitySpeedToChange(layers[i].opacitySpeed),
      });
    }

    this.params.debug && console.log('Layer Configuration', this.layers);

    for (const config of commonEventRegions) {
      if (config.regionId > 0 && config.commonEventId > 0) {
        this.commonEventRegions.set(config.regionId, config.commonEventId);
      }
    }
    for (const config of namedRegions) {
      if (config.regionId > 0) {
        this.namedRegions.set(config.regionId, config.name.trim());
      }
    }
  }

  static opacitySpeedToChange(speed) {
    if (speed) {
      return 255 / speed;
    }

    return 10;
  }

  static changeLayerOpacity(layerName, opacity, duration) {
    for (const layer of this.layers) {
      if (layer.layerName === layerName) {
        CycloneAdvancedMaps$1.params.debug && console.log(`Layer ${layerName} opacity changed from ${ layer.opacity } to ${ opacity }, duration = ${ duration || layer.opacitySpeed }`);
        layer.opacity = opacity;
        layer.oneTimeOpacityDuration = duration;
        return;
      }
    }
  }

  static addCustomLayer(layerData) {
    this.params.debug && console.log('Add custom layer', layerData);

    // Remove an existing layer with the same name if found
    if (layerData.layerName) {
      for (let i = 0; i < this.layers.length; i++) {
        if (this.layers[i].layerName === layerData.layerName) {
          this.layers.splice(i, 1);
          break;
        }
      }
    }

    this.layers.push({
      extraX: 0,
      extraY: 0,
      ...layerData,
      index: this.layers.length,
      id: `cyclone_custom_layer_${ this.layers.length }`,
      changed: false,
      opacityChange: this.opacitySpeedToChange(layerData.opacitySpeed),
      mapList: [
        $gameMap._mapId,
      ],
    });
  }

  static loadMapCustomLayers() {
    const objects = getMetaObject($dataMap.note || '', 'customLayer');
    const structType = this.structs.get('CycloneCustomLayer');

    for (const data of objects) {
      CycloneAdvancedMaps$1.parseStructData(structType, data);

      if (!data.name && !data.layerName) {
        this.params.debug && console.error('Custom Layer is missing a name');
        continue;
      }

      this.addCustomLayer({
        layerName: data.name || data.layerName,
        fileName: data.file || data.fileName || '',
        tagName: '',
        appendMapId: false,
        switchId: data.switch || data.switchId || 0,
        invertSwitch: !!data.invertSwitch,
        quickStart: false,
        z: data.z || 0,
        opacity: data.opacity || 255,
        opacitySpeed: data.opacitySpeed || 25,
        blendMode: data.blendMode || 0,
        fadeIn: false,
        position: {
          x: data.x || 0,
          y: data.y || 0,
          unit: ['pixels', 'px'].includes(data.unit) ? 'pixels' : 'tiles',
          boundTo: data.boundTo === 'screen' ? 'screen' : 'map',
          moveX: data.moveX || 0,
          moveY: data.moveY || 0,
          tiling: !!data.tiling,
        }
      });
    }
  }

  static changeLayerFileName(layerName, fileName) {
    for (const layer of this.layers) {
      if (layer.layerName === layerName) {
        CycloneAdvancedMaps$1.params.debug && console.log(`Layer ${layerName} file name changed from ${ layer.fileName } to ${ fileName }`);
        layer.fileName = fileName;
        layer.changed = true;
        return;
      }
    }
  }

  static checkRegionActions() {
    const regionId = $gameMap.regionId($gamePlayer.x, $gamePlayer.y);

    if (this.commonEventRegions.has(regionId)) {
      this.runCommonEvent(this.commonEventRegions.get(regionId));
    }
  }
}

globalThis.CycloneAdvancedMaps = CycloneAdvancedMaps$1;
CycloneAdvancedMaps$1.register();

CycloneAdvancedMaps.patchClass(DataManager, $super => class {
  static setupNewGame() {
    $super.setupNewGame.call(this);

    if (!CycloneAdvancedMaps.params.overlayEnabled) {
      return;
    }

    for (const { quickStart, switchId } of CycloneAdvancedMaps.params.layers) {
      if (!quickStart || !switchId) {
        continue;
      }

      CycloneAdvancedMaps.params.debug && console.log(`Initializing switch ${ switchId }`);
      $gameSwitches.setValue(switchId, true);
    }
  }

  static createGameObjects() {
    $super.createGameObjects.call(this);
    CycloneAdvancedMaps.clearSettings();
  }
});

CycloneAdvancedMaps.patchClass(Game_Event, $super => class {
  isMapPassable(x, y, d) {
    const blockRegionId = CycloneAdvancedMaps.params.blockEventRegionId;
    const unblockRegionId = CycloneAdvancedMaps.params.unblockEventRegionId;

    if (blockRegionId > 0 || unblockRegionId > 0) {
      const newX = $gameMap.roundXWithDirection(x, d);
      const newY = $gameMap.roundYWithDirection(y, d);
      const regionId = $gameMap.regionId(newX, newY);

      if (regionId > 0) {
        if (regionId === blockRegionId) {
          return false;
        }

        if (regionId === unblockRegionId) {
          return true;
        }
      }
    }

    return $super.isMapPassable.call(this, x, y, d);
  }
});

CycloneAdvancedMaps.patchClass(Game_Map, $super => class {
  setup(...args) {
    $super.setup.call(this, ...args);
    if (CycloneAdvancedMaps.params.overlayEnabled) {
      CycloneAdvancedMaps.loadMapCustomLayers();
    }
  }

  tileWidth() {
    const customWidth = CycloneAdvancedMaps.params.tileWidth;
    if (typeof customWidth === 'number' && customWidth > 0) {
      return customWidth;
    }

    return $super.tileWidth.call(this);
  }

  tileHeight() {
    const customHeight = CycloneAdvancedMaps.params.tileHeight;
    if (typeof customHeight === 'number' && customHeight > 0) {
      return customHeight;
    }

    return $super.tileHeight.call(this);
  }

  isBush(x, y) {
    if ($super.isBush.call(this, x, y)) {
      return true;
    }

    const bushRegionId = CycloneAdvancedMaps.params.bushRegionId;
    if (!bushRegionId) {
      return false;
    }

    if (!this.isValid(x, y)) {
      return false;
    }

    return $gameMap.regionId(x, y) === bushRegionId;
  }

  checkRegionPassability(x, y) {
    const blockRegionId = CycloneAdvancedMaps.params.blockRegionId;
    const unblockRegionId = CycloneAdvancedMaps.params.unblockRegionId;

    if (blockRegionId > 0 || unblockRegionId > 0) {
      const regionId = this.regionId(x, y);

      if (regionId > 0) {
        if (regionId === blockRegionId) {
          return false;
        }

        if (regionId === unblockRegionId) {
          return true;
        }
      }
    }

    return null;
  }

  checkPassage(x, y, bit) {
    const region = this.checkRegionPassability(x, y);
    if (typeof region === 'boolean') {
      return region;
    }

    return $super.checkPassage.call(this, x, y, bit);
  }
});

CycloneAdvancedMaps.patchClass(Game_Party, $super => class {
  onPlayerWalk() {
    $super.onPlayerWalk.call(this);

    if (CycloneAdvancedMaps.commonEventRegions.size > 0) {
      CycloneAdvancedMaps.checkRegionActions();
    }
  }
});

CycloneAdvancedMaps.patchClass(Game_Player, $super => class {
  performTransfer() {
    if (this.isTransferring()) {
      if (CycloneAdvancedMaps.params.commonEventId > 0) {
        $gameTemp.reserveCommonEvent(CycloneAdvancedMaps.params.commonEventId);
      }
    }

    $super.performTransfer.call(this);
  }

  isMapPassable(x, y, d) {
    const blockRegionId = CycloneAdvancedMaps.params.blockPlayerRegionId;
    const unblockRegionId = CycloneAdvancedMaps.params.unblockPlayerRegionId;

    if (blockRegionId > 0 || unblockRegionId > 0) {
      const newX = $gameMap.roundXWithDirection(x, d);
      const newY = $gameMap.roundYWithDirection(y, d);
      const regionId = $gameMap.regionId(newX, newY);

      if (regionId > 0) {
        if (regionId === blockRegionId) {
          return false;
        }

        if (regionId === unblockRegionId) {
          return true;
        }
      }
    }

    return $super.isMapPassable.call(this, x, y, d);
  }
});

CycloneAdvancedMaps.patchClass(ImageManager, $super => class {
  static loadTileset(filename) {
    const customPath = CycloneAdvancedMaps.params.tilesetPath;
    if (customPath) {
      return this.loadBitmap(customPath, filename);
    }

    return $super.loadTileset.call(this, filename);
  }
});

class WindowRegionName extends Window_MapName {
  refresh() {
    this.contents.clear();

    const regionId = this._currentRegionId || 0;
    if (regionId === 0) {
      return;
    }

    const regionName = CycloneAdvancedMaps.namedRegions.get(regionId);
    if (!regionName) {
      return;
    }

    const width = this.contentsWidth();
    this.drawBackground(0, 0, width, this.lineHeight());
    this.drawText(regionName, 0, 0, width, 'center');
  }

  update() {
    if (this._delay) {
      this._delay--;
      return;
    }

    this._delay = 10;
    const regionId = $gameMap.regionId($gamePlayer._x, $gamePlayer._y);

    const shouldUpdate = regionId > 0 || !this._showCount;

    if (shouldUpdate && (CycloneAdvancedMaps.params.regionNamesStay || regionId !== this._currentRegionId)) {
      this._currentRegionId = regionId;
      this.open();
      return;
    }

    super.update();
  }
}

CycloneAdvancedMaps.patchClass(Scene_Map, $super => class {
  createRegionNameWindow() {
    const rect = this.mapNameWindowRect();
    this._regionNameWindow = new WindowRegionName(rect);
    this.addChild(this._regionNameWindow);
  }

  createMapNameWindow() {
    $super.createMapNameWindow.call(this);
    this.createRegionNameWindow();
  }

  updateTransferPlayer() {
    if ($gamePlayer.isTransferring()) {
      this._regionNameWindow.close();
    }

    $super.updateTransferPlayer();
    this._regionNameWindow._delay = 0;
    this._regionNameWindow.update();
  }

  callMenu() {
    $super.callMenu.call(this);
    this._regionNameWindow.hide();
  }

  launchBattle() {
    $super.launchBattle.call(this);
    this._regionNameWindow.hide();
  }

  stop() {
    this._regionNameWindow.close();
    $super.stop.call(this);
  }
});

CycloneAdvancedMaps.patchClass(Sprite_Animation, $super => class {
  initMembers() {
    $super.initMembers.call(this);
    const animationZ = CycloneAdvancedMaps.params.animationZ;

    // Only apply if we have a valid Z different from the default
    if (animationZ !== 0 && animationZ !== 8) {
      this.z = animationZ;
    }
  }
});

CycloneAdvancedMaps.patchClass(Sprite_Balloon, $super => class {
  initMembers() {
    $super.initMembers.call(this);
    const balloonZ = CycloneAdvancedMaps.params.balloonZ;

    // Only apply if we have a valid Z different from the default
    if (balloonZ !== 0 && balloonZ !== 7) {
      this.z = balloonZ;
    }
  }
});

CycloneAdvancedMaps.patchClass(Spriteset_Map, $super => class {
  getLayerFolderName(layerData) {
    switch (CycloneAdvancedMaps.params.folders) {
      case 'perLayer':
        return layerData.tagName;
      case 'perMap':
        return $gameMap._mapId ? String($gameMap._mapId) : '';
    }

    return '';
  }

  getLayerFileName(layerData) {
    const fileNamePrefix = layerData.fileName || '';
    const tagFileName = this.getMeta(layerData.tagName);
    const fileName = typeof tagFileName === 'string' ? tagFileName : '';

    const fileNameSuffix = layerData.appendMapId ? $gameMap._mapId : '';
    return `${fileNamePrefix}${fileName}${fileNameSuffix}`;
  }

  loadOverlayBitmap(layerData) {
    const bitmapPath = this.getLayerFileName(layerData);
    if (!bitmapPath) {
      return null;
    }

    CycloneAdvancedMaps.params.debug && console.log('Loading bitmap: ', bitmapPath);
    const overlayFolder = CycloneAdvancedMaps.params.overlayPath || 'img/overlays';
    const folderName = this.getLayerFolderName(layerData);
    const path = folderName ? `${overlayFolder}/${folderName}` : overlayFolder;

    return ImageManager.loadBitmap(`${path}/`, bitmapPath);
  }

  isLayerEnabled(layerData) {
    if (layerData.switchId > 0) {
      const switchValue = $gameSwitches.value(layerData.switchId);
      return layerData.invertSwitch ? !switchValue : switchValue;
    }

    return true;
  }

  isLayerAllowed(layerData, debug = false) {
    if (layerData.mapList?.includes($gameMap._mapId)) {
      debug && CycloneAdvancedMaps.params.debug && console.log(`Layer ${layerData.layerName || layerData.tagName} allowed by map list. (Map ${ $gameMap._mapId })`);
      return true;
    }

    if (layerData.tagName) {
      if ((this.getMeta(layerData.tagName) || this.getMeta('all'))) {
        debug && CycloneAdvancedMaps.params.debug && console.log(`Layer ${layerData.layerName || layerData.tagName} allowed by tag. (Map ${ $gameMap._mapId })`);
        return true;
      }

      debug && CycloneAdvancedMaps.params.debug && console.log(`Layer ${layerData.layerName || layerData.tagName} blocked by tag. (Map ${ $gameMap._mapId })`);
      return false;
    }

    if (!layerData.mapList?.length) {
      debug && CycloneAdvancedMaps.params.debug && console.log(`Layer ${layerData.layerName || layerData.tagName} allowed by lack of filters. (Map ${ $gameMap._mapId })`);
      return true;
    }

    debug && CycloneAdvancedMaps.params.debug && console.log(`Layer ${layerData.layerName || layerData.tagName} blocked by map list. (Map ${ $gameMap._mapId })`);
    return false;
  }

  createOverlayLayer(layerData) {
    if (!this.isLayerAllowed(layerData, true)) {
      return null;
    }

    const bitmap = this.loadOverlayBitmap(layerData);
    if (!bitmap) {
      return null;
    }

    const layer = layerData.position?.tiling ? new TilingSprite(bitmap) : new Sprite(bitmap);

    if (layerData.position?.tiling) {
      layer.width = Graphics.width;
      layer.height = Graphics.height;
    }

    layer.z = layerData.z || 0;

    this._tilemap.addChild(layer);

    if (layerData.fadeIn) {
      layer.opacity = 0;
    } else {
      layer.opacity = this.isLayerEnabled(layerData) ? (layerData.opacity || 255) : 0;
    }

    if (layerData.blendMode) {
      layer.blendMode = layerData.blendMode;
    }

    return layer;
  }

  createLowerOverlayLayers() {
    for (const layer of CycloneAdvancedMaps.layers) {
      if (layer.z > 1) {
        continue;
      }

      this[layer.id] = this.createOverlayLayer(layer);
    }
  }

  createUpperOverlayLayers() {
    for (const layer of CycloneAdvancedMaps.layers) {
      if (layer.z > 1) {
        this[layer.id] = this.createOverlayLayer(layer);
      }
    }
  }

  createCharacters() {
    if (!CycloneAdvancedMaps.params.overlayEnabled) {
      return $super.createCharacters.call(this);
    }

    this.createLowerOverlayLayers();
    $super.createCharacters.call(this);
    this.createUpperOverlayLayers();
  }

  getMeta(name) {
    if ($dataMap && $dataMap.meta) {
      return $dataMap.meta[name];
    }
  }

  getLayerPosition(layerData) {
    const boundToScreen = layerData.position?.boundTo === 'screen';

    const top = boundToScreen ? 0 : $gameMap.displayY() * (0 - $gameMap.tileHeight());
    const left = boundToScreen ? 0 : $gameMap.displayX() * (0 - $gameMap.tileWidth());
    const x = layerData.position?.x || 0;
    const y = layerData.position?.y || 0;

    if (layerData.position?.unit === 'pixels') {
      return [left + x, top + y];
    }

    return [
      left + x * $gameMap.tileWidth(),
      top + y * $gameMap.tileHeight(),
    ];
  }

  updateOverlayLayer(layerData) {
    let layer = this[layerData.id];

    const bitmap = layer?.bitmap || this.loadOverlayBitmap(layerData);
    if (!bitmap) {
      return;
    }

    if (!layer) {
      layer = this.createOverlayLayer(layerData);
      layerData.changed = false;
    }

    if (!layer) {
      return;
    }

    layerData.extraX -= layerData.position?.moveX || 0;
    layerData.extraY -= layerData.position?.moveY || 0;

    const [x, y] = this.getLayerPosition(layerData);
    if (layerData.position?.tiling) {
      if (layerData.position?.boundTo === 'screen') {
        layer.origin.x = $gameMap.displayX() * $gameMap.tileWidth() + layerData.extraX;
        layer.origin.y = $gameMap.displayY() * $gameMap.tileHeight() + layerData.extraY;
      } else {
        layer.origin.x = layerData.extraX;
        layer.origin.y = layerData.extraY;
      }
      layer.x = x;
      layer.y = y;
    } else {
      layer.x = x + layerData.extraX;
      layer.y = y + layerData.extraY;
    }

    this.updateLayerOpacity(layer, layerData);

    if (layerData.changed) {
      layer.bitmap = this.loadOverlayBitmap(layerData);
      layerData.changed = false;
    }

    this[layerData.id] = layer;
  }

  updateOverlayLayers() {
    for (const layer of CycloneAdvancedMaps.layers) {
      if (this.isLayerAllowed(layer, layer.changed)) {
        this.updateOverlayLayer(layer);
      }
    }
  }

  updateLayerOpacity(layer, layerData) {
    const opacity = this.isLayerEnabled(layerData) ? layerData.opacity ?? 255 : 0;
    if (layer.opacity === opacity) {
      return;
    }

    if (layerData.oneTimeOpacityDuration) {
      layerData.oneTimeOpacityChange = Math.ceil(Math.abs(layer.opacity - layerData.opacity) / layerData.oneTimeOpacityDuration);
      CycloneAdvancedMaps.params.debug && console.log('Single use opacity change calculated: ', layerData.oneTimeOpacityChange);
      layerData.oneTimeOpacityDuration = undefined;
    }
    const opacityChange = (layerData.oneTimeOpacityChange || layerData.opacityChange || 10) * (opacity > layer.opacity ? 1 : -1);

    // If the opacity is decreasing, the minimum is the target, otherwise it's 0
    const min = opacityChange > 0 ? 0 : opacity;
    // If the opacity is increasing, the maximum is the target, otherwise it's 255
    const max = opacityChange > 0 ? opacity : 255;

    const newOpacity = (layer.opacity + opacityChange).clamp(min, max);

    if (layer.opacity !== newOpacity) {
      layer.opacity = newOpacity;
    }

    if (newOpacity === opacity && layerData.oneTimeOpacityChange) {
      delete layerData.oneTimeOpacityChange;
    }
  }

  updateTilemap() {
    if (CycloneAdvancedMaps.params.overlayEnabled) {
      this.updateOverlayLayers();
    }

    $super.updateTilemap.call(this);
  }
});

CycloneAdvancedMaps.patchClass(Tilemap, $super => class {
  initialize() {
    $super.initialize.call(this);
    this._tileWidth = $gameMap.tileWidth();
    this._tileHeight = $gameMap.tileHeight();
  }

  updateTransform() {
    if (CycloneAdvancedMaps.params.disableTilemap) {
      this._sortChildren();
      PIXI.Container.prototype.updateTransform.call(this);
      return;
    }

    $super.updateTransform.call(this);
  }

  _addShadow(...args) {
    if (CycloneAdvancedMaps.params.disableAutoShadows) {
      return;
    }

    return $super._addShadow.call(this, ...args);
  }
});
})();
