Downloads containing MLLE-Include-1.7.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Starling (CTF) cooba Capture the flag N/A Download file

File preview

//1738760862
//This is a standard library created by MLLE to read some JJ2+ properties from a level file whose script includes this library. DO NOT MANUALLY MODIFY THIS FILE.





#pragma require 'MLLE-Include-1.7.asc'
namespace MLLE {
    jjPAL@ Palette;
    dictionary@ _layers, _palettes;
    array<_offgridObject>@ _offGridObjects;

    bool Setup() {
        jjPAL palette = jjBackupPalette;
        @Palette = @palette;
        dictionary layers;
        @_layers = @layers;
        dictionary palettes;
        @_palettes = @palettes;

        jjSTREAM crcCheck('MLLE-Include-1.7.asc');
        string crcLine;
        if (crcCheck.isEmpty() || !crcCheck.getLine(crcLine)) {
            jjDebug('MLLE::Setup: Include file has been renamed!');
            return false;
        }
        array<string> regexResults;
        if (!jjRegexMatch(crcLine, '\\/\\/(\\d+)\\r?', regexResults)) {
            jjDebug('MLLE::Setup: Include file is improperly formatted!');
            return false;
        }
        if (parseUInt(regexResults[1]) != jjCRC32(crcCheck)) {
            jjDebug('MLLE::Setup: Include file has been damaged!');
            return false;
        }
        
        jjSTREAM level(jjLevelFileName);
        if (level.isEmpty()) {
            jjDebug('MLLE::Setup: Error reading "' + jjLevelFileName + '"!');
            return false;
        }
        level.discard(230);
        array<uint> CompressedDataSizes(4, 0);
        for (uint i = 0; i < CompressedDataSizes.length; ++i) {
            level.pop(CompressedDataSizes[i]);
            level.discard(4);
        }
        for (uint i = 0; i < CompressedDataSizes.length; ++i)
            level.discard(CompressedDataSizes[i]);

        if (level.getSize() < 20) {
            jjDebug('MLLE::Setup: Level file does not contain any additional data!');
            return false;
        }
        string magic;
        level.get(magic, 'MLLE'.length);
        if (magic != 'MLLE') {
            jjDebug('MLLE::Setup: Level was not saved by MLLE!');
            return false;
        }
        uint levelDataVersion;
        level.pop(levelDataVersion);
        if (levelDataVersion != 0x107) {
            jjDebug('MLLE::Setup: Level\'s Data5 section was saved in a different version of MLLE than this script!');
            return false;
        }

        uint csize, usize;
        level.pop(csize); level.pop(usize);
        jjSTREAM data5;
        if (!jjZlibUncompress(level, data5, usize)) {
            jjDebug('MLLE::Setup: Error during ZLIB uncompression!');
            return false;
        }

        bool pbool; uint8 pbyte; int8 pchar; int16 pshort; float pfloat; int pint; uint puint, puint2;
        data5.pop(pbool); jjIsSnowing = pbool;
        data5.pop(pbool); jjIsSnowingOutdoorsOnly = pbool;
        data5.pop(pbyte); jjSnowingIntensity = pbyte;
        data5.pop(pbyte); jjSnowingType = SNOWING::Type(pbyte);

        if (jjIsSnowing) {
            if (jjSnowingType == SNOWING::SNOW && jjAnimSets[ANIM::SNOW] == 0)
                jjAnimSets[ANIM::SNOW].load();
            else if (jjSnowingType == SNOWING::LEAF && jjAnimSets[ANIM::PLUS_SCENERY] == 0)
                jjAnimSets[ANIM::PLUS_SCENERY].load();
        }

        data5.pop(pbool); jjWarpsTransmuteCoins = pbool;
        data5.pop(pbool); jjDelayGeneratedCrateOrigins = pbool;
        data5.pop(pint);  jjEcho = pint;
        data5.pop(puint); jjSetDarknessColor(_colorFromArgb(puint));
        data5.pop(pfloat);jjWaterChangeSpeed = pfloat;
        data5.pop(pbyte); jjWaterInteraction = WATERINTERACTION::WaterInteraction(pbyte);
        data5.pop(pint);  jjWaterLayer = pint;
        data5.pop(pbyte); jjWaterLighting = WATERLIGHT::wl(pbyte);
        data5.pop(pfloat); if (int(pfloat) < jjLayerHeight[4] * 32) jjSetWaterLevel(pfloat, true);
        data5.pop(puint); data5.pop(puint2); jjSetWaterGradient(_colorFromArgb(puint), _colorFromArgb(puint2));

        data5.pop(pbool); if (pbool) {
            _readPalette(data5, palette);
            palette.apply();
            data5.pop(pbool);
        }

        data5.pop(pbyte);
        array<uint8> mappingIndices(pbyte);
        puint = 0;
        while (pbyte-- != 0) {
            jjPAL extra;
            string paletteName = _read7BitEncodedStringFromStream(data5);
            _readPalette(data5, extra);
            int index = jjSpriteModeFirstFreeMapping();
            if (index < 0) {
                jjDebug('MLLE::Setup: Not enough room for additional palette ' + paletteName);
            } else {
                mappingIndices[puint++] = uint8(index);
                _palettes.set(paletteName, uint8(index));
                array<uint8> indexMapping(256);
                for (uint i = 0; i < 256; ++i)
                    indexMapping[i] = jjPalette.findNearestColor(extra.color[i]);
                jjSpriteModeSetMapping(index, indexMapping, extra);
            }
        }

        _recolorAnimationIf(data5, ANIM::PINBALL, 0, 4);
        _recolorAnimationIf(data5, ANIM::PINBALL, 2, 4);
        _recolorAnimationIf(data5, ANIM::CARROTPOLE, 0, 1);
        _recolorAnimationIf(data5, ANIM::DIAMPOLE, 0, 1);
        _recolorAnimationIf(data5, ANIM::PINBALL, 4, 8);
        _recolorAnimationIf(data5, ANIM::JUNGLEPOLE, 0, 1);
        _recolorAnimationIf(data5, ANIM::PLUS_SCENERY, 0, 17);
        _recolorAnimationIf(data5, ANIM::PSYCHPOLE, 0, 1);
        _recolorAnimationIf(data5, ANIM::SMALTREE, 0, 1);
        _recolorAnimationIf(data5, ANIM::SNOW, 0, 8);
        _recolorAnimationIf(data5, ANIM::COMMON, 2, 18);
        _recolorAnimationIf(data5, ANIM::BOLLPLAT, 0, 2);
        _recolorAnimationIf(data5, ANIM::FRUITPLAT, 0, 2);
        _recolorAnimationIf(data5, ANIM::GRASSPLAT, 0, 2);
        _recolorAnimationIf(data5, ANIM::PINKPLAT, 0, 2);
        _recolorAnimationIf(data5, ANIM::SONICPLAT, 0, 2);
        _recolorAnimationIf(data5, ANIM::SPIKEPLAT, 0, 2);
        _recolorAnimationIf(data5, ANIM::SPIKEBOLL, 0, 2);
        _recolorAnimationIf(data5, ANIM::SPIKEBOLL3D, 0, 2);
        _recolorAnimationIf(data5, ANIM::VINE, 1, 1);

        data5.pop(pbyte);
        for (uint i = 0; i < pbyte; ++i) {
            string tilesetFilename = _read7BitEncodedStringFromStream(data5);
            uint16 tileStart, tileCount;
            data5.pop(tileStart); data5.pop(tileCount);
            array<uint8>@ colors = null;
            data5.pop(pbool); if (pbool) {
                @colors = array<uint8>(256);
                for (uint j = 0; j < 256; ++j)
                    data5.pop(colors[j]);
            }
            if (!jjTilesFromTileset(tilesetFilename, tileStart, tileCount, colors)) {
                jjDebug('MLLE::Setup: Error reading "' + tilesetFilename + '"!');
                return false;
            }
        }
        if (pbyte != 0) {
            array<uint> layersIDsWithTileMaps;
            for (uint i = 1; i <= 8; ++i)
                if (jjLayers[i].hasTileMap)
                    layersIDsWithTileMaps.insertLast(i);
            if (jjLayersFromLevel(jjLevelFileName, layersIDsWithTileMaps).length == 0) {
                jjDebug('MLLE::Setup: Error reading "' + jjLevelFileName + '"!');
            }
        }

        array<jjLAYER@> newLayerOrder, nonDefaultLayers;
        data5.pop(puint);
        for (uint i = 8; i < puint; i += 8) {
            array<uint> layerIDsToGrab;
            for (uint j = i; j < puint && j < i + 8; ++j) {
                layerIDsToGrab.insertLast((j & 7) + 1);
            }
            const string extraLayersFilename = jjLevelFileName.substr(0, jjLevelFileName.length() - 4) + '-MLLE-Data-' + (i/8) + '.j2l';
            array<jjLAYER@> extraLayers = jjLayersFromLevel(extraLayersFilename, layerIDsToGrab);
            if (extraLayers.length == 0) {
                jjDebug('MLLE::Setup: Error reading "' + extraLayersFilename + '"!');
                return false;
            }
            for (uint j = 0; j < extraLayers.length(); ++j)
                nonDefaultLayers.insertLast(extraLayers[j]);
        }
        uint nextNonDefaultLayerID = 0;
        for (uint i = 0; i < puint; ++i) {
            data5.pop(pchar);
            jjLAYER@ layer;
            if (pchar >= 0)
                @layer = jjLayers[pchar + 1];
            else
                @layer = nonDefaultLayers[nextNonDefaultLayerID++];
            string layerName = _read7BitEncodedStringFromStream(data5);
            _layers.set(layerName, @layer);
            data5.pop(pbool);
            if (layer.hasTileMap)
                layer.hasTiles = !pbool;
            data5.pop(pbyte);
            layer.spriteMode = SPRITE::Mode(pbyte);
            data5.pop(pbyte);
            layer.spriteParam = (layer.spriteMode == SPRITE::MAPPING || layer.spriteMode == SPRITE::TRANSLUCENTMAPPING) ? mappingIndices[pbyte] : pbyte;
            data5.pop(pint);
            layer.rotationAngle = pint;
            data5.pop(pint);
            layer.rotationRadiusMultiplier = pint;
            data5.pop(pbyte);
            layer.xSpeedModel = LAYERSPEEDMODEL::LayerSpeedModel(pbyte);
            data5.pop(pbyte);
            layer.ySpeedModel = LAYERSPEEDMODEL::LayerSpeedModel(pbyte);
            data5.pop(pbyte);
            layer.textureSurface = SURFACE::Surface(pbyte);
            data5.pop(pbyte);
            layer.reflection.tintColor = pbyte;
            data5.pop(pfloat);
            layer.reflection.fadePositionX = pfloat;
            data5.pop(pfloat);
            layer.reflection.top = pfloat;
            data5.pop(pfloat);
            layer.xInnerSpeed = pfloat;
            data5.pop(pfloat);
            layer.yInnerSpeed = pfloat;
            data5.pop(pfloat);
            layer.xInnerAutoSpeed = pfloat;
            data5.pop(pfloat);
            layer.yInnerAutoSpeed = pfloat;
            data5.pop(pchar);
            if (pchar >= 0)
                layer.texture = TEXTURE::Texture(pchar);
            else {
                jjPIXELMAP texture(256, 256);
                for (uint y = 0; y < 256; ++y)
                    for (uint x = 0; x < 256; ++x)
                        data5.pop(texture[x,y]);
                texture.makeTexture(layer);
            }
            newLayerOrder.insertLast(layer);
        }
        jjLayerOrderSet(newLayerOrder);

        uint16 numberOfObjects; data5.pop(numberOfObjects);
        while (numberOfObjects-- != 0) {
            uint16 tileID; data5.pop(tileID);
            jjPIXELMAP tile(32, 32);
            for (int y = 0; y < 32; ++y)
                for (int x = 0; x < 32; ++x)
                    data5.pop(tile[x,y]);
            tile.save(tileID, true);
        }
        data5.pop(numberOfObjects);
        while (numberOfObjects-- != 0) {
            uint16 tileID; data5.pop(tileID);
            jjMASKMAP tile;
            for (int y = 0; y < 32; ++y)
                for (int x = 0; x < 32; ++x)
                    data5.pop(tile[x,y]);
            tile.save(tileID, true);
        }

        data5.pop(pshort);
        for (uint i = 1; i <= 9; ++i) {
            jjWEAPON@ weapon = jjWeapons[i];
            data5.pop(pbool);
            data5.pop(pint); weapon.maximum = pint;
            data5.pop(pbyte); weapon.comesFromBirds = pbyte != 0; weapon.comesFromBirdsPowerup = pbyte == 2;
            data5.pop(pbyte); weapon.comesFromGunCrates = pbyte != 0;
            data5.pop(pbyte); weapon.gemsLost = pbyte;
            data5.pop(pbyte); weapon.gemsLostPowerup = pbyte;
            data5.pop(pbyte); weapon.infinite = pbyte & 1 == 1; weapon.replenishes = pbyte & 2 == 2;
            uint8 ammoCrateEventID = 0;
            if (i >= 7) {
                data5.pop(ammoCrateEventID);
                if (ammoCrateEventID > 32) {
                    jjOBJ@ preset = jjObjectPresets[ammoCrateEventID];
                    if (jjGameConnection == GAME::LOCAL)
                        preset.behavior = AmmoCrate(ammoCrateEventID);
                    else
                        preset.behavior = BEHAVIOR::AMMO15;
                    preset.playerHandling = HANDLING::SPECIAL;
                    preset.scriptedCollisions = false;
                    preset.direction = 1;
                    preset.energy = 1;
                    preset.curFrame = jjAnimations[preset.curAnim = (i == 7) ? (jjAnimSets[ANIM::PICKUPS] + 59) : (jjAnimSets[ANIM::PLUS_COMMON] + i - 8)] + (preset.frameID = 0);
                    preset.killAnim = jjAnimSets[ANIM::AMMO] + 71;
                    preset.eventID = OBJECT::ICEAMMO15;
                    preset.var[2] = 31 + i;
                    preset.var[3] = i - 1;
                    preset.points = 300;
                }
            }
            if (i == 8) {
                data5.pop(pbyte);
                if (pbyte == 0)
                    weapon.spread = SPREAD::GUN8;
                else if (pbyte == 1)
                    weapon.spread = SPREAD::PEPPERSPRAY;
                else if (pbyte >= 2)
                    weapon.spread = SPREAD::NORMAL;
                if (pbyte == 2)
                    weapon.gradualAim = false;
            }
        }

        data5.pop(numberOfObjects);
        if (numberOfObjects != 0) {
            if (jjGameConnection != GAME::LOCAL)
                jjObjectPresets[254].behavior = _replaceMe;
            else
                jjObjectPresets[254].behavior = BEHAVIOR::INACTIVE;
            @_offGridObjects = array<_offgridObject>();
            array<uint8> animSetsToLoad(256, 0);
            animSetsToLoad[OBJECT::BONUSPOST] = ANIM::BONUS;
            animSetsToLoad[OBJECT::SWINGINGVINE] = ANIM::VINE;
            animSetsToLoad[OBJECT::TUFTURT] = ANIM::TUFTUR;
            animSetsToLoad[OBJECT::LABRAT] = ANIM::LABRAT;
            animSetsToLoad[OBJECT::LIZARD] = ANIM::LIZARD;
            animSetsToLoad[OBJECT::FLOATLIZARD] = ANIM::LIZARD;
            animSetsToLoad[OBJECT::SUCKER] = ANIM::SUCKER;
            animSetsToLoad[OBJECT::CATERPILLAR] = ANIM::CATERPIL;
            animSetsToLoad[OBJECT::SMOKERING] = ANIM::CATERPIL;
            animSetsToLoad[OBJECT::CHESHIRE1] = ANIM::CAT;
            animSetsToLoad[OBJECT::CHESHIRE2] = ANIM::CAT2;
            animSetsToLoad[OBJECT::HATTER] = ANIM::HATTER;
            animSetsToLoad[OBJECT::SKELETON] = ANIM::SKELETON;
            animSetsToLoad[OBJECT::DOGGYDOGG] = ANIM::DOG;
            animSetsToLoad[OBJECT::NORMTURTLE] = ANIM::TURTLE;
            animSetsToLoad[OBJECT::TURTLESHELL] = ANIM::TURTLE;
            animSetsToLoad[OBJECT::DEMON] = ANIM::DEMON;
            animSetsToLoad[OBJECT::STEAM] = ANIM::STEAM;
            animSetsToLoad[OBJECT::ROTATINGROCK] = ANIM::ROCK;
            animSetsToLoad[OBJECT::HELMUT] = ANIM::HELMUT;
            animSetsToLoad[OBJECT::BILSY] = ANIM::BILSBOSS;
            animSetsToLoad[OBJECT::BAT] = ANIM::BAT;
            animSetsToLoad[OBJECT::BEE] = ANIM::BUMBEE;
            animSetsToLoad[OBJECT::DRAGONFLY] = ANIM::DRAGFLY;
            animSetsToLoad[OBJECT::FATCHICK] = ANIM::FATCHK;
            animSetsToLoad[OBJECT::FENCER] = ANIM::FENCER;
            animSetsToLoad[OBJECT::FISH] = ANIM::FISH;
            animSetsToLoad[OBJECT::MOTH] = ANIM::MOTH;
            animSetsToLoad[OBJECT::RAPIER] = ANIM::RAPIER;
            animSetsToLoad[OBJECT::SPARK] = ANIM::SPARK;
            animSetsToLoad[OBJECT::LEFTPADDLE] = ANIM::PINBALL;
            animSetsToLoad[OBJECT::RIGHTPADDLE] = ANIM::PINBALL;
            animSetsToLoad[OBJECT::FIVEHUNDREDBUMP] = ANIM::PINBALL;
            animSetsToLoad[OBJECT::CARROTBUMP] = ANIM::PINBALL;
            animSetsToLoad[OBJECT::QUEEN] = ANIM::QUEEN;
            animSetsToLoad[OBJECT::FLOATSUCKER] = ANIM::SUCKER;
            animSetsToLoad[OBJECT::BRIDGE] = ANIM::BRIDGE;
            animSetsToLoad[OBJECT::MONKEY] = ANIM::MONKEY;
            animSetsToLoad[OBJECT::STANDMONKEY] = ANIM::MONKEY;
            animSetsToLoad[OBJECT::RAVEN] = ANIM::RAVEN;
            animSetsToLoad[OBJECT::TUBETURTLE] = ANIM::TUBETURT;
            animSetsToLoad[OBJECT::SMALLTREE] = ANIM::SMALTREE;
            animSetsToLoad[OBJECT::DIAMONDUSPOLE] = ANIM::DIAMPOLE;
            animSetsToLoad[OBJECT::PSYCHPOLE] = ANIM::PSYCHPOLE;
            animSetsToLoad[OBJECT::CARROTUSPOLE] = ANIM::CARROTPOLE;
            animSetsToLoad[OBJECT::JUNGLEPOLE] = ANIM::JUNGLEPOLE;
            animSetsToLoad[OBJECT::UTERUS] = ANIM::UTERUS;
            animSetsToLoad[OBJECT::UTERUSSPIKEBALL] = ANIM::UTERUS;
            animSetsToLoad[OBJECT::CRAB] = ANIM::UTERUS;
            animSetsToLoad[OBJECT::ROBOT] = ANIM::ROBOT;
            animSetsToLoad[OBJECT::DEVANROBOT] = ANIM::DEVAN;
            animSetsToLoad[OBJECT::FRUITPLATFORM] = ANIM::FRUITPLAT;
            animSetsToLoad[OBJECT::BOLLPLATFORM] = ANIM::BOLLPLAT;
            animSetsToLoad[OBJECT::GRASSPLATFORM] = ANIM::GRASSPLAT;
            animSetsToLoad[OBJECT::PINKPLATFORM] = ANIM::PINKPLAT;
            animSetsToLoad[OBJECT::SONICPLATFORM] = ANIM::SONICPLAT;
            animSetsToLoad[OBJECT::SPIKEPLATFORM] = ANIM::SPIKEPLAT;
            animSetsToLoad[OBJECT::SPIKEBOLL] = ANIM::SPIKEBOLL;
            animSetsToLoad[OBJECT::SPIKEBOLL3D] = ANIM::SPIKEBOLL3D;
            animSetsToLoad[OBJECT::EVA] = ANIM::EVA;
            animSetsToLoad[OBJECT::WITCH] = ANIM::WITCH;
            animSetsToLoad[OBJECT::ROCKETTURTLE] = ANIM::ROCKTURT;
            animSetsToLoad[OBJECT::BUBBA] = ANIM::BUBBA;
            animSetsToLoad[OBJECT::DEVILDEVAN] = ANIM::DEVILDEVAN;
            animSetsToLoad[OBJECT::TUFBOSS] = ANIM::TUFBOSS;
            animSetsToLoad[OBJECT::BIGROCK] = ANIM::BIGROCK;
            animSetsToLoad[OBJECT::BIGBOX] = ANIM::BIGBOX;
            animSetsToLoad[OBJECT::BOLLY] = ANIM::SONCSHIP;
            animSetsToLoad[OBJECT::BUTTERFLY] = ANIM::BUTTERFLY;
            animSetsToLoad[OBJECT::BEEBOY] = ANIM::BEEBOY;
            animSetsToLoad[OBJECT::XMASNORMTURTLE] = ANIM::XTURTLE;
            animSetsToLoad[OBJECT::XMASLIZARD] = ANIM::XLIZARD;
            animSetsToLoad[OBJECT::XMASFLOATLIZARD] = ANIM::XLIZARD;
            animSetsToLoad[OBJECT::XMASBILSY] = ANIM::XBILSY;
            animSetsToLoad[OBJECT::CAT] = ANIM::ZDOG;
            animSetsToLoad[OBJECT::PACMANGHOST] = ANIM::ZSPARK;
            do {
                uint16 xPos; data5.pop(xPos);
                uint16 yPos; data5.pop(yPos);
                int32 params; data5.pop(params);
                _offGridObjects.insertLast(_offgridObject(xPos, yPos, params));
                uint8 eventID = params;
                if (eventID == OBJECT::GENERATOR) eventID = params >> 12;
                if (animSetsToLoad[eventID] != 0) {
                    jjOBJ@ preset = jjObjectPresets[eventID];
                    if (preset.curAnim < 100) {
                        preset.curFrame = jjAnimations[preset.determineCurAnim(animSetsToLoad[eventID], preset.curAnim)] + preset.frameID;
                        if ((eventID >= OBJECT::FRUITPLATFORM && eventID <= OBJECT::SPIKEBOLL3D) || eventID == OBJECT::WITCH)
                            preset.killAnim += jjAnimSets[animSetsToLoad[eventID]];
                        else if (eventID == OBJECT::CATERPILLAR && jjObjectPresets[OBJECT::SMOKERING].curAnim < 100)
                            jjObjectPresets[OBJECT::SMOKERING].determineCurAnim(ANIM::CATERPIL, jjObjectPresets[OBJECT::SMOKERING].curAnim);
                    }
                    animSetsToLoad[eventID] = 0;
                }
            } while (--numberOfObjects != 0);
        }

        if (!data5.isEmpty()) {
            jjDebug('MLLE::Setup: Warning, Data5 longer than expected');
        }
        
        return true;
    }

