r66r

Fantasy dreamer... sometimes.
Veteran
Joined
Jan 5, 2020
Messages
141
Reaction score
146
First Language
French
Primarily Uses
RMMZ
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.

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?
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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.


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.
 

Tuomo L

Oldbie
Veteran
Joined
Aug 6, 2012
Messages
2,490
Reaction score
1,411
First Language
Finnish
Primarily Uses
RMMV
Is it possible to set time system so that some months have less days, like February?
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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.
 

MasterTenchi

Veteran
Veteran
Joined
Nov 2, 2019
Messages
88
Reaction score
39
First Language
English
Primarily Uses
RMMZ
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.
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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.
 

orphalese

Veteran
Veteran
Joined
Jun 1, 2021
Messages
36
Reaction score
7
First Language
Spanish
Primarily Uses
RMMZ
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!!
 
Last edited:

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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);
    });
 
Last edited:

Gabezin

Veteran
Veteran
Joined
Sep 27, 2016
Messages
43
Reaction score
13
First Language
Spanish
Primarily Uses
Whoa, finally the fixing light to parallax feature.

Thank you!
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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.

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
 

Tuomo L

Oldbie
Veteran
Joined
Aug 6, 2012
Messages
2,490
Reaction score
1,411
First Language
Finnish
Primarily Uses
RMMV
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.
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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.
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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!
 

OMGerm

Veteran
Veteran
Joined
Apr 18, 2012
Messages
37
Reaction score
4
First Language
English
Primarily Uses
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.
 

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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.
 
Last edited:

OMGerm

Veteran
Veteran
Joined
Apr 18, 2012
Messages
37
Reaction score
4
First Language
English
Primarily Uses
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!
 

MasterTenchi

Veteran
Veteran
Joined
Nov 2, 2019
Messages
88
Reaction score
39
First Language
English
Primarily Uses
RMMZ
@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...
 
Last edited:

OMGerm

Veteran
Veteran
Joined
Apr 18, 2012
Messages
37
Reaction score
4
First Language
English
Primarily Uses
@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.
 
Last edited:

OcRam

Servant of the Universe
Veteran
Joined
Aug 2, 2016
Messages
950
Reaction score
1,105
First Language
Finnish
Primarily Uses
RMMZ
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)());
 

Latest Threads

Latest Posts

Latest Profile Posts

I really, really want the Magic story to be good, but instead we got today's chapter of the most predictable mish-mash of tropes with almost no personality. I let go of the physical game years ago, but I can't seem to let go of the story.
Just finished watching the last of the Final Destination movies. I'm digging every last water bottle out of that old van :p
Put off learning to drive for so long, I hope I don't run into any of-fences ..... (quietly leaves)
I introduced a new character to Mintabelle's Wonderland. Fufu the Raccoon! She's supposed to be a motherly daycare attendant, but when the power goes out, she becomes a monster. Rumor among kids says she causes these outages herself...
After an entire year, finally made new story cutscenes. Ones that take place after the current last boss. Praise be.

Forum statistics

Threads
129,803
Messages
1,205,304
Members
170,908
Latest member
DarrelOdin
Top