OcRam MZ -plugins

● ARCHIVED · READ-ONLY
Started by OcRam 404 posts Page 11 of 21 View original ↗
  1. Hi @OcRam, another improvement suggestion for the Battle Core plugin: make the flight animation customizable depending on the enemy state. In the examples below, the problem is explicit: if the enemy has a "sleep" or "KO" state, the sprite changes so that the enemy is static... but the flight animation continues, which doesn't look very good.

    Sample
    Sleep State
    SleepState.gif

    KO State
    DeathState.gif

    I guess that by using the target.setFlying(alt, var) function in the troop pages, or with common events in the skills, we should be able to solve the problem. But ideally, I think, we should be able to determine in the enemies' notetags the different behaviors (e.g. <flying:96,4/2,0/0,0> = default 96,4, sleep 1,1, KO 0,0 => no flying... or something like that).

    What do you think about it?
  2. r66r said:
    Hi @OcRam, another improvement suggestion for the Battle Core plugin: make the flight animation customizable depending on the enemy state. In the examples below, the problem is explicit: if the enemy has a "sleep" or "KO" state, the sprite changes so that the enemy is static... but the flight animation continues, which doesn't look very good.

    Sample
    Sleep State
    View attachment 216365

    KO State
    View attachment 216366

    I guess that by using the target.setFlying(alt, var) function in the troop pages, or with common events in the skills, we should be able to solve the problem. But ideally, I think, we should be able to determine in the enemies' notetags the different behaviors (e.g. <flying:96,4/2,0/0,0> = default 96,4, sleep 1,1, KO 0,0 => no flying... or something like that).

    What do you think about it?
    Thank you for the idea!

    I think this is great idea and I'll try to implement this in next update.
  3. Is it possible to set time system so that some months have less days, like February?
  4. Tuomo L said:
    Is it possible to set time system so that some months have less days, like February?
    Hi,

    Only "real-time" feature has different amount of days per month.
  5. Bah! The new VisuaStella Lighting Effects nukes a few of the OcRam plugins.
    Probably because of how they use layers.
    Even the walkStairs script calls are dead.
  6. MasterTenchi said:
    Bah! The new VisuaStella Lighting Effects nukes a few of the OcRam plugins.
    Probably because of how they use layers.
    Even the walkStairs script calls are dead.
    Thank you for the info.
  7. Of course!
  8. Hello @OcRam

    I found that I'm not seeing the shadows tiles in the game (But I see them in the editor) and disabling the Star_Tile_Fix makes them appear again. Could it be a bug?

    (I tried only enabling core and star_tile_fix and the bug stills happens)

    Screenshots


    Is there any other info that I can provide that can be useful to find the problem?
    Besides that, the plugin works fine
    Thanks!!
  9. orphalese said:
    Hello @OcRam

    I found that I'm not seeing the shadows tiles in the game (But I see them in the editor) and disabling the Star_Tile_Fix makes them appear again. Could it be a bug?

    (I tried only enabling core and star_tile_fix and the bug stills happens)

    Screenshots


    Is there any other info that I can provide that can be useful to find the problem?
    Besides that, the plugin works fine
    Thanks!!

    Thank you for the report!

    It is a bug indeed, but for me it only happens on some maps?? I will fix this before next release!

    Edit: It seems that in some cases shadow tiles are considered as "_isHigherTile" by engine... I'm working on it :)

    Edit2 (as QUICK fix just replace orginal "_readMapData" inside OcRam_Star_Tile_Fix with this):
    Code:
        this.extend(Tilemap, "_readMapData", function (x, y, z) {
            if (z > 1 && z < 4) {
                const tmp = _this["Tilemap__readMapData"].apply(this, arguments);
                if (this._isHigherTile(tmp)) return 0; return tmp;
            } return _this["Tilemap__readMapData"].apply(this, arguments);
        });
  10. Whoa, finally the fixing light to parallax feature.

    Thank you!
  11. Hello fellow RPG Makers!

    Firstly I'd like to thank you all for the reports and ideas! Without your input OcRam -plugins wouldn't be what they are today! You guys and gals are awesome!

    :MV1:

    Please remember to backup your project before any updates. New plugin parameters requires re-validation, if you are already using older version of that plugin. Re-validation is done by clicking "OK" on each new parameter - or then re-install plugin to reset "factory" defaults.

    This update adds new plugin with various features ready to be used in your RPG Maker projects! Update also focuses on floor/state damages and deaths/revives while player(s) is/are on map.

    The new plugin OcRam_Misc has features such as: pixel perfect rendering, full screen control, on map damage and a lot of other miscellaneous features.

    Pixel perfect rendering vs. Default "smoothing"
    pixel_perfect_vs_default2.png

    On map damage example:
    on_map_damage.png

    And how about... those "zombie" followers? No worries OcRam_Followers now has "Dead followers are" plugin parameter which can be:
    • Default (aka. "Zombie" followers)
      /has zero impact on core functionality.
    • Ghosts (this will make dead followers 50% transparent)
      /has some aliases made and partial override on face draw.
    • Gone/Hidden (this will make them disappear and are forced to the end positions of formation)
      /has most effects in core functionality AND NOTE that dead followers are no longer playable until revived.
    If "Ghosts" or "Gone/Hidden" mode is used: battlers are GHOSTS when battle starts (they just observe - until revived). In addition this update includes "Ghosts interact" to prevent event interacting if player is ghost!

    OcRam_Local_Coop now iterates only characters that follow the player who takes floor / state damage on map! In addition dead players are now ghosts if OcRam_Followers "Dead followers are" == "Ghosts" or "Gone/Hidden" (for playability reasons).
    ps. RETRO got v0.11 update, if you wish to use these plugins in MV -project.

    There has been also tons of bug fixes and other new features - and I hope I have written every change to log inside spoiler below.

    More details inside this spoiler!
    Core v1.14
    • Fixed a bug if character sprite was deleted while called getSpriteByEventId(n) method!

    Battle_Core v1.03 - (no RETRO support yet)
    • Battler collapse on map bug fixed
    • Fixed bug when setFlying() didn't have battler sprite.
    • NEW <flying:Normal/Abnormal/Sleep/KO> custom flying height and fluctuation for each motion (Credits to @r66r )
    Battle_Troops v1.00 - (no RETRO support yet) - No changes

    Time_System v1.12 - No changes

    Weather_System v1.07
    • Now game system saves whole weather object as it is! (including support weathers and their parameters)
    • Lightining flashes works again after map terminate and cleaned some double definitions in code base
    Movement v1.09
    • increaseSteps && onPlayerWalk is now tilebased even if pseudo-pixel movement is used (before fix was x4 faster)
    • This also fixed a bug where states/floor damage didn't apply to followers on map (de-synched steps /w player)
    • "Event on touch" now works with OcRam_Events
    • "Diagonal hug" on wall won't bug followers anymore!
    • Different event will reset "stepsToTrigger" (pixel move)
    Events v1.07
    • Fixed crash bug when speaker name \N[x] was used...
    • "Event on touch" now works with OcRam_Movement
    • Fixed _currentEMotion bug if player had no actor assigned (Credits @TrueDekay )

    Followers v1.07
    • NEW plugin parameter "Dead followers are"!
    • NEW plugin parameter "Ghosts interact"
    • Compatibility fix for OcRam_Battle_Core v1.3
    • In Local_Coop: If dead followers are "Gone/Hidden" all PLAYERS who are dead - will be forced to ghosts.
    Audio v1.03 - No changes

    Lights v1.12 - No changes

    Layers v1.06
    • Title layers now resets x/y to 0 when re-created!
    Title_Shuffler v1.02
    • New feature - Splash screen (image + sound effect)! Also works as a small pre-loader for title screen! (very handy especially if title shuffler has a lot of different templates)
    Title_Info v1.00 - No changes

    Indicators v1.04
    • Fixed bug when event was not found for the indicator...
    • New plugin parameters: Disable all indicators while player is in menu- or battle scene? (Credits to @Raggon )
    Passages v1.14
    • Fixed bug if follower had no character sprite (Credits to @OMGerm for report and testing)
    • New plugin command "Set transfer floor" to make your life easier (no need for complex JS / eventing)
    NPC_Scheduler v1.03 - No changes

    Star_Tile_Fix v1.05
    • Shadow tiles are now drawn properly (Credits @orphalese )
    Local_Coop v1.05
    • Compatibility patch for OcRam_Followers "dead followers"
    • OcRam.Local_Coop.refreshPrecedingCharacters() + trigger that refresh on $gameParty.removeActor()
    • Floor/state damage now applies for each players sub party
    • NOTE: If followers don't follow (OcRam_Followers) each character takes damage individually!
    • OcRam.Local_Coop.getMaxPlayers() // Max no. of players
    • Max number of players can't exceed party size no longer.
    • NEW JS call OcRam.Local_Coop.allowJoin() or via plugin command. To allow/prevent player joining (this setting will be saved to Game_System object).
    • Info text (player join/left) Y-Offset in pixels

    Map_Transfer v1.02
    • OcRam_Weather_System is no longer needed by this plugin for scrolling map transfers (Credits @DLHubb )
    • 2022/01/24 v1.01 - *HOT FIX* Removed invalid plugin command meta

    Credits v1.00 - No changes

    OcRam_Misc - NEW plugin!

    This plugin is a collection of small functions. Such as:
    • Map turn steps
    • On map damage (damage floor/states/events)
    • Pixel perfect rendering on/off!
    • Start game on full screen? + JS calls for full screen on/off!
    • Show Exit Command + Exit caption
    • Since editor doesn't give option to set (and hide) general options...
    • "Always Dash" + option to show / hide it
    • "Remember Command" + option to show / hide it
    • "Touch UI" + option to show / hide it
    • Run notetag JS evals on (see notetags section):
      - Item: onAdd / onRemove / onUse
      - State: onAdd / onRemove / onTick
  12. How do I make it, so that after certain time, the time no longer automatically moves? Like in Harvest Moon, where after becoming night, the day was stuck forever into night unless you went to bed.
  13. Tuomo L said:
    How do I make it, so that after certain time, the time no longer automatically moves? Like in Harvest Moon, where after becoming night, the day was stuck forever into night unless you went to bed.
    Thank you for the message,

    "Time enabled switch" might be the one you are looking for. Time stops if it is false and goes on if it's true.
  14. Hello fellow RPG Makers!

    Now ALL OcRam MZ -plugins are RETRO'able to RPG Maker MV!

    Finally all differences between MV and MZ battle related code has been addressed. Thus today OcRam_Battle_Core and OcRam_Battle_Troops are available on MV too!

    RETRO'ed OcRam Battle:


    NOTE 1:
    If "Enhanced Damage Popups" is used it will override MV's way to create damage popups.
    In MZ damage popups are created from TEXT to BITMAP while MV takes them from BITMAP to BITMAP. TEXT to BITMAP (MZ style) allows us to create DYNAMICALLY "IMMUNE", "MISS", "EVADE", "RESISTS", "CRITICAL" etc... texts.

    NOTE 2:
    Using any OcRam MZ -plugin in MV requires at least RETRO.js version 0.11 in order to work. Import RETRO -plugin before any MZ -plugin
    and read instructions carefully! Also "RETRO Plugin Command UI" is strongly recommended to have MZ style plugin commands in MV.

    RETRO is made by awesome @Drakkonis !

    RETRO plugin: Thread page
    RETRO Plugin Command UI: Thread page

    ALL-IN-1 (~122Mb) package - includes everything (MV project)
    RETRO'ed OcRam Demo Project: Download here


    Battle_Core v1.04
    RETRO support for RPG Maker MV!
    Number font face, size, outline plugin parameters!
    Please note that if you use "Number font" feature in MZ it will override default Number Font...

    Battle_Troops v1.01
    RETRO support for RPG Maker MV!
  15. I’m sorry to report that the 1.5.0 update has complicated the Passages plugin. Regions aren’t functioning as intended and several ID’s will simply erase underlying tiles, specifically on the upper layers in MZ. I’ve not troubleshooted extensively but expect you’ll want to take a look.
  16. OMGerm said:
    I’m sorry to report that the 1.5.0 update has complicated the Passages plugin. Regions aren’t functioning as intended and several ID’s will simply erase underlying tiles, specifically on the upper layers in MZ. I’ve not troubleshooted extensively but expect you’ll want to take a look.
    Thank you for the report!

    I can confirm this. I will add support for RMMZ v1.5.0 to all of my plugins in next patch. I'm currently really busy and I can't give estimated release date yet (hopefully this month).

    EDIT: @OMGerm

    If you want to do OcRam_Passages QUICK fix:

    I had 5 mins to debug this and found that in Tilemap.prototype.drawAutotile_OC

    Fix this:
    Code:
    const w1 = this._tileWidth / 2;
    const h1 = this._tileHeight / 2;

    To this:
    Code:
    const w1 = this.tileWidth / 2;
    const h1 = this.tileHeight / 2;

    And it will work normally with RMMZ v1.5.0

    EDIT2: I will add also RETRO support for this in next update.
  17. OcRam said:
    Thank you for the report!

    I can confirm this. I will add support for RMMZ v1.5.0 to all of my plugins in next patch. I'm currently really busy and I can't give estimated release date yet (hopefully this month).

    EDIT: @OMGerm

    If you want to do OcRam_Passages QUICK fix:

    I had 5 mins to debug this and found that in Tilemap.prototype.drawAutotile_OC

    Fix this:
    Code:
    const w1 = this._tileWidth / 2;
    const h1 = this._tileHeight / 2;

    To this:
    Code:
    const w1 = this.tileWidth / 2;
    const h1 = this.tileHeight / 2;

    And it will work normally with RMMZ v1.5.0

    EDIT2: I will add also RETRO support for this in next update.
    While I was not having any issues with running the game, I am encountering graphical errors wherein tiles seem to be getting erased by region 18 and region 19, though it is not consistent. Layer 3 seems to be most problematic at first glance. I have edited the code as instructed as well without result. See here:

    Screen Shot 2022-06-02 at 11.39.54 AM.png

    And in-game:

    Screen Shot 2022-06-02 at 11.41.04 AM.png

    I understand you are very busy and I certainly don't expect attention to this until you find that you've got free time. Thank you as always!
  18. @OMGerm - It kinda looks like you're running 19 - AUTO TILE on a standard graphic slot... which might be causing the confusion. Try turning those 19's into 18's...
  19. MasterTenchi said:
    @OMGerm - It kinda looks like you're running 19 - AUTO TILE on a standard graphic slot... which might be causing the confusion. Try turning those 19's into 18's...
    I’ll give that a shot and edit this post. I’d also add that the setup pictured worked fine pre-update, then patched to 1.5.0 and now what you see pictured is the result. No other changes. I can also try a new project.

    EDIT: @MasterTenchi - Changing the 19's to 18's will further erase tiles on the upper layers. Changing to 18's from 19's will show the tile but function incorrectly. A new project has not resolved the issue. I suspect @OcRam will get this sorted in due time.

    EDIT 2: I'd also add that moving from a region 16 tile to a region 17 tile while "Use Tall Sprites" is enabled will erase all followers until moving to an adjacent region 16 tile. This appears to be the same issue as I have reported in the past. This is on a new project with only Core and Passages as plugins.
  20. OMGerm said:
    I’ll give that a shot and edit this post. I’d also add that the setup pictured worked fine pre-update, then patched to 1.5.0 and now what you see pictured is the result. No other changes. I can also try a new project.

    EDIT: @MasterTenchi - Changing the 19's to 18's will further erase tiles on the upper layers. Changing to 18's to 19's will show the tile but function incorrectly. A new project has not resolved the issue. I suspect @OcRam will get this sorted in due time.

    EDIT 2: I'd also add that moving from a region 16 tile to a region 17 tile while "Use Tall Sprites" is enabled will erase all followers until moving to an adjacent region 16 tile. This appears to be the same issue as I have reported in the past. This is on a new project with only Core and Passages as plugins.
    Ok I've now tested this (also with RETRO in MV) and it seems to work. Please let me know if this fixes mentioned issues. Please note that this is not yet officially released version.

    JavaScript:
    //-----------------------------------------------------------------------------
    // OcRam plugins - OcRam_Passages.js
    //=============================================================================
    
    "use strict"; if (!Imported || !Imported.OcRam_Core) alert('OcRam_Core.js ' +
        'is required!'); if (parseFloat(OcRam.version) < 1.14) alert("OcRam core v1.14 or greater is required!");
    
    OcRam.addPlugin("Passages", "1.15");
    
    /*:
     * @target MZ
     * @plugindesc v1.15 Use region IDs to determine a character 'floor level'.
     * Even autotiles can be drawn ABOVE characters.
     * @author OcRam
     * @url https://ocram-codes.net
     * @orderAfter OcRam_Layers
     * @orderAfter OcRam_Lights
     * @
     *
     * ----------------------------------------------------------------------------
     * PLUGIN COMMANDS
     * ============================================================================
     *
     * @command setFloorLevel
     * @text Set Floor Level
     * @desc Set desired floor level to desired game object.
     *
     * @arg objectId
     * @type number
     * @decimals 0
     * @min -255
     * @max 999999
     * @default -1
     * @text Object Id
     * @desc All positive numbers = event id. -1 = player + followers, -100 = boat, -101 = ship, -102 = airship.
     *
     * @arg floorLevel
     * @type select
     * @option Low
     * @value Low
     * @option High
     * @value High
     * @option Auto
     * @value Auto
     * @text Floor level
     * @default Auto
     * @desc To which floor level object is assigned?
     *
     * @command setTransferLevel
     * @text Set Transfer Level
     * @desc Set desired floor level where player should be transfered.
     *
     * @arg floorLevel
     * @type select
     * @option Low
     * @value 1
     * @option High
     * @value 2
     * @option Auto
     * @value 0
     * @text Floor level
     * @default 0
     * @desc Set desired floor level where player should be transfered.
     *
     * ----------------------------------------------------------------------------
     * PLUGIN PARAMETERS
     * ============================================================================
     *
     * @param Underpass Region ID
     * @type number
     * @desc Region ID for reducing character 'floor level' lower
     * @default 16
     *
     * @param Overpass Region ID
     * @type number
     * @desc Region ID for raising character 'floor level' higher. Also draws B-E tiles
     * @default 17
     *
     * @param Cover Region ID
     * @type number
     * @desc Region ID for "cover" such as bridges and other B-E tiles
     * @default 18
     *
     * @param Cover Autotile Region ID
     * @type number
     * @desc Region ID for "Autotile cover" such as cliffs, roofs and other A-tiles
     * @default 19
     *
     * @param Block Region ID
     * @type number
     * @desc Region ID to block movement from ALL floor levels
     * @default 20
     *
     * @param Overhead Region ID
     * @type number
     * @desc Region ID to block movement AND show tiles ABOVE character (if low ground)
     * @default 21
     *
     * @param Block High-Low Region ID
     * @type number
     * @desc Use to block movement from high <> low (must be next to underpass)
     * @default 22
     *
     * @param Use automatic floor levels
     * @type boolean
     * @desc 'true' Enable automatic floor level detection else disable function
     * @default true
     *
     * @param Use tall sprites
     * @type boolean
     * @desc 'true' = player and followers have same floor level.
     * 'false' = individual floor levels.
     * @default false
     *
     * @param Debug mode
     * @parent Other parameters
     * @type boolean
     * @desc Write some events to console log (F8 or F12).
     * @default false
     *
     * @help
     * ----------------------------------------------------------------------------
     * Introduction                  (Made for RPG Maker MZ + RETRO support for MV)
     * ============================================================================
     * This plugin uses region ID to determine a character 'floor level'. Even
     * autotiles can be drawn ABOVE characters (events, player, followers etc).
     *
     * Character passage is also allowed based on floor level (collision tests).
     *
     * Events are signed to desired 'floor level' via event comments <lower> or
     * <higher>. Event comments are processed per event PAGE! Or use "floor_level"
     * plugin command. By default: event interaction is allowed only within same
     * floor level. <trigger_always> will change this.
     *
     * NOTE:  Use DEBUG mode ONLY if you need to debug something. It's logging
     *        quite big JS objects and often ...so it might slow things down.
     *
     * When using "setTransferLevel" plugin command use it BEFORE transfer
     * command. After transfer "transfer level" is always set to "Auto"
     *
     * ----------------------------------------------------------------------------
     * Usage (Region IDs)
     * ============================================================================
     * For example horizontal bridge paint following regions (with default ids)
     *
     * 16 = Underpassage point, 17 = Overpassage point, 18 = Bridge (cover tile)
     *
     *      [16] [16] [16]
     * [17] [18] [18] [18] [17]
     *      [16] [16] [16]
     *
     * Possible event comments: <higher>, <lower>, <trigger_always>
     *
     * ----------------------------------------------------------------------------
     * Plugin commands
     * ============================================================================
     * objectId:
     *      -102 = Airship
     *      -101 = Ship
     *      -100 = Boat
     *      -2 to -99 = Followers
     *      -1 = Player (and followers)
     *      > 0 = Event id
     *
     * floorLevel:
     *      Low = Below cover tiles
     *      High = Above cover tiles
     *      Auto = Automatic assigment based on tile type where character is.
     *
     * MV example: OcRam_Passages/setFloorLevel -1 Low
     * setFloorLevel    To set player, event or vehicle "floor level"
     * >> objectId      See: "objectId" in the begining of this section.
     * >> floorLevel    To which floor level object is assigned?
     *
     * MV example: OcRam_Passages/setTransferLevel Low
     * setFloorLevel    To set player transfer "floor level"
     * >> floorLevel    To which floor level player will be transfered?
     *
     * ----------------------------------------------------------------------------
     * JavaScript commands
     * ============================================================================
     * OcRam.Passages.setFloorLevel($gamePlayer, 'low/high/auto'); // Set floor lvl
     * OcRam.Passages.initSprites(); // Force initialization of ALL sprites
     * character.setFloorLevel('low/high/auto'); // Set floor lvl
     *
     * ----------------------------------------------------------------------------
     * Terms of Use
     * ============================================================================
     * Edits are allowed as long as "Terms of Use" is not changed in any way.
     * Exception: Obfuscating and/or minifying JS, where ALL comments are removed
     * (incluging these "Terms of Use"), is allowed (won't change ToU itself).
     *
     * NON-COMMERCIAL USE: Free to use with credits to 'OcRam'
     *
     * If you gain money with your project by ANY MEANS (including: donations,
     * crypto-mining, micro-transactions, advertisements, merchandises etc..)
     * it's considered as COMMERCIAL use of this plugin!
     *
     * COMMERCIAL USE: (Standard license: 20 EUR, No-credits license: 60 EUR)
     * Payment via PayPal (https://paypal.me/MarkoPaakkunainen), please mention
     * PLUGIN NAME(S) you are buying / ALL plugins and your PROJECT NAME(S).
     *
     * Licenses are purchased per project and standard licenses requires credits.
     * ALL of my plugins for 1 project = 40 EUR (standard licenses).
     *
     * License for lifetime is 3x base price of any license / bundle. Permission
     * to use this type of license only in projects where you own most of it.
     * Else project license OR project owner lifetime license is required.
     *
     * OcRam -plugins available at https://ocram-codes.net/plugins.aspx?engine=mz
     *
     * DO NOT COPY, RESELL OR CLAIM ANY PIECE OF THIS PLUGIN AS YOUR OWN!
     * Copyright (c) 2021, Marko Paakkunainen // mmp_81 (at) hotmail.com
     *
     * ----------------------------------------------------------------------------
     * Version History
     * ============================================================================
     * 2020/09/03 v1.00 - Initial release for v1.00
     * 2020/09/19 v1.01 - Autosave won't mess up previous maps event floor levels
     * 2020/09/27 v1.02 - Compatibility patch for OcRam_Layers
     *                    + Tileset change bug fixes
     * 2020/10/10 v1.03 - Compatibility patch for OcRam_Movement
     * 2020/10/25 v1.04 - 2. compatibility patch for OcRam_Movement + Followers
     * 2020/10/28 v1.05 - Fixed rare bug when $gamePlayer and 2 events with
     *                    different floor levels in same tile caused stack overflow
     * 2021/01/06 v1.06 - Compatibility patch for VisuMZ_4_EncounterEffects
     *                    (Credits to Kneeshaw (also for the Visu plugin license))
     *                    New JS method OcRam.Passages.initSprites() in case you
     *                    need to refresh floor levels for every object.
     *                    (Credits to poorrabit)
     *                    Optimized a little bit event update method!
     * 2021/01/26 v1.07 - Compatibility patch for MOG_CharShatterEffect
     *                    (Credits to MasterTenchi)
     * 2021/03/07 v1.08 - Fixed a bug where there was a slight chance for "_flags"
     *                    to be null (_flags can be "empty" but NOT null)
     *                    <higher> and <lower> notetags are now checked for EACH
     *                    page individually and will override any floor level +
     *                    auto-assign will ne performed IF tag not found!
     *                    Balloons/Animations are now shown ABOVE cover tiles!
     *                    "Higher level" sprites are now only created when needed
     *                    and will be purged when changed to "lower level".
     *                    character.autoAssign() -method added (Credits: clitvin)
     *                    Optimized isMapPassable/checkPassage (Credits: clitvin)
     *                    All known character "flickering" issues has been fixed.
     *                    Support for LOOPPED MAPS! + Optimized mapCulling method!
     * 2021/04/02 v1.09 - Compatibility patch for OcRam_Events lift & throw system!
     *                    If character is on underpass region id > autoAssign = low
     *                    Game_Character.setFloorLevel('high'/'low'/'auto');
     * 2021/06/04 v1.10 - RETRO'ed for RMMV! (Credits to Drakkonis)
     *                    startMapEvent doesn't any more start "Below character"
     *                    events if they are on NON-passable tile.
     *                    1px glitch above "higher level" sprites is now fixed.
     * 2021/10/21 v1.11 - Plugin meta data (order/base) is now editor compatible!
     * 2021/12/01 v1.12 - Fixed bug when airship was higher level even if placed
     *                    on "underpass" region id (16 by default).
     * 2022/01/23 v1.13 - OcRam.Passage.doCulling() // Performs culling NOW
     *                    initSprites() can be used now only twice per second.
     *                    + Seamless map transfer with OcRam_Map_Transfer!
     *                    Airship floor level autoassign now works as intended.
     *                    Loopped maps are now working properly.
     *                    Menu while in airship now works properly!
     * 2022/04/22 v1.14 - Fixed bug if follower had no character sprite
     *                    (Credits to OMGerm for report and testing)
     *                    New plugin command "Set transfer floor" to make your
     *                    life easier (no need for complex JS / eventing)
     * 2022/??/?? v1.15 - Added support for RMMZ v1.5.0 + RETRO compatibility
     *                    for this change. (Credits to OMGerm for report)
     *
     * ----------------------------------------------------------------------------
     * RMMV CORE function overrides (destructive) are listed here
     * ============================================================================
     *     Game_Map.prototype.isMapPassable
     *     Game_Map.prototype.isPassable
     *     Game_Map.prototype.checkPassage
     *     Game_Player.prototype.startMapEvent
     *     Game_CharacterBase.prototype.isCollidedWithEvents
     *     Game_Event.prototype.isCollidedWithEvents
     *     Game_Vehicle.prototype.refreshBushDepth
     *     Tilemap.prototype._readMapData
     *     Game_CharacterBase.prototype.isCollidedWithEvents_OC (IF OcRam_Movement)
     *     Sprite_Balloon.updatePosition
     *     Sprite_AnimationMV.updatePosition
     *     Game_Player.prototype.checkThrowPass (If OcRam_Events is imported)
     */
    
    Sprite.prototype.isOcRam = () => { return false; }; // Fix for the Visu_4_EncounterEffects
    
    OcRam.hackSC_LooppedMaps = sc_oc => { }; // This will remove lot of iffing every frame IF not needed ;d
    
    // New class from Sprite_Character to make some adjustments... To reduce code base A LOT
    class Sprite_Character_OC extends Sprite_Character {
    
        constructor(character) {
            super(character); //this._character._adjX = 0; this._character._adjY = 0;
        }
    
        update () {
            if (this._showHigherSprite) {
                this.visible = true; super.update();
            } else {
                if (this.visible) {
                    this.visible = false; super.update();
                }
            }
        }
    
        isOcRam () { return true; }
        
        updatePosition() { // Removed + _adjX && + _adjY from calculations (couldn't find out what they were added for...)
            if (!this.visible) { this.x = -100; this.y = -100; return; }
            this.x = (this._character._realX * OcRam.twh[0] + OcRam.twh50[0]);
            this.y = ((((this._character._realY * OcRam.twh[1] + OcRam.twh[1] - this._character.jumpHeight() -
                (!this._character._isObjectCharacter ? 6 : 0)) | 0) + (this._character._altitude ? -this._character._altitude : 0)));
            OcRam.hackSC_LooppedMaps(this); this.z = this._character.screenZ();
        }
    
    }
    
    // Used in OcRam_Time_System (preloader)...
    ImageManager.loadTileset_OC = function (filename) {
        return this.loadBitmap('img/tilesets/', filename, 0, true);
    };
    
    (function () {
    
        // ------------------------------------------------------------------------------
        // Plugin variables and parameters
        // ==============================================================================
        const _this = this; // Refers to this plugin - To be used in subscopes...
    
        const _underpassId = Number(this.parameters['Underpass Region ID'] || 16);
        const _overpassId = Number(this.parameters['Overpass Region ID'] || 17);
        const _coverId = Number(this.parameters['Cover Region ID'] || 18);
        const _autoCoverId = Number(this.parameters['Cover Autotile Region ID'] || 19);
        const _blockId = Number(this.parameters['Block Region ID'] || 20);
        const _overheadId = Number(this.parameters['Overhead Region ID'] || 21);
        const _blockHighLowId = Number(this.parameters['Block High-Low Region ID'] || 22);
        const _useAutoassign = OcRam.getBoolean(this.parameters['Use automatic floor levels']);
        const _useTallSprites = OcRam.getBoolean(this.parameters['Use tall sprites']);
        const _tableEdgeVirtualId = 10000;
    
        let _isAirshipLanded = true; // AirShip Sprite landed?
        let _flags = []; // Game_Map flags needs to be loaded only once...
        let _currentShaderTilemap = null; // Used to get shadertilemap bitmaps
        let _cacheReady = false; // Check that cache is ready
        let _tileSprites = []; // Static tiles here (to avoid Y sorting)
        let _initDone = false; // Prevents init spamming
        let _shiftedX = false; let _shiftedY = false;
        let _transferLevel = 0;
    
        let _useTimeSystem = OcRam.Time_System && OcRam.getFloat(OcRam.Time_System.version) > 1.02;
        if (!_useTimeSystem) {
            this.debug("Create fake OcRam.Time_System object (for tileset changes).");
            OcRam.Time_System = {};
            OcRam.Time_System._currentTilesetId = 0;
            OcRam.Time_System._prevTilesetId = 0;
        }
    
        // Debuging purposes
        this.getPassageTilesA = () => {
            return _tileSprites[0];
        }; this.getPassageTilesBE = () => {
            return _tileSprites[1];
        }; this.getPassagesLayer = () => {
            if (!SceneManager || !SceneManager._scene || !SceneManager._scene._spriteset) return;
            return SceneManager._scene._spriteset._layerContainer_OC;
        }; this.totalNumberOfCoverSprites = () => {
            let sum = 0; if (SceneManager && SceneManager._scene && SceneManager._scene._spriteset && SceneManager._scene._spriteset._layerContainer_OC) sum += SceneManager._scene._spriteset._layerContainer_OC.children.length - 2;
            if (_tileSprites[0] && _tileSprites[0].children) sum += _tileSprites[0] && _tileSprites[0].children.length; if (_tileSprites[1] && _tileSprites[1].children) sum += _tileSprites[1] && _tileSprites[1].children.length; return sum;
        }
    
        // ------------------------------------------------------------------------------
        // Private Utility functions - Inherited to all sub scopes here
        // ==============================================================================
        const autoAssignFloorLevel = ev => {
    
            if (!$gameMap.tileset().mode) {
                this.debug("autoAssignFloorLevel [OVERWORLD!] (_higherLevel=" + false + ")", ev);
                return false;
            }
    
            if (ev._higherLevel != undefined) return ev._higherLevel;
            if (!_useAutoassign) { return ev._higherLevel || false; }
    
            const tiles = $gameMap.allTiles(ev._x, ev._y); let tile_id = 0;
            for (let i = 0; i < tiles.length; i++) {
                if (Tilemap.isAutotile(tiles[i])) tile_id = tiles[i];
            }
    
            let ret = false;
            switch (ev.regionId()) {
                case _underpassId: ret = false; break;
                case _coverId: case _autoCoverId: case _overpassId: case _overheadId: ret = true; break;
                default: ret = (Tilemap.isRoofTile(tile_id) || Tilemap.isWallTopTile(tile_id)); break;
            } this.debug("autoAssignFloorLevel (_higherLevel=" + ret + ")", ev); return ret;
    
        };
    
        const purgeHigherLevelSprite = obj => {
            let sprite = getCharSprite(obj); if (sprite)
                SceneManager._scene._spriteset._layerContainer_OC.removeChild(sprite); sprite = null;
        };
    
        const setObjFloorLevel = (obj, level) => {
            if (obj !== undefined && obj != null) {
                let sprite = getCharSprite(this); if (sprite)
                    SceneManager._scene._spriteset._layerContainer_OC.removeChild(sprite); sprite = null;
                if (level == "low") obj._higherLevel = false;
                if (level == "high") obj._higherLevel = true;
                if (level == "auto") obj._higherLevel = autoAssignFloorLevel(obj);
                updateEvent(obj);
            }
        };
    
        const preLoadSeasonTilesets = () => {
            let tmp = [];
            $dataTilesets.forEach(ts => {
                if (ts != null) {
                    tmp = [];
                    ts.tilesetNames.forEach(s => {
                        tmp.push(ImageManager.loadTileset_OC(s));
                    });
                }
            }); isCacheReady();
        };
    
        const isCacheReady = () => {
            window.setTimeout(() => {
                _cacheReady = ImageManager.isReady();
                if (!_cacheReady) {
                    this.debug("preLoadSeasonTilesets()", "...Loading...");
                    isCacheReady();
                } else {
                    this.debug("preLoadSeasonTilesets()", "...Done!");
                }
            }, 1000);
        };
    
        const callSeasonChange = () => {
    
            if (SceneManager._scene.isTitle() || DataManager.isEventTest()) return;
    
            // Do not change tileset if not required to...
            this.debug("Tileset check:", OcRam.Time_System._prevTilesetId + " -VS- " + OcRam.Time_System._currentTilesetId);
            if (OcRam.Time_System._prevTilesetId == OcRam.Time_System._currentTilesetId) return;
    
            _currentShaderTilemap = SceneManager._scene._spriteset._tilemap; // Current ShaderTilemap
    
            let tileset_names = $dataTilesets[OcRam.Time_System._currentTilesetId].tilesetNames;
            this.debug("LOADED NEW TILESET!", tileset_names); // Force new tileset bitmaps...
    
            OcRam.Time_System._currentTilesetId = OcRam.Time_System._prevTilesetId;
    
            for (let i = 0; i < tileset_names.length - 1; i++) {
                _currentShaderTilemap._bitmaps[i] = ImageManager.loadTileset_OC(tileset_names[i]);
            }
    
        };
    
        const reStartScene = () => {
            if (!OcRam.scene()) return;
            if (OcRam.scene().isReady()) {
                requestAnimationFrame(() => {
                    OcRam.scene().start();
                });
            } else {
                requestAnimationFrame(() => {
                    reStartScene();
                });
            }
        };
    
        const initSprites = () => {
    
            if (_initDone) return;
    
            _initDone = true;
            setTimeout(() => { _initDone = false; }, 500);
    
            OcRam.twh = [$gameMap.tileWidth(), $gameMap.tileHeight()];
    
            this.debug("Do culling?", OcRam._doCulling);
    
            // Initialize bitmap arrays on autotile covers...
            _flags = $gameMap.tilesetFlags();
    
            let ev_cmts = [];
    
            $gameSystem.loadPassageData(); callSeasonChange();
    
            let is_auto_or_overhead = false; let region_id = 0;
            const lw = $gameMap.width(); const lh = $gameMap.height();;
            for (let x = 0; x < lw; x++) {
                for (let y = 0; y < lh; y++) {
                    region_id = $gameMap.regionId(x, y); is_auto_or_overhead = (region_id == _autoCoverId || region_id == _overheadId);
                    if (is_auto_or_overhead) {
                        drawLowerLayers(x, y); // Autotiles covers WHOLE tile
                    } if (region_id == _coverId || is_auto_or_overhead || region_id == _overpassId) {
                        drawBELayers(x, y); // Characters and B-E tiles may have transparent backgrounds
                    }
                }
            }
    
            $gameMap.events().forEach(ev => {
                if (ev._higherLevel === undefined) {
                    ev._higherLevel = autoAssignFloorLevel(ev);
                    ev_cmts = ev.getStringComments();
                    ev_cmts.forEach(s => {
                        if (s == "<higher>") {
                            ev._higherLevel = true;
                        } else if (s == "<lower>") {
                            ev._higherLevel = false;
                        }
                    });
                } if (ev._higherLevel === undefined) ev._higherLevel = autoAssignFloorLevel(ev);
                if (ev._higherLevel) { updateEvent(ev); }
            });
    
            const aship_obj = $gameMap.airship();
            if (aship_obj._mapId == $gameMap._mapId) {
                aship_obj._higherLevel = undefined;
                if ($gamePlayer._vehicleType == "airship") {
                    aship_obj._higherLevel = true;
                    setObjFloorLevel(aship_obj, "high");
                    updateEvent(aship_obj);
                } else {
                    aship_obj._higherLevel = autoAssignFloorLevel(aship_obj);
                    setObjFloorLevel(aship_obj, aship_obj._higherLevel ? 'high' : 'low');
                    updateEvent(aship_obj);
                }
            } drawActors();
    
            mapCulling(); $gameMap.moveSpritesX_OC(); $gameMap.moveSpritesY_OC();
    
        };
    
        const drawLowerLayers = (px, py) => {
            let low_bm = new Bitmap(OcRam.twh[0], OcRam.twh[1]);
            let ctm = SceneManager._scene._spriteset._tilemap;
            ctm.paintTilesOnBitmap_OC(low_bm, null, px, py, true);
            addTileBitmap(low_bm, px * OcRam.twh[0], py * OcRam.twh[1], 0);
        };
    
        const drawActors = () => {
            // Pre-load actor higherlevel sprites to avoid minor lag when triggered 'higher' level...
            let ev = null; let old_hl = false;
            for (let i = $gamePlayer._followers.visibleFollowers().length - 1; i > -1; i--) {
                ev = $gamePlayer._followers.visibleFollowers()[i];
                old_hl = ev._higherLevel; ev._higherLevel = true; addCharBitmap(ev); ev._higherLevel = old_hl;
                ev._higherLevel = !_transferLevel ? autoAssignFloorLevel(ev) : _transferLevel == 1 ? false : true; updateEvent(ev);
            } ev = $gamePlayer; old_hl = ev._higherLevel; ev._higherLevel = true; addCharBitmap(ev); ev._higherLevel = old_hl;
            ev._higherLevel = !_transferLevel ? autoAssignFloorLevel(ev) : _transferLevel == 1 ? false : true; updateEvent(ev);
        };
    
        // Set visibilities to avoid sprite rendering/sorting for sprites that are off screen!
        const mapCulling = () => {
    
            if (!OcRam._doCulling) return;
    
            if (_tileSprites[0]) {
                _tileSprites[0].children.forEach(s => {
                    if (s.visible) { // Is it off screen now?
                        if (!OcRam.isInScreen(s._x, s._y)) s.visible = false;
                    } else { // Is it in screen now?
                        if (OcRam.isInScreen(s._x, s._y)) s.visible = true;
                    }
                });
            }
    
            if (_tileSprites[1]) {
                _tileSprites[1].children.forEach(s => {
                    if (s.visible) { // Is it off screen now?
                        if (!OcRam.isInScreen(s._x, s._y)) s.visible = false;
                    } else { // Is it in screen now?
                        if (OcRam.isInScreen(s._x, s._y)) s.visible = true;
                    }
                });
            }
    
        };
    
        // Draw tile to desired layer
        const addTileBitmap = (p_bitmap, x, y, z) => {
    
            let sprite = new Sprite(); sprite.bitmap = p_bitmap;
    
            sprite.x = x; sprite.y = y;
            sprite._x = (x / OcRam.twh[0]) | 0;
            sprite._y = (y / OcRam.twh[1]) | 0;
            sprite.z = z; sprite.visible = !OcRam._doCulling;
    
            if (z == 0) {
                _tileSprites[0].addChild(sprite); // Lower tiles
            } else {
                _tileSprites[1].addChild(sprite); // Upper tiles
            }
    
        };
    
        // Add char to _layerContainer_OC
        const addCharBitmap = ev => {
            if (ev._eventId !== undefined) {
                if (ev._characterName == "" && ev._tileId == 0) { this.debug("Event with no graphics...", ev); return; }
                if (!SceneManager._scene._spriteset || !SceneManager._scene._spriteset._layerContainer_OC) return;
                let sprite = new Sprite_Character_OC(ev); sprite.z = ev._priorityType * 2 + 1; sprite.visible = !OcRam._doCulling;
                SceneManager._scene._spriteset._layerContainer_OC.addChild(sprite); updateEvent(ev);
            }
        };
    
        // Update event sprite
        const updateEvent = ev => {
            if (ev._eventId == undefined) {
                if (ev.isPlayer()) ev._eventId = -1;
                if (ev.isFollower()) ev._eventId = -(ev._memberIndex + 1);
                if (ev.isVehicle()) {
                    if (ev._type == "boat") { ev._eventId = -100; ev._higherLevel = false; }
                    if (ev._type == "ship") { ev._eventId = -101; ev._higherLevel = false; }
                    if (ev._type == "airship") { ev._eventId = -102; }
                }
            } let sprite = getCharSprite(ev);
            if (ev._higherLevel) {
                if (sprite) {
                    sprite._showHigherSprite = ev._higherLevel && !ev._transparent;
                } else { // Event not created yet?
                    addCharBitmap(ev);
                }
            } else {
                if (sprite) requestAnimationFrame(() => { // 3 frames should be enough to avoid flickering...
                    requestAnimationFrame(() => { // 1 more after this one...
                        requestAnimationFrame(() => { purgeHigherLevelSprite(ev); });
                    });
                });
            }
        };
    
        const getCharSprite = ev => {
            if (!ev || !SceneManager._scene._spriteset || !SceneManager._scene._spriteset._layerContainer_OC) return null;
            return SceneManager._scene._spriteset._layerContainer_OC.children.find(sprite => {
                if (sprite.isOcRam() && sprite._character) {
                    if (sprite._character._eventId == ev._eventId) return true;
                }
            });
        };
    
        const drawBELayers = (px, py) => { // Draw B-E tiles - Returns: Bitmap
            let tmp_x = px * OcRam.twh[0]; let tmp_y = py * OcRam.twh[1];
            let low_bm = new Bitmap(OcRam.twh[0], OcRam.twh[1]);
            let high_bm = new Bitmap(OcRam.twh[0], OcRam.twh[1]);
            const ctm = SceneManager._scene._spriteset._tilemap;
            ctm.paintTilesOnBitmap_OC(low_bm, high_bm, px, py, false);
            addTileBitmap(low_bm, tmp_x, tmp_y, 0, px, py); // Add below chars
            addTileBitmap(high_bm, tmp_x, tmp_y, 4, px, py); // Add above chars
        };
    
        const shiftScreenPassagesX = () => {
            if (_shiftedX) {
                _this.debug("SHOULD REVERT SPRITES TO ORGINAL X!");
                let tiles = _tileSprites[0].children.filter(t => { // A tiles
                    return t.x > $gameMap._pixelWidth;
                }); tiles.forEach(t => {
                    t.x -= ($gameMap._pixelWidth);
                }); tiles = _tileSprites[1].children.filter(t => { // B-E tiles
                    return t.x > $gameMap._pixelWidth;
                }); tiles.forEach(t => {
                    t.x -= ($gameMap._pixelWidth);
                }); _shiftedX = false;
            } else {
                _this.debug("SHOULD SHIFT SPRITES TO NEW DISP X!");
                let tiles = _tileSprites[0].children.filter(t => {
                    return t.x < Graphics.width * 0.75;
                }); tiles.forEach(t => {
                    t.x += ($gameMap._pixelWidth);
                }); tiles = _tileSprites[1].children.filter(t => {
                    return t.x < Graphics.width * 0.75;
                }); tiles.forEach(t => {
                    t.x += ($gameMap._pixelWidth);
                }); _shiftedX = true;
            }
        };
    
        const shiftScreenPassagesY = () => {
            if (_shiftedY) {
                _this.debug("SHOULD REVERT SPRITES TO ORGINAL Y!");
                let tiles = _tileSprites[0].children.filter(t => { // A tiles
                    return t.y > $gameMap._pixelHeight;
                }); tiles.forEach(t => {
                    t.y -= ($gameMap._pixelHeight);
                }); tiles = _tileSprites[1].children.filter(t => { // B-E tiles
                    return t.y > $gameMap._pixelHeight;
                }); tiles.forEach(t => {
                    t.y -= ($gameMap._pixelHeight);
                }); _shiftedY = false;
            } else {
                _this.debug("SHOULD SHIFT SPRITES TO NEW DISP Y!");
                let tiles = _tileSprites[0].children.filter(t => {
                    return t.y < Graphics.height * 0.75;
                }); tiles.forEach(t => {
                    t.y += ($gameMap._pixelHeight);
                }); tiles = _tileSprites[1].children.filter(t => {
                    return t.y < Graphics.height * 0.75;
                }); tiles.forEach(t => {
                    t.y += ($gameMap._pixelHeight);
                }); _shiftedY = true;
            }
        };
    
        // ------------------------------------------------------------------------------
        // Public plugin functions - Usage: OcRam.PluginName.myFunction(arguments)
        // ==============================================================================
        this.setFloorLevel = (game_obj, str_level) => {
            setObjFloorLevel(game_obj, str_level);
        }; this.initSprites = () => { initSprites(); };
        this.reStartScene = () => { reStartScene(); };
        this.doCulling = () => { mapCulling();};
        this.setTransferLevel = level => { _transferLevel = level; };
    
        // ------------------------------------------------------------------------------
        // MOG_CharShatter fix
        // ==============================================================================
        if (Imported.MOG_CharShatterEffect) {
            const _MOG_CharShatterEffect_Fix = Sprite_Character.prototype.loadShatterData;
            Sprite_Character.prototype.loadShatterData = function (i) {
                if (this._character._shatter[1][i].opacity === undefined) {
                    this.saveShatterData(); $gameTemp._forceSkipShatter = true;
                } else {
                    _MOG_CharShatterEffect_Fix.call(this, i);
                }
            };
        }
    
        // ------------------------------------------------------------------------------
        // New methods
        // ==============================================================================
    
        // Force autoassign...
        Game_CharacterBase.prototype.autoAssign = function() {
            this._higherLevel = undefined;
            this._higherLevel = autoAssignFloorLevel(this);
            updateEvent(this);
        };
    
        // This is triggered every frame!
        Tilemap.prototype._sortChildren_OC = function () {
            SceneManager._scene._spriteset._layerContainer_OC.children.sort(this._compareChildOrder_OC.bind(this));
        };
    
        // This is triggered every frame!
        Tilemap.prototype._compareChildOrder_OC = function (a, b) {
            if (a.z !== b.z) return a.z - b.z;
            if (a.y !== b.y) return a.y - b.y;
            return a.spriteId - b.spriteId;
        };
    
        // Save passage data (high / low)
        Game_System.prototype.savePassageData = function () {
    
            this._passageHigh = []; this._passageLow = []; const fl = OcRam.followers().length;
    
            if ($gamePlayer._higherLevel) { this._passageHigh.push(-1); } else { this._passageLow.push(-1); }
            if (fl > 0 && OcRam.getGameObject(-2)._higherLevel) { this._passageHigh.push(-2); } else { this._passageLow.push(-2); }
            if (fl > 1 && OcRam.getGameObject(-3)._higherLevel) { this._passageHigh.push(-3); } else { this._passageLow.push(-3); }
            if (fl > 2 && OcRam.getGameObject(-4)._higherLevel) { this._passageHigh.push(-4); } else { this._passageLow.push(-4); }
            if (OcRam.getGameObject(-100)._higherLevel) { this._passageHigh.push(-100); } else { this._passageLow.push(-100); }
            if (OcRam.getGameObject(-101)._higherLevel) { this._passageHigh.push(-101); } else { this._passageLow.push(-101); }
            if (OcRam.getGameObject(-102)._higherLevel) { this._passageHigh.push(-102); } else { this._passageLow.push(-102); }
    
            $gameMap.events().forEach(ev => {
                if (ev._higherLevel) {
                    this._passageHigh.push(ev.eventId());
                } else { this._passageLow.push(ev.eventId()); }
            }); _this.debug("savePassageData >>> Saved _passageHigh:", this._passageHigh);
    
            _this.debug("savePassageData >>> Saved _passageLow:", this._passageLow);
    
        };
    
        // Load previously saved passage data (high / low)
        Game_System.prototype.loadPassageData = function () {
    
            let gobj = null;
    
            if (this._passageHigh != undefined) {
                if (this._passageHigh.length > 0) {
                    _this.debug("Loaded _passageHigh:", this._passageHigh);
                    this._passageHigh.forEach(i => {
                        gobj = OcRam.getGameObject(i); if (gobj) gobj._higherLevel = true;
                    }); this._passageHigh = [];
                }
            }
    
            if (this._passageLow != undefined) {
                if (this._passageLow.length > 0) {
                    _this.debug("Loaded _passageLow:", this._passageLow);
                    this._passageLow.forEach(i => {
                        gobj = OcRam.getGameObject(i); if (gobj) gobj._higherLevel = false;
                    }); this._passageLow = [];
                }
            }
    
        };
    
        Game_Map.prototype.moveSpritesX_OC = function () {
            SceneManager._scene._spriteset._layerContainer_OC.x = -(this._displayX * OcRam.twh[0]) | 0;
        };
    
        Game_Map.prototype.moveSpritesY_OC = function () {
            SceneManager._scene._spriteset._layerContainer_OC.y = -(this._displayY * OcRam.twh[1]) | 0;
        };
    
        // Create layers for cover graphics
        Spriteset_Map.prototype.createCoverLayers_OC = function () {
    
            // Clear layer container, just in case (you never know what other plugins do in this.terminate)
            if (this._layerContainer_OC !== undefined) {
                alert("OMG! Had to clear _layerContainer_OC >> This shouldn't happen!\nPlease report this message to passages thread!");
                this._tilemap.removeChild(this._layerContainer_OC);
            }
    
            _tileSprites = []; // Init tile sprite containers
            this._layerContainer_OC = new Sprite(); // Layer wrapper
            this._layerContainer_OC.z = 9999 + 1;
            let s = new Sprite(); s.z = 0; _tileSprites.push(s);
            this._layerContainer_OC.addChild(_tileSprites[0]);
            s = new Sprite(); s.z = 4; _tileSprites.push(s);
            this._layerContainer_OC.addChild(_tileSprites[1]);
            this._tilemap.addChild(this._layerContainer_OC);
    
        };
    
        // These methods will force system to draw tiles on bitmap (cover tiles)
        if (OcRam.Layers) {
            if (OcRam.Layers.parallaxOptimization()) {
                this.debug("OcRam.Layers parallax optimization detected", "DISABLE tile drawing totally!");
                Tilemap.prototype.drawTileToBitmap_OC = function () { return; };
            } else {
                this.debug("OcRam.Layers detected", "DISABLE passage tile drawing for <parallax> tilesets/maps!");
                Tilemap.prototype.drawTileToBitmap_OC = function (bitmap, tileId, dx, dy) {
                    if (OcRam.Layers._usingParallax) return;
                    if (Tilemap.isVisibleTile(tileId)) {
                        if (Tilemap.isAutotile(tileId)) {
                            this.drawAutotile_OC(bitmap, tileId, dx, dy);
                        } else {
                            this.drawNormalTile_OC(bitmap, tileId, dx, dy);
                        }
                    }
                };
            }
        } else {
            Tilemap.prototype.drawTileToBitmap_OC = function (bitmap, tileId, dx, dy) {
                if (Tilemap.isVisibleTile(tileId)) {
                    if (Tilemap.isAutotile(tileId)) {
                        this.drawAutotile_OC(bitmap, tileId, dx, dy);
                    } else {
                        this.drawNormalTile_OC(bitmap, tileId, dx, dy);
                    }
                }
            };
        }
    
        Tilemap.prototype.drawAutotile_OC = function (bitmap, tileId, dx, dy) {
    
            const kind = Tilemap.getAutotileKind(tileId);
            const shape = Tilemap.getAutotileShape(tileId);
            const tx = kind % 8;
            const ty = Math.floor(kind / 8);
    
            let setNumber = 0;
            let bx = 0;
            let by = 0;
            let autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
            let isTable = false;
    
            if (Tilemap.isTileA1(tileId)) {
                const waterSurfaceIndex = [0, 1, 2, 1][this.animationFrame % 4];
                setNumber = 0;
                if (kind === 0) {
                    bx = waterSurfaceIndex * 2;
                    by = 0;
                } else if (kind === 1) {
                    bx = waterSurfaceIndex * 2;
                    by = 3;
                } else if (kind === 2) {
                    bx = 6;
                    by = 0;
                } else if (kind === 3) {
                    bx = 6;
                    by = 3;
                } else {
                    bx = Math.floor(tx / 4) * 8;
                    by = ty * 6 + (Math.floor(tx / 2) % 2) * 3;
                    if (kind % 2 === 0) {
                        bx += waterSurfaceIndex * 2;
                    } else {
                        bx += 6;
                        autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
                        by += this.animationFrame % 3;
                    }
                }
            } else if (Tilemap.isTileA2(tileId)) {
                setNumber = 1;
                bx = tx * 2;
                by = (ty - 2) * 3;
                isTable = this._isTableTile(tileId);
            } else if (Tilemap.isTileA3(tileId)) {
                setNumber = 2;
                bx = tx * 2;
                by = (ty - 6) * 2;
                autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
            } else if (Tilemap.isTileA4(tileId)) {
                setNumber = 3;
                bx = tx * 2;
                by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
                if (ty % 2 === 1) {
                    autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
                }
            }
    
            const table = autotileTable[shape];
            const w1 = OcRam.twh50[0];
            const h1 = OcRam.twh50[1];
            const source = this._bitmaps[setNumber];
    
            for (let i = 0; i < 4; i++) {
                const qsx = table[i][0];
                const qsy = table[i][1];
                const sx1 = (bx * 2 + qsx) * w1;
                const sy1 = (by * 2 + qsy) * h1;
                const dx1 = dx + (i % 2) * w1;
                const dy1 = dy + Math.floor(i / 2) * h1;
                if (isTable && (qsy === 1 || qsy === 5)) {
                    const qsx2 = qsy === 1 ? (4 - qsx) % 4 : qsx;
                    const qsy2 = 3;
                    const sx2 = (bx * 2 + qsx2) * w1;
                    const sy2 = (by * 2 + qsy2) * h1;
                    bitmap.blt(source, sx2, sy2, w1, h1, dx1, dy1, w1, h1);
                    dy1 += h1 * 0.5;
                    bitmap.blt(source, sx1, sy1, w1, h1 * 0.5, dx1, dy1, w1, h1 * 0.5);
                } else {
                    bitmap.blt(source, sx1, sy1, w1, h1, dx1, dy1, w1, h1);
                }
            }
    
        };
    
        Tilemap.prototype.drawNormalTile_OC = function (bitmap, tileId, dx, dy) {
    
            let setNumber = 0;
    
            if (Tilemap.isTileA5(tileId)) {
                setNumber = 4;
            } else {
                setNumber = 5 + Math.floor(tileId / 256);
            }
    
            const w = OcRam.twh[0]; const h = OcRam.twh[1];
            const sx = ((Math.floor(tileId / 128) % 2) * 8 + (tileId % 8)) * w;
            const sy = (Math.floor((tileId % 256) / 8) % 16) * h;
            const source = this._bitmaps[setNumber];
    
            if (source) bitmap.blt(source, sx, sy, w, h, dx, dy, w, h);
    
        };
    
        Tilemap.prototype.drawTableEdge_OC = function (bitmap, tileId, dx, dy) {
            if (Tilemap.isTileA2(tileId)) {
                const autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
                const kind = Tilemap.getAutotileKind(tileId);
                const shape = Tilemap.getAutotileShape(tileId);
                const tx = kind % 8;
                const ty = Math.floor(kind / 8);
                const bx = tx * 2;
                const by = (ty - 2) * 3;
                const table = autotileTable[shape];
                const w1 = OcRam.twh50[0];
                const h1 = OcRam.twh50[1];
                const source = this._bitmaps[1];
                for (let i = 0; i < 2; i++) {
                    const qsx = table[2 + i][0];
                    const qsy = table[2 + i][1];
                    const sx1 = (bx * 2 + qsx) * w1;
                    const sy1 = (by * 2 + qsy) * h1 + h1 / 2;
                    const dx1 = dx + (i % 2) * w1;
                    const dy1 = dy + Math.floor(i / 2) * h1;
                    bitmap.blt(source, sx1, sy1, w1, h1 * 0.5, dx1, dy1, w1, h1 * 0.5);
                }
            }
        };
    
        Tilemap.prototype.drawShadow_OC = function (bitmap, shadowBits, dx, dy) {
            if (shadowBits & 0x0f) {
                const w1 = OcRam.twh50[0];
                const h1 = OcRam.twh50[1];
                for (let i = 0; i < 4; i++) {
                    if (shadowBits & (1 << i)) {
                        const dx1 = dx + (i % 2) * w1;
                        const dy1 = dy + Math.floor(i / 2) * h1;
                        bitmap.fillRect(dx1, dy1, w1, h1, 'rgba(0,0,0,0.5)');
                    }
                }
            }
        };
    
        Tilemap.prototype.paintTilesOnBitmap_OC = function (bm_lo, bm_hi, x, y, at) {
    
            const x1 = 0; const y1 = 0;
            const tileId0 = this._readMapData_OC(x, y, 0); // Autotile (ground)
            const tileId1 = this._readMapData_OC(x, y, 1); // Autotile (bush)
            const tileId2 = this._readMapData_OC(x, y, 2, true); // B-E tile (x/o)
            const tileId3 = this._readMapData_OC(x, y, 3, true); // B-E tile (*)
            const upperTileId1 = this._readMapData_OC(x, y - 1, 1);
    
            let tilesHigh = []; let tilesLow = [];
            const shadowBits = this._readMapData_OC(x, y, 4);
    
            if (at) { // Autotiles will be drawn "flat" (1 bitmap)
    
                if (this._isHigherTile(tileId0)) { tilesHigh.push(tileId0); }
                else { tilesLow.push(tileId0); }
    
                if (this._isHigherTile(tileId1)) { tilesHigh.push(tileId1); }
                else { tilesLow.push(tileId1); }
    
                bm_lo.clearRect(x1, y1, OcRam.twh[0], OcRam.twh[1]);
    
                let i = 0;
    
                for (i = 0; i < tilesLow.length; i++) {
                    const lowerTileId = tilesLow[i];
                    if (lowerTileId < 0) {
                    } else if (lowerTileId >= _tableEdgeVirtualId) {
                        this.drawTableEdge_OC(bm_lo, upperTileId1, x1, y1);
                    } else {
                        this.drawTileToBitmap_OC(bm_lo, lowerTileId, x1, y1);
                    } if (at) this.drawShadow_OC(bm_lo, shadowBits, x1, y1);
                }
    
                for (i = 0; i < tilesHigh.length; i++) {
                    this.drawTileToBitmap_OC(bm_lo, tilesHigh[i], x1, y1);
                }
    
            } else { // B-E layers are drawn to low and high (2 bitmaps)
    
                if (this._isTableTile(upperTileId1) && !this._isTableTile(tileId1)) {
                    if (!Tilemap.isShadowingTile(tileId0)) { tilesLow.push(_tableEdgeVirtualId + upperTileId1); }
                }
    
                if (this._isHigherTile(tileId2)) { tilesHigh.push(tileId2); }
                else { tilesLow.push(tileId2); }
    
                if (this._isHigherTile(tileId3)) { tilesHigh.push(tileId3); }
                else { tilesLow.push(tileId3); }
    
                bm_lo.clearRect(x1, y1, OcRam.twh[0], OcRam.twh[1]);
                bm_hi.clearRect(x1, y1, OcRam.twh[0], OcRam.twh[1]);
    
                for (let i = 0; i < tilesLow.length; i++) {
                    const lowerTileId = tilesLow[i];
                    if (lowerTileId < 0) {
                    } else if (lowerTileId >= _tableEdgeVirtualId) {
                        this.drawTableEdge_OC(bm_lo, upperTileId1, x1, y1);
                    } else {
                        this.drawTileToBitmap_OC(bm_lo, lowerTileId, x1, y1);
                    } if (at) this.drawShadow_OC(bm_lo, shadowBits, x1, y1);
                }
    
                for (let i = 0; i < tilesHigh.length; i++) {
                    this.drawTileToBitmap_OC(bm_hi, tilesHigh[i], x1, y1);
                }
    
            }
    
        };
    
        // Create shadow sprite to top layer
        Spriteset_Map.prototype.createShadow_OC = function () {
            this._shadowSprite = new Sprite();
            this._shadowSprite.bitmap = ImageManager.loadSystem('Shadow1');
            this._shadowSprite.anchor.x = 0.5;
            this._shadowSprite.anchor.y = 1;
            this._shadowSprite.z = 6;
            this._layerContainer_OC.addChild(this._shadowSprite);
        };
    
        // Local_Coop compatibility
        Game_Followers.prototype.isSomeoneCollided_OC = function (x, y, hl) {
            return this.visibleFollowers().some(function (follower) {
                return follower.pos(x, y) && (follower._higherLevel == hl);
            }, this);
        };
    
        // ------------------------------------------------------------------------------
        // Aliases
        // ==============================================================================
    
        this.extend(Spriteset_Map, "createDestination", function () {
            _this["Spriteset_Map_createDestination"].apply(this, arguments);
            this._destinationSprite.z = 9999 + 2;
        });
    
        this.extend(Game_Player, "center", function () {
            _this["Game_Player_center"].apply(this, arguments); mapCulling();
        });
    
        // Preload tilesets
        this.extend(Scene_Boot, "isReady", function () {
            let ret = _this["Scene_Boot_isReady"].apply(this, arguments);
            if (ret) preLoadSeasonTilesets(); return ret;
        });
    
        // Forces system to reload whole tileset
        this.forceTilesetReload = function () {
            _flags = []; _currentShaderTilemap = null;
            _cacheReady = false; _tileSprites = [];
        };
    
        // Tileset change
        this.extend(Game_Map, "changeTileset", function (tilesetId, just_loaded) {
            OcRam.Time_System._prevTilesetId = this._tilesetId;
            OcRam.Time_System._currentTilesetId = tilesetId;
            _this["Game_Map_changeTileset"].apply(this, arguments);
            if (!just_loaded) {
                requestAnimationFrame(() => { reStartScene(); });
            }
        });
    
        // Update "z" based on Y coordinate if same priority
        this.extend(Tilemap, "updateTransform", function () {
            this._sortChildren_OC(); _this["Tilemap_updateTransform"].apply(this, arguments);
        }); if (!OcRam.isMZ()) { // In MV there's ShaderTilemap for webGL
            this.extend(ShaderTilemap, "updateTransform", function () {
                this._sortChildren_OC(); _this["ShaderTilemap_updateTransform"].apply(this, arguments);
            });
        }
    
        // Do not face up when behind ladders
        this.extend(Game_CharacterBase, "isOnLadder", function () {
            if (!this._higherLevel && this.regionId() == _coverId) return false;
            return _this["Game_CharacterBase_isOnLadder"].apply(this, arguments);
        });
    
        // Auto-assign floor level to new party members
        this.extend(Game_Party, "addActor", function (actorId) {
            _this["Game_Party_addActor"].apply(this, arguments);
            if (SceneManager._scene.isMap()) {
                const new_index = $gamePlayer._followers.visibleFollowers().length - 1;
                if (new_index < 4 && new_index > -1) {
                    $gamePlayer._followers._data[new_index]._higherLevel = autoAssignFloorLevel($gamePlayer._followers._data[new_index]);
                }
            }
        });
    
        // Move sprites when scrolling map
        this.extend(Spriteset_Map, "update", function () {
            _this["Spriteset_Map_update"].apply(this, arguments);
            if (SceneManager._scene._spriteset && SceneManager._scene._spriteset._layerContainer_OC) {
                $gameMap.moveSpritesX_OC(); $gameMap.moveSpritesY_OC();
            }
        });
    
        // This is the day, events can underpass AND overpass despite of player floor level...
        this.extend(Game_CharacterBase, "refreshBushDepth", function () {
            _this["Game_CharacterBase_refreshBushDepth"].apply(this, arguments);
            const region_id = this.regionId();
            if (region_id == _overpassId) {
                this._higherLevel = true;
                if (SceneManager._scene._spriteset !== undefined) updateEvent(this);
            } else if (region_id == _underpassId) {
                this._higherLevel = this._priorityType == 2;
                if (SceneManager._scene._spriteset !== undefined) updateEvent(this);
            }
        });
    
        if (_useTallSprites) { // Followers use same floor level as player...
    
            this.extend(Game_Player, "refreshBushDepth", function () {
                mapCulling(); const old_lvl = this._higherLevel;
                _this["Game_Player_refreshBushDepth"].apply(this, arguments);
                if (old_lvl != this._higherLevel) {
                    this._followers.visibleFollowers().forEach(function (f) {
                        f._higherLevel = $gamePlayer._higherLevel;
                        if (SceneManager._scene._spriteset !== undefined) {
                            const hs = getCharSprite(f);
                            if (hs) hs._showHigherSprite = f._higherLevel;
                        }
                    });
                }
            });
    
            this.extend(Game_Follower, "refreshBushDepth", function () {
                _this["Game_CharacterBase_refreshBushDepth"].apply(this, arguments);
            });
    
        } else {
    
            // Culling done when player is moved (once in distance of tile)
            this.extend(Game_Player, "refreshBushDepth", function () {
                mapCulling(); _this["Game_Player_refreshBushDepth"].apply(this, arguments);
            });
    
        }
    
        // Update character graphics on event page change
        this.extend(Game_Event, "setupPage", function () {
    
            _this["Game_Event_setupPage"].apply(this, arguments);
    
            const ev_cmts = this.getStringComments();
            ev_cmts.forEach(s => {
                if (s == "<higher>") {
                    this._higherLevel = true;
                } else if (s == "<lower>") {
                    this._higherLevel = false;
                }
            });
    
            if (SceneManager._scene._spriteset !== undefined) {
                if (this._higherLevel === undefined) this._higherLevel = autoAssignFloorLevel(this);
                updateEvent(this);
            } else {
                requestAnimationFrame(() => {
                    if (this._higherLevel === undefined) this._higherLevel = autoAssignFloorLevel(this);
                    updateEvent(this);
                });
            }
    
        });
    
        // Check if sprite needs to be drawed
        this.extend(Sprite_Character, "updateVisibility", function () {
            _this["Sprite_Character_updateVisibility"].apply(this, arguments);
            if (this._showHigherSprite === undefined && !this._character._higherLevel) return;
            this.visible = (this._character._higherLevel) ? this._showHigherSprite : false;
        });
    
        // Update sprite graphics on updateAirshipAltitude
        this.extend(Game_Vehicle, "updateAirshipAltitude", function () {
    
            const is_hl = this._higherLevel;
            const cs = getCharSprite(this);
    
            if (!this._driving && this.isHighest() && cs) cs.visible = is_hl;
            
            if (!this.isLowest() && !this.isHighest()) {
                updateEvent(this); purgeHigherLevelSprite($gamePlayer);
                OcRam.followers().forEach(f => { purgeHigherLevelSprite(f); });
            }
            
            _this["Game_Vehicle_updateAirshipAltitude"].apply(this, arguments);
    
            if (!this._driving && this.isLowest()) {
                if (!_isAirshipLanded) {
                    _isAirshipLanded = true; $gamePlayer._higherLevel = is_hl; updateEvent($gamePlayer);
                    if (is_hl) getCharSprite($gamePlayer)._showHigherSprite = is_hl;
                    $gamePlayer._followers.visibleFollowers().forEach(function (f) {
                        f._higherLevel = $gamePlayer._higherLevel; updateEvent(f);
                        if (is_hl) getCharSprite(f)._showHigherSprite = is_hl;
                    });
                }
            }
    
        });
    
        // Floor level wise landing...
        this.extend(Game_Vehicle, "isLandOk", function (x, y, d) {
            const tmp_ret = _this["Game_Vehicle_isLandOk"].apply(this, arguments);
            if (tmp_ret) {
                if ($gameMap.regionId(x, y) == _coverId) return false;
                if (this._type == "airship") {
                    if (!$gameMap.checkPassage(x, y, (1 << (d / 2 - 1)) & 0x0f, true)) return false;
                }
                return true;
            }
        });
    
        // "Smart" drop >> check which autotile is drawn to landing point
        this.extend(Game_Vehicle, "getOff", function () {
            _isAirshipLanded = false; let is_hl = false;
            if ($gamePlayer._vehicleType == "airship") {
                $gamePlayer._higherLevel = undefined;
                is_hl = autoAssignFloorLevel($gamePlayer) ||
                    ($gameMap.regionId($gamePlayer._x, $gamePlayer._y) == _overpassId);
                this._higherLevel = is_hl;
            } _this.debug("Landed to " + (is_hl ? "higher" : "lower") + " level", $gamePlayer);
            _this["Game_Vehicle_getOff"].apply(this, arguments);
        });
    
        // If it's boat sea level is always 'low' level... Must be overwritten via JS if this is not the case
        this.extend(Game_Player, "updateVehicleGetOff", function() {
            if (!this.areFollowersGathering() && this.vehicle().isLowest()) {
                if ($gamePlayer._vehicleType != "airship") {
                    setObjFloorLevel($gamePlayer, 'low');
                    OcRam.followers().forEach(f => {
                        setObjFloorLevel(f, 'low');
                    });
                }
            } _this["Game_Player_updateVehicleGetOff"].apply(this, arguments);
        });
    
        // For OcRam_Movement compatibility and floor level wise boarding...
        this.extend(Game_Player, "getOnVehicle", function () {
    
            const d = this.direction(); const x1 = this.x; const y1 = this.y;
            const x2 = $gameMap.roundXWithDirection(x1, d);
            const y2 = $gameMap.roundYWithDirection(y1, d);
    
            let vehicle_type = ''; let ret = false;
    
            if ($gameMap.airship().pos(x1, y1)) {
                vehicle_type = 'airship';
            } else if ($gameMap.ship().pos(x2, y2)) {
                vehicle_type = 'ship';
            } else if ($gameMap.boat().pos(x2, y2)) {
                vehicle_type = 'boat';
            } else {
                return false;
            }
    
            if (vehicle_type == 'airship' && (this._higherLevel != $gameMap.airship()._higherLevel)) {
                ret = false;
            } else {
                ret = _this["Game_Player_getOnVehicle"].apply(this, arguments);
            }
    
            if (ret && vehicle_type == 'airship') {
                setObjFloorLevel(OcRam.getGameObject(-102), 'high');
            } return ret;
            
        });
    
        // Hide actor higherlevel sprites...
        this.extend(Game_Vehicle, "getOn", function () {
            _this["Game_Vehicle_getOn"].apply(this, arguments); let sprite = null;
            this._higherLevel = this.isAirship();
            for (let i = 0; i < $gamePlayer._followers.visibleFollowers().length; i++) {
                $gamePlayer._followers.visibleFollowers()[i]._transparent = true;
                updateEvent($gamePlayer._followers.visibleFollowers()[i]);
            } sprite = getCharSprite($gamePlayer);
            $gamePlayer._higherLevel = this._higherLevel;
            OcRam.followers().forEach(f => {
                f._higherLevel = this._higherLevel;
            }); if (sprite != undefined) sprite.visible = false; updateEvent($gamePlayer);
        });
    
        // Floor level wise collision check
        this.extend(Game_Event, "isCollidedWithPlayerCharacters", function (x, y) {
            if ($gamePlayer._followers.isSomeoneCollided_OC(x, y, this._higherLevel)) return true;
            if ($gamePlayer._higherLevel != this._higherLevel) return false;
            return _this["Game_Event_isCollidedWithPlayerCharacters"].apply(this, arguments);
        });
    
        // Special alias (to be used diffrently than normal aliases)
        const OC_Tilemap_readMapData = Tilemap.prototype._readMapData;
    
        // Passages will draw B-E COVER layers ALWAYS
        Tilemap.prototype._readMapData_OC = function (x, y, z) {
            return OC_Tilemap_readMapData.call(this, x, y, z);
        };
    
        // Core engine calls this to draw tiles
        // >> Do not draw B-E COVER layers if they are already drawn on higher layers
        Tilemap.prototype._readMapData = function (x, y, z) {
            if (z == 2 || z == 3) {
                if ($gameMap.regionId(x, y) == _coverId) return 0;
            } return OC_Tilemap_readMapData.call(this, x, y, z);
        };
    
        // ------------------------------------------------------------------------------
        // Overrides
        // ==============================================================================
    
        if (Imported.OcRam_Movement) {
            Game_CharacterBase.prototype.isCollidedWithEvents_OC = function (x, y, d, mx, my) {
                if (this.isPlayer()) {
                    if (this.isInAirship()) return false;
                    let idir = this.getInputDirection();
                    if (OcRam.Movement.isDiagonalDir(idir)) return false;
                } let events = $gameMap.eventsXyNt(x, y);
                let ret = events.some(event => event.isNormalPriority() && (event._higherLevel == this._higherLevel));
                ret = ret && ((mx < 0.5 && my < 0.1) || (my < 0.5 && mx < 0.1));
                if (!ret) {
                    switch (d) {
                        case 2: case 8:
                            if (mx > 0.5 && my < 0.1) {
                                events = $gameMap.eventsXyNt(x + 1, y);
                                ret = events.some(event => event.isNormalPriority() && (event._higherLevel == this._higherLevel));
                            } break;
                        case 4: case 6:
                            if (my > 0.5 && mx < 0.1) {
                                events = $gameMap.eventsXyNt(x, y + 1);
                                ret = events.some(event => event.isNormalPriority() && (event._higherLevel == this._higherLevel));
                            } break;
                    }
                } return ret;
            };
        }
    
        if (Imported.OcRam_Movement && OcRam.Movement.pixelMoveEnabled()) {  // Consider floor level also! - PIXEL MOVEMENT VERSION!
    
            // Consider floor level also! (with OcRam pixel movement...)
            Game_Map.prototype.isPassable = function (x, y, d, hl) {
                return this.checkPassage(Math.round(x), Math.round(y), (1 << (d / 2 - 1)) & 0x0f, hl);
            };
    
            Game_CharacterBase.prototype.isMapPassable = function (x, y, d) {
    
                if (x > $gameMap.width() - 1) x %= $gameMap.width();
                if (y > $gameMap.height() - 1) y %= $gameMap.height();
    
                const x2 = $gameMap.roundXWithDirection(x, d);
                const y2 = $gameMap.roundYWithDirection(y, d);
                const next_region_id = $gameMap.regionId(x2, y2);
                if (next_region_id == _blockId) return false;
    
                const this_region_id = $gameMap.regionId(x, y);
                const t_hl = this._higherLevel;
                const is_this_cover = (this_region_id == _coverId || this_region_id == _autoCoverId);
    
                if (t_hl) { // This char is in higher ground
                    if (is_this_cover) {
                        if (next_region_id == _underpassId) return false;
                    } if (this_region_id == _blockHighLowId && next_region_id == _underpassId) return false;
                } else { // This char is in lower ground
                    if (is_this_cover && (next_region_id == _overpassId || next_region_id == 0)) return false;
                    if (next_region_id == _overheadId) return false;
                    if ((this_region_id == _underpassId || is_this_cover) && (next_region_id == _underpassId ||
                        next_region_id == _coverId || next_region_id == _autoCoverId)) return true;
                } return $gameMap.isPassable(x, y, d, this._higherLevel) && $gameMap.isPassable(x2, y2, this.reverseDir(d), this._higherLevel);
    
            };
        } else { // Consider floor level also! - TILE BASED MOVEMENT
    
            // Consider floor level also!
            Game_Map.prototype.isPassable = function (x, y, d, hl) {
                return this.checkPassage(x, y, (1 << (d / 2 - 1)) & 0x0f, hl);
            };
    
            Game_CharacterBase.prototype.isMapPassable = function (x, y, d) {
    
                const x2 = $gameMap.roundXWithDirection(x, d);
                const y2 = $gameMap.roundYWithDirection(y, d);
                const next_region_id = $gameMap.regionId(x2, y2);
                if (next_region_id == _blockId) return false;
    
                const this_region_id = $gameMap.regionId(x, y);
                const t_hl = this._higherLevel;
                const is_this_cover = (this_region_id == _coverId || this_region_id == _autoCoverId);
    
                if (t_hl) { // This char is in higher ground
                    if (is_this_cover) {
                        if (next_region_id == _underpassId) return false;
                    } if (this_region_id == _blockHighLowId && next_region_id == _underpassId) return false;
                } else { // This char is in lower ground
                    if (is_this_cover && (next_region_id == _overpassId || next_region_id == 0)) return false;
                    if (next_region_id == _overheadId) return false;
                    if ((this_region_id == _underpassId || is_this_cover) && (next_region_id == _underpassId ||
                        next_region_id == _coverId || next_region_id == _autoCoverId)) return true;
                } return $gameMap.isPassable(x, y, d, this._higherLevel) && $gameMap.isPassable(x2, y2, this.reverseDir(d), this._higherLevel);
    
            };
        }
    
        
    
        // Consider floor level also!
        Game_Map.prototype.checkPassage = function (x, y, bit, hl) {
    
            const this_region_id = $gameMap.regionId(x, y);
            if (this_region_id == _blockId) return false;
            if (!hl) { // Event which called this method, is on lower floor level
                if (this_region_id == _overheadId) return false;
                if (this_region_id == _blockHighLowId) return false;
            }
    
            const tiles = this.allTiles(x, y);
    
            for (let i = 0; i < tiles.length; i++) {
                if (tiles) {
                    const flag = _flags[tiles[i]];
                    if (((flag & 0x10) !== 0) || (hl ? false : (this_region_id == _coverId || this_region_id == _autoCoverId) ? true : false)) // [*] No effect on passage
                        continue;
                    if ((flag & bit) === 0) // [o] Passable
                        return true;
                    if ((flag & bit) === bit) // [x] Impassable
                        return false;
                }
            } return true;
    
        };
    
        // Consider floor level also!
        Game_CharacterBase.prototype.isCollidedWithEvents = function (x, y) {
            let events = $gameMap.eventsXyNt(x, y); const is_hl = this._higherLevel;
            return events.some(function (event) {
                return (event.isNormalPriority() && (event._higherLevel == is_hl));
            });
        };
    
        // Consider floor level also!
        Game_Event.prototype.isCollidedWithEvents = function (x, y) {
            let events = $gameMap.eventsXyNt(x, y); const is_hl = this._higherLevel;
            return events.some(function (event) {
                return (event.isNormalPriority() && (event._higherLevel == is_hl));
            });
        };
    
        // Change event interaction by floor level
        Game_Player.prototype.startMapEvent = function (x, y, triggers, normal) { // Start events ONLY if they are on same 'floor'
            if (!$gameMap.isEventRunning()) {
                const this_hl = (this !== undefined) ? this._higherLevel : false;
                for (const event of $gameMap.eventsXy(x, y)) {
                    if (
                        event.isTriggerIn(triggers) &&
                        event.isNormalPriority() === normal
                    ) {
                        let ev_cmts = []; let trigger_always = false;
                        ev_cmts = event.getStringComments(); trigger_always = false;
                        ev_cmts.forEach(s => {
                            if (s == "<trigger_always>") trigger_always = true;
                        }); if (event._higherLevel == this_hl || trigger_always) {
                            event.start(); updateEvent(event);
                        }
                    }
                }
            }
        };
    
        // Do not "refreshBushDepth" if vehicle (prevents undesired under-/over passages)
        Game_Vehicle.prototype.refreshBushDepth = function () { /* do nothing */ };
    
        // Show balloons ABOVE cover tiles...
        Sprite_Balloon.prototype.updatePosition = function () {
            if ($gameMap) {
                this.x = this._target.x + Math.floor($gameMap._displayX * OcRam.twh[0]);
                this.y = this._target.y - this._target.height + Math.floor($gameMap._displayY * OcRam.twh[1]);
            } else {
                this.x = this._target.x;
                this.y = this._target.y - this._target.height;
            }
        };
    
        // Show animations ABOVE cover tiles...
        Sprite_AnimationMV.prototype.updatePosition = function () {
            if (this._animation.position === 3) {
                this.x = this.parent.width / 2;
                this.y = this.parent.height / 2;
            } else if (this._targets.length > 0) {
                const target = this._targets[0];
                const parent = target.parent;
                const grandparent = parent ? parent.parent : null;
                if ($gameMap) {
                    this.x = target.x + Math.floor($gameMap._displayX * OcRam.twh[0]);
                    this.y = target.y + Math.floor($gameMap._displayY * OcRam.twh[1]);
                } else {
                    this.x = target.x;
                    this.y = target.y;
                }
                if (this.parent === grandparent) {
                    this.x += parent.x;
                    this.y += parent.y;
                }
                if (this._animation.position === 0) {
                    this.y -= target.height;
                } else if (this._animation.position === 1) {
                    this.y -= target.height / 2;
                }
            }
        };
    
        Game_Character.prototype.setFloorLevel = function (str_level) {
            setObjFloorLevel(this, str_level);
        };
    
        if (Imported.OcRam_Events) { // Overwrite Game_Player.prototype.checkThrowPass to allow throw from higher to lower!
            Game_Character.prototype.checkThrowPass = function (x, y) {
                const x1 = this._direction == 6 ? x + 1 : this._direction == 4 ? x - 1 : x;
                const y1 = this._direction == 2 ? y + 1 : this._direction == 8 ? y - 1 : y;
                const nxt_rid = $gameMap.regionId(x1, y1); const ths_rid = $gameMap.regionId(x, y);
                const hne = !($gameMap.eventsXyNt(x1, y1).find(e => { return e._priorityType == 1 && this._higherLevel == e._higherLevel; }));
                return (this.isMapPassable(x, y, this._direction, this._higherLevel) && hne) ||
                    (nxt_rid == _underpassId && hne) ||
                    nxt_rid == OcRam.Events.allowedThrowRegionId() ||
                    nxt_rid == OcRam.Events.allowedLandingRegionId() ||
                    ths_rid == OcRam.Events.allowedThrowRegionId() ||
                    ths_rid == OcRam.Events.allowedLandingRegionId();
            };
        }
    
        // ------------------------------------------------------------------------------
        // Core "must overrides"
        // ==============================================================================
        this.clearPluginData = function () {
            this.forceTilesetReload(); // When exited to title clear things up...
        };
    
        this.loadPluginData = gs => {
            gs.loadPassageData();
        };
    
        this.savePluginData = gs => {
            if (!OcRam._autoSaving) gs.savePassageData();
        };
    
        this.onMapStart = sm => {
    
            initSprites();
    
            let loop_hack_redefined = false;
    
            if (!$gameMap.isLoopHorizontal() && !$gameMap.isLoopVertical()) {
                OcRam.hackSC_LooppedMaps = sc_oc => { }; loop_hack_redefined = true;
            } else if ($gameMap.isLoopHorizontal() && $gameMap.isLoopVertical()) {
                OcRam.hackSC_LooppedMaps = sc_oc => { // Loopping maps... both x and y
                    if ((-(sc_oc.parent.x) > OcRam.Passages._loopEndPointX)) {
                        if (sc_oc.x < OcRam.Passages._loopEndPointX) sc_oc.x += $gameMap._pixelWidth;
                    } else {
                        if (sc_oc.x < Graphics.width * 0.5) sc_oc.x += $gameMap._pixelWidth;
                    } if ((-(sc_oc.parent.y) > OcRam.Passages._loopEndPointY)) {
                        if (sc_oc.y < OcRam.Passages._loopEndPointY) sc_oc.y += $gameMap._pixelHeight;
                    } else {
                        if (sc_oc.y < Graphics.height * 0.5) sc_oc.y += $gameMap._pixelHeight;
                    }
                }; loop_hack_redefined = true;
            }
    
            if ($gameMap.isLoopHorizontal()) { // Horizontal loopped map edges....
                this._loopEndPointX = $gameMap._pixelWidth - (Graphics.width * 0.5);
                Game_Map.prototype.moveSpritesX_OC = function () {
                    const cx = (this._displayX * OcRam.twh[0]) | 0;
                    SceneManager._scene._spriteset._layerContainer_OC.x = -cx;
                    if (_shiftedX && cx < this._pixelWidth - Graphics.width) {
                        shiftScreenPassagesX();
                    } else if (!_shiftedX && cx > this._pixelWidth - Graphics.width) {
                        shiftScreenPassagesX();
                    }
                }; if (OcRam._menuCalled) shiftScreenPassagesX();
                if (!loop_hack_redefined) {
                    OcRam.hackSC_LooppedMaps = sc_oc => { // Loopping maps... x
                        if ((-(sc_oc.parent.x) > OcRam.Passages._loopEndPointX)) {
                            if (sc_oc.x < OcRam.Passages._loopEndPointX) sc_oc.x += $gameMap._pixelWidth;
                        } else {
                            if (sc_oc.x < Graphics.width * 0.5) sc_oc.x += $gameMap._pixelWidth;
                        }
                    };
                }
            } else {
                Game_Map.prototype.moveSpritesX_OC = function () {
                    SceneManager._scene._spriteset._layerContainer_OC.x = -(this._displayX * OcRam.twh[0]) | 0;
                }; this._loopEndPointX = 512 * OcRam.twh[0]; // Maps will never hit this...
            }
    
            if ($gameMap.isLoopVertical()) { // Vertical loopped map edges....
                this._loopEndPointY = $gameMap._pixelHeight - (Graphics.height * 0.5);
                Game_Map.prototype.moveSpritesY_OC = function () {
                    const cy = (this._displayY * OcRam.twh[1]) | 0;
                    SceneManager._scene._spriteset._layerContainer_OC.y = -cy;
                    if (_shiftedY && cy < this._pixelHeight - Graphics.height) {
                        shiftScreenPassagesY();
                    } else if (!_shiftedY && cy > this._pixelHeight - Graphics.height) {
                        shiftScreenPassagesY();
                    }
                }; if (OcRam._menuCalled) shiftScreenPassagesY();
                if (!loop_hack_redefined) {
                    OcRam.hackSC_LooppedMaps = sc_oc => { // Loopping maps... y
                        if ((-(sc_oc.parent.y) > OcRam.Passages._loopEndPointY)) {
                            if (sc_oc.y < OcRam.Passages._loopEndPointY) sc_oc.y += $gameMap._pixelHeight;
                        } else {
                            if (sc_oc.y < Graphics.height * 0.5) sc_oc.y += $gameMap._pixelHeight;
                        }
                    };
                }
            } else {
                Game_Map.prototype.moveSpritesY_OC = function () {
                    SceneManager._scene._spriteset._layerContainer_OC.y = -(this._displayY * OcRam.twh[1]) | 0;
                }; this._loopEndPointY = 512 * OcRam.twh[1]; // Maps will never hit this...
            }
    
        };
    
        this.onMapTerminate = sm => { // Make sure layers are saved before scene is terminated (except if new map)
            if (SceneManager.isNextScene(Scene_Map)) {
                if ($gamePlayer.newMapId() != $gameMap.mapId()) {
                    Game_Map.prototype.moveSpritesY_OC = function () { }; Game_Map.prototype.moveSpritesX_OC = function () { };
                    this.forceTilesetReload(); this.debug("New map is coming...", "Force to re-load whole tileset!");
                } else {
                    if (OcRam.Time_System._currentTilesetId != OcRam.Time_System._prevTilesetId) {
                        this.forceTilesetReload(); this.debug("Season has been changed!", "Force to re-load whole tileset!");
                    }
                }
            }
        };
    
        this.createLowerMapLayer = sm => {
            sm._baseSprite.removeChild(sm._shadowSprite);
            sm.createCoverLayers_OC();
            sm._baseSprite.addChild(sm._shadowSprite);
            sm._effectsContainer = sm._layerContainer_OC; // Show balloons + animations ABOVE cover tiles...
        };
    
        this.createLowerBattleLayer = sb => { };
        this.onMapLoaded = sm => { };
    
        // ------------------------------------------------------------------------------
        // Plugin commands
        // ==============================================================================
    
        PluginManager.registerCommand("OcRam_" + this.name, "setFloorLevel", function (args) {
            _this.debug("Plugin command: setFloorLevel", args);
            if (SceneManager._scene._spriteset !== undefined) {
                const obj_id = Number(args.objectId);
                const this_obj = OcRam.getGameObject(obj_id);
                const str_level = String(args.floorLevel).toLowerCase();
                this_obj.setFloorLevel(str_level.toLowerCase());
                if (obj_id == -1) { // update followers also...
                    OcRam.followers().forEach(f => {
                        f.setFloorLevel(str_level.toLowerCase());
                    });
                }
            }
        });
    
        PluginManager.registerCommand("OcRam_" + this.name, "setTransferLevel", function (args) {
            _this.debug("Plugin command: setTransferLevel", args);
            _transferLevel = Number(args.floorLevel);
        });
    
    }.bind(OcRam.Passages)());