    jjLAYER@ GetLayer(const string &in name) {
        jjLAYER@ handle = null;
        _layers.get(name, @handle);
        return handle;
    }
    uint8 GetPaletteMappingID(const string &in name) {
        uint8 mappingID;
        _palettes.get(name, mappingID);
        return mappingID;
    }
    jjPAL@ GetPalette(const string &in name) {
        if (name == 'Level Palette')
            return Palette;
        return jjSpriteModeGetColorMapping(GetPaletteMappingID(name));
    }

    void ReapplyPalette() {
        Palette.apply();
    }

    class AmmoCrate : jjBEHAVIORINTERFACE {
        uint8 realEventID;
        AmmoCrate(uint8 r) { realEventID = r; }
        bool onIsSolid(jjOBJ@) { return true; }
        void onBehave(jjOBJ@ obj) {
            if (obj.state == STATE::DEACTIVATE)
                obj.eventID = realEventID;
            obj.behave(BEHAVIOR::AMMO15);
        }
    }

    void SpawnOffgrids() {
        if (jjGameConnection == GAME::LOCAL) {
            SpawnOffgridsLocal();
            for (int y = 0; y < jjLayerHeight[4]; ++y)
            for (int x = 0; x < jjLayerWidth[4]; ++x) {
                const int ev = jjParameterGet(x,y, -12,32);
                if (ev == 0)
                    return;
                else if (ev == int(0xFFFFF3FE))
                    jjParameterSet(x,y, -12,32, 0);
            }
        } else {
            for (uint i = 0; i < _offGridObjects.length; ++i) {
                const auto params = _offGridObjects[i].params;
                uint8 eventID = params;
                if (eventID == OBJECT::GENERATOR || eventID == OBJECT::GUNCRATE || eventID == OBJECT::CARROTCRATE || eventID == OBJECT::ONEUPCRATE || eventID == OBJECT::BOMBCRATE || eventID == OBJECT::SPRINGCRATE)
                    eventID = params >> 12;
                if ((eventID >= OBJECT::APPLE && eventID <= OBJECT::STRAWBERRY) || (eventID >= OBJECT::LEMON && eventID <= OBJECT::CHEESE)) {
                    jjSugarRushAllowed = true;
                } else switch (eventID) {
                    case OBJECT::BOUNCERAMMO3:
                    case OBJECT::BOUNCERAMMO15:
                    case OBJECT::GUNCRATE:
                    case OBJECT::GUNBARREL:
                        jjWeapons[WEAPON::BOUNCER].allowed = true;
                        break;
                    case OBJECT::ICEAMMO3:
                    case OBJECT::ICEAMMO15:
                        jjWeapons[WEAPON::ICE].allowed = true;
                        break;
                    case OBJECT::SEEKERAMMO3:
                    case OBJECT::RFAMMO3:
                    case OBJECT::TOASTERAMMO3:
                    case OBJECT::TNTAMMO3:
                    case OBJECT::GUN8AMMO3:
                    case OBJECT::GUN9AMMO3:
                        jjWeapons[eventID - 31].allowed = true;
                        break;
                    case OBJECT::SEEKERAMMO15:
                    case OBJECT::RFAMMO15:
                    case OBJECT::TOASTERAMMO15:
                        jjWeapons[eventID - 51].allowed = true;
                        break;
                    case OBJECT::BLASTERPOWERUP:
                    case OBJECT::BOUNCERPOWERUP:
                    case OBJECT::ICEPOWERUP:
                    case OBJECT::SEEKERPOWERUP:
                    case OBJECT::RFPOWERUP:
                    case OBJECT::TOASTERPOWERUP: {
                        const auto weaponID = eventID - 130;
                        jjWeapons[weaponID].allowed = jjWeapons[weaponID].allowedPowerup = true;
                        break; }
                    case OBJECT::TNTPOWERUP:
                    case OBJECT::GUN8POWERUP:
                    case OBJECT::GUN9POWERUP: {
                        const auto weaponID = eventID - 212;
                        jjWeapons[weaponID].allowed = jjWeapons[weaponID].allowedPowerup = true;
                        break; }
                }
            }
        }
    }
    void SpawnOffgridsLocal() {
        for (uint i = 0; i < _offGridObjects.length; ++i)
            _spawnOffgrid(i);
    }
    class _offgridObject {
        float xPos, yPos;
        int32 params;
        _offgridObject() {}
        _offgridObject(uint16 x, uint16 y, int32 p) { xPos = x; yPos = y; params = p; }
    }
    void _spawnOffgrid(uint i) {
        const _offgridObject@ og = _offGridObjects[i];
        const int difficulty = og.params & 0x300;
        if (difficulty != 0 && (jjGameMode == GAME::SP || jjGameMode == GAME::COOP || jjGameConnection == GAME::LOCAL)) {
            if (difficulty == 0x100) {
                if (jjDifficulty > 0)
                    return;
            } else if (difficulty == 0x200) {
                if (jjDifficulty < 2)
                    return;
            } else {
                if (jjGameConnection == GAME::LOCAL && jjLocalPlayerCount == 1)
                    return;
            }
        }
        const uint xTile = uint(og.xPos) >> 5, yTile = uint(og.yPos) >> 5;
        const uint8 eventID = og.params;
        if ((eventID <= 32 || eventID == AREA::SUCKERTUBE || eventID == AREA::WATERBLOCK || eventID == AREA::WARPTARGET || eventID == AREA::PATH || eventID == OBJECT::CTFBASE || eventID == AREA::NOFIREZONE || eventID == AREA::TRIGGERZONE || eventID == AREA::TEXT || eventID == AREA::WATERLEVEL || eventID == AREA::MORPHFROG || eventID == 255) && cast<jjVOIDFUNCOBJ>(jjObjectPresets[eventID].behavior) is null) {
            jjDebug('MLLE::Setup: Warning, Event #' + eventID + ' (at ' + xTile + ',' + yTile + ') should not be placed off-grid.');
        } else {
            const int realEvent = jjParameterGet(xTile,yTile, -12,32);
            jjParameterSet(xTile,yTile, -12,32, og.params);
            jjOBJ@ obj = jjObjects[jjAddObject(eventID, og.xPos, og.yPos, 0, CREATOR::LEVEL)];
            jjParameterSet(xTile,yTile, -12,32, realEvent);
            if (jjGameConnection == GAME::LOCAL) {
                obj.deactivates = false;
                obj.creatorID = 1;
            }
        }
    }
    uint _replaceMeIndex = 0;
    void _replaceMe(jjOBJ@ obj) {
        jjParameterSet(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, -12,32, 0);
        obj.delete();
        _spawnOffgrid(_replaceMeIndex++);
    }

    jjPALCOLOR _colorFromArgb(uint Argb) {
        return jjPALCOLOR(Argb >> 16, Argb >> 8, Argb >> 0);
    }

    void _readPalette(jjSTREAM& stream, jjPAL& palette) {
        for (uint i = 0; i < 256; ++i) {
            stream.pop(palette.color[i].red);
            stream.pop(palette.color[i].green);
            stream.pop(palette.color[i].blue);
        }
    }

    uint _read7BitEncodedUintFromStream(jjSTREAM& stream) {
        uint result = 0;
        while (true) {
            uint8 byteRead; stream.pop(byteRead);
            result |= (byteRead & 0x7F);
            if (byteRead >= 0x80)
                result <<= 7;
            else
                break;
        }
        return result;
    }
    string _read7BitEncodedStringFromStream(jjSTREAM& stream) {
        string result;
        stream.get(result, _read7BitEncodedUintFromStream(stream));
        return result;
    }

    void _recolorAnimationIf(jjSTREAM& stream, ANIM::Set set, uint animID, uint frameCount) {
        bool pbool; stream.pop(pbool); if (!pbool) return;

        if (jjAnimSets[set] == 0)
            jjAnimSets[set].load();
        const uint firstFrameID = jjAnimations[jjAnimSets[set] + animID];
        array<uint8> colors(256);
        for (uint i = 0; i < 256; ++i)
            stream.pop(colors[i]);
        for (uint i = 0; i < frameCount; ++i) {
            jjANIMFRAME@ frame = jjAnimFrames[firstFrameID + i];
            jjPIXELMAP(frame).recolor(colors).save(frame);
        }
    }
}