Downloads containing BL18_level04.j2as

Downloads
Name Author Game Mode Rating
JJ2+ Only: Foo Single Player 2/14:...Featured Download Violet CLM Single player 10 Download file

File preview

  1. #include "MLLE-Include-1.4.asc"
  2. const bool MLLESetupSuccessful = MLLE::Setup();
  3. #pragma require "BL18_level04-MLLE-Data-2.j2l"
  4. #pragma require "BL18_level04-MLLE-Data-1.j2l"
  5. #pragma require "Hustar3.j2t"
  6. #pragma require "bndesktop82405.j2t"
  7. #pragma require "gloweeb-fix.j2t"
  8. #pragma require "eyes.j2t"
  9. #pragma require "mez03a.j2t"
  10. #pragma require "mezmerize.j2t"
  11. #pragma require "BL18_level04.j2l"
  12. #pragma require "scary1.mp3"
  13. #pragma require "nr-underthine.it"
  14. #pragma require "benjamins_rock.s3m"
  15.  
  16. #include "BL18.asc"
  17.  
  18. const uint TimeTarget = 5500;
  19. void onLevelLoad() {
  20.         TrueColor::EnableCaching();
  21.         BL18::Setup();
  22.         @BL18::myLevelReload = RefreshStatsAfterBoss;
  23.         @BL18::myCoinWarpFunction = TurnOnTriggerThirty;
  24.         @BL18::myHUD = DrawMapWhenPressingTab;
  25.        
  26.         BN(jjObjectPresets[OBJECT::MORPH]);
  27.         Cow(jjObjectPresets[OBJECT::MILK]);
  28.        
  29.         jjObjectPresets[OBJECT::FIVEHUNDREDBUMP].behavior = GemBumper;
  30.         jjObjectPresets[OBJECT::FIVEHUNDREDBUMP].killAnim = jjObjectPresets[OBJECT::SPIKEBOLL].killAnim; //chain size
  31.         jjObjectPresets[OBJECT::FIVEHUNDREDBUMP].xAcc = -1;
  32.         jjObjectPresets[OBJECT::FIVEHUNDREDBUMP].deactivates = false;
  33.        
  34.         if (jjAnimSets[ANIM::DEVILDEVAN] != 0)
  35.                 BloodDemon(jjObjectPresets[OBJECT::DEVILDEVAN]);
  36.        
  37.         if (jjAnimSets[ANIM::GRASSPLAT]!=0) {
  38.                 BL18::Recolor(jjAnimations[jjAnimSets[ANIM::GRASSPLAT]], array<uint8>={40}, BL18::RecolorReplace(63));
  39.                 jjObjectPresets[OBJECT::GRASSPLATFORM].deactivates = false;
  40.                 jjObjectPresets[OBJECT::GRASSPLATFORM].behavior = DoublePlatform;
  41.         }
  42.        
  43.         if (jjAnimSets[ANIM::PINKPLAT]!=0) {
  44.                 for (int i = 0; i < 2; ++i)
  45.                         BL18::Recolor(jjAnimations[jjAnimSets[ANIM::PINKPLAT] + i], array<uint8>={88}, BL18::RecolorReplace(152));
  46.                 jjObjectPresets[OBJECT::PINKPLATFORM].deactivates = false;
  47.                 jjObjectPresets[OBJECT::PINKPLATFORM].behavior = DoublePlatform;
  48.         }
  49.        
  50.         array<string> inbetween = ShortcutNames;
  51.         array<uint> newIndices = {0};
  52.         while (newIndices.length < ShortcutNames.length)
  53.                 newIndices.insertAt(jjRandom() % newIndices.length, newIndices.length);
  54.         for (uint i = 0; i < ShortcutNames.length; ++i)
  55.                 ShortcutNames[i] = inbetween[newIndices[i]];
  56.         for (uint i = 0; i < 4; ++i)
  57.                 Conversations[5 + i][1] += ShortcutNames[i];
  58.         //for (uint i = 4; i < 6; ++i)
  59.         //      jjDebug(ShortcutNames[i]);
  60.                
  61.         GenerateShortcutList();
  62.        
  63.         FirstShortcutFrame = jjAnimations[jjAnimSets[TrueColor::FindCustomAnim()].allocate(array<uint>={ShortcutNames.length})];
  64.         for (uint i = 0; i < ShortcutNames.length; ++i) {
  65.                 jjPIXELMAP(newIndices[i]*64, 8*32, 64, 96, 5).save(jjAnimFrames[FirstShortcutFrame+i]);
  66.         }
  67.         DesktopPalette.load("bndesktop82405.j2t");
  68.        
  69.         jjObjectPresets[OBJECT::SWINGINGVINE].behavior = SyncedVine;
  70.         jjObjectPresets[OBJECT::BRIDGE].behavior = BridgeWhenYourGravityIsNormal;
  71.        
  72.         jjObjectPresets[OBJECT::CARROTBUMP].determineCurAnim(ANIM::BAT, 0);
  73.         jjObjectPresets[OBJECT::CARROTBUMP].frameID = 4;
  74.         jjObjectPresets[OBJECT::CARROTBUMP].determineCurFrame();
  75.         jjObjectPresets[OBJECT::CARROTBUMP].playerHandling = HANDLING::PARTICLE;
  76.         jjObjectPresets[OBJECT::CARROTBUMP].behavior = Batform;
  77.         jjObjectPresets[OBJECT::CARROTBUMP].ySpeed = -1;
  78.        
  79.         jjObjectPresets[OBJECT::BUBBLER].behavior = Portal();
  80.         jjObjectPresets[OBJECT::BUBBLER].playerHandling = HANDLING::PARTICLE;
  81.         jjObjectPresets[OBJECT::BUBBLER].curFrame = jjObjectPresets[OBJECT::MILK].curFrame;
  82.         jjObjectPresets[OBJECT::BUBBLER].light = 1;
  83.         jjObjectPresets[OBJECT::BUBBLER].lightType = LIGHT::FLICKER;
  84.        
  85.         jjObjectPresets[OBJECT::RFAMMO15].behavior = PasswordMonitor();
  86.         jjObjectPresets[OBJECT::RFAMMO15].deactivates = false;
  87.         jjObjectPresets[OBJECT::RFAMMO15].playerHandling = HANDLING::PARTICLE;
  88.         jjObjectPresets[OBJECT::RFAMMO15].light = 6;
  89.         jjObjectPresets[OBJECT::RFAMMO15].lightType = LIGHT::BRIGHT;
  90.        
  91.         jjObjectPresets[OBJECT::ICEAMMO15].behavior = QuestionMarkBlock();
  92.         jjObjectPresets[OBJECT::ICEAMMO15].playerHandling = HANDLING::PICKUP;
  93.         jjObjectPresets[OBJECT::ICEAMMO15].scriptedCollisions = true;
  94.        
  95.         if (jjDifficulty > 0) {
  96.                 jjObjectPresets[OBJECT::SPIKEBOLL].energy = 0; //unkillable
  97.                 jjObjectPresets[OBJECT::SPIKEBOLL].deactivates = false;
  98.                 if (jjDifficulty > 1)
  99.                         jjObjectPresets[OBJECT::SPIKEBOLL].behavior = DoublePlatform;
  100.                 for (int i = 0; i < 2; ++i) {
  101.                         const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::SPIKEBOLL] + i];
  102.                         BL18::Recolor(anim, array<uint8>={72}, BL18::RecolorReplace(80));
  103.                         BL18::Recolor(anim, array<uint8>={24,48}, BL18::RecolorReplace(152));
  104.                 }
  105.         } else
  106.                 jjObjectPresets[OBJECT::SPIKEBOLL].behavior = BEHAVIOR::INACTIVE; //no spike bolls on easy
  107.        
  108.         jjObjectPresets[OBJECT::EXTRATIME].behavior = FieldName;
  109.         jjObjectPresets[OBJECT::EXTRATIME].playerHandling = HANDLING::PARTICLE;
  110.        
  111.         jjObjectPresets[OBJECT::TNTDESTRUCTSCENERY].behavior = FireOrIce;
  112.        
  113.         jjObjectPresets[OBJECT::FASTFEET].behavior = MarioPlatform;
  114.         jjObjectPresets[OBJECT::FASTFEET].playerHandling = HANDLING::PARTICLE;
  115.         {
  116.                 jjANIMFRAME@ marioFrame = jjAnimFrames[jjObjectPresets[OBJECT::FASTFEET].curFrame];
  117.                 jjPIXELMAP(1*32 - 8, 111*32 + 12, 96 + 8*2, 8, 5).save(marioFrame);
  118.                 marioFrame.hotSpotX = int(marioFrame.width) / -2;
  119.                 marioFrame.hotSpotY = -4;
  120.         }
  121.        
  122.         jjObjectPresets[OBJECT::TUFBOSS].determineCurAnim(ANIM::TUFBOSS, 5);
  123.         jjObjectPresets[OBJECT::TUFBOSS].determineCurFrame();
  124.         jjObjectPresets[OBJECT::TUFBOSS].direction = -1;
  125.         jjObjectPresets[OBJECT::TUFBOSS].behavior = MyTufBoss;
  126.        
  127.         jjObjectPresets[OBJECT::ONEUPCRATE].behavior = GemColorWall;
  128.         jjObjectPresets[OBJECT::ONEUPCRATE].playerHandling = HANDLING::PARTICLE;
  129.  
  130.         jjObjectPresets[OBJECT::ROTATINGROCK].behavior = Basketball();
  131.         jjObjectPresets[OBJECT::ROTATINGROCK].scriptedCollisions = true;
  132.         jjObjectPresets[OBJECT::ROTATINGROCK].deactivates = true;
  133.         jjObjectPresets[OBJECT::ROTATINGROCK].curFrame = jjAnimations[jjAnimSets[ANIM::ROCK].load(BL18::AnimSets::Basketball, "BL18.j2a")];
  134.        
  135.         //jjObjectPresets[OBJECT::LIME].behavior = WarpSpikes();
  136.        
  137.         if (jjDifficulty == 0)
  138.                 jjEventSet(115,82, 0); //friend introducing smart bombs
  139.        
  140.         //for (int i = 32; i < 256; ++i)
  141.         //      jjObjectPresets[i].deactivates = false;
  142.        
  143.         {
  144.                 jjANIMFRAME@ mapFrame = jjAnimFrames[MapFrameID];
  145.                 mapFrame.hotSpotX = 0;
  146.                 mapFrame.hotSpotY = 0;
  147.         }
  148.        
  149.         for (int x = 0; x < jjLayerWidth[4]; ++x)
  150.                 for (int y = 0; y < jjLayerHeight[4]; ++y)
  151.                         if (jjEventGet(x,y) == 32) { //room corner
  152.                                 const uint roomID = jjParameterGet(x,y, 0,9);
  153.                                 if (Rooms.length < roomID+1)
  154.                                         Rooms.resize(roomID+1);
  155.                                 Rooms[roomID].FoundEvent(x,y);
  156.                                 Rooms[roomID].ID = roomID;
  157.                         } //else if (jjEventGet(x,y) == OBJECT::TRIGGERSCENERY || jjEventGet(x,y) == OBJECT::BANANA)
  158.                                 //jjDebug(jjParameterGet(x,y,0,5) + "");
  159.                        
  160.         @BGRotate = jjPIXELMAP(0,0, 256,256, 5);
  161.         @BGWarp = jjPIXELMAP(256,0, 256,256, 5);
  162.         jjUseLayer8Speeds = true;
  163. }
  164.  
  165. const uint BNFriendID = 9;
  166. int LastFood = 0, LastLives = 0;
  167. bool BNBeaten = false;
  168. void RefreshStatsAfterBoss() {
  169.         if (LastFood != 0 || LastLives != 0)  { //should only be true if you died while fighting the boss, in which case, remove references to killing teh BNs
  170.                 BL18::Player.food = LastFood;
  171.                 BL18::Player.lives = LastLives;
  172.                 LastFood = LastLives = 0;
  173.         }
  174.         @PlayerRoom = null;
  175.         lastSubscreenWidth = lastSubscreenHeight = 0; //force AdjustMaxResolution call
  176. }
  177.  
  178. class BN : BL18::Enemy {
  179.         array<jjOBJ@> BNs(8, null);
  180.         uint halfNameWidth;
  181.         string name;
  182.         BN(jjOBJ@ preset) {
  183.                 super(preset);
  184.                 preset.energy = 120; //let's try this
  185.                 preset.playerHandling = HANDLING::PARTICLE; //inactive
  186.                 preset.isTarget = false;
  187.                 //preset.deactivates = false; //except in case of death
  188.                 name = BL18::FriendNames[BNFriendID];
  189.                 halfNameWidth = jjGetStringWidth("1" + name, STRING::SMALL, STRING::NORMAL) / 2;
  190.                 gender = BL18::Gender::Male;
  191.         }
  192.         void onBehave(jjOBJ@ obj) override {
  193.                 if (obj.state == STATE::DEACTIVATE)
  194.                         obj.deactivate();
  195.                 else if (obj.creatorType == CREATOR::LEVEL) { //health
  196.                         if (obj.state == STATE::START) {
  197.                                 if (BL18::Player.bossActivated) {
  198.                                         BL18::Player.boss = obj.objectID;
  199.                                         obj.state = STATE::ACTION;
  200.                                         LastFood = BL18::Player.food;
  201.                                         LastLives = BL18::Player.lives;
  202.                                         for (int i = 0; i < 8; ++i)
  203.                                                 @BNs[i] = jjObjects[jjAddObject(obj.eventID, obj.xPos + (-6 + i * 2) * 32, obj.yPos - (8 * 32), obj.objectID, CREATOR::OBJECT)];
  204.                                 }
  205.                         } else {
  206.                                 if (obj.counter < 5000 && (obj.counter += (1 + jjDifficulty)) % 600 == 60) {
  207.                                         jjOBJ@ obj2 = BNs[obj.counter / 600];
  208.                                         obj2.isTarget = true;
  209.                                         obj2.state = STATE::FADEIN;
  210.                                         obj2.determineCurAnim(ANIM::JAZZ, RABBIT::TELEPORTFALL);
  211.                                         obj2.curFrame = jjAnimations[obj2.curAnim];
  212.                                         jjSample(obj2.xPos, obj2.yPos, SOUND::COMMON_TELPORT2);
  213.                                 }
  214.                                 obj.energy = 0;
  215.                                 for (uint i = 0; i < 8; ++i) {
  216.                                         jjOBJ@ obj2 = BNs[i];
  217.                                         if (cast<jjBEHAVIORINTERFACE>(obj2.behavior) is this)
  218.                                                 obj.energy += obj2.energy;
  219.                                 }
  220.                                 if (obj.energy <= 0) {
  221.                                         obj.delete();
  222.                                         onFunction22(BL18::Player, false);
  223.                                         BNBeaten = true;
  224.                                 }
  225.                         }
  226.                 } else switch (obj.state) {
  227.                         case STATE::START:
  228.                                 obj.energy = jjObjectPresets[obj.eventID].energy / 8;
  229.                                 break;
  230.                         case STATE::FREEZE:
  231.                                 if (--obj.freeze == 0)
  232.                                         obj.state = obj.oldState;
  233.                                 break;
  234.                         case STATE::FADEIN:
  235.                                 obj.frameID = ++obj.counter >> 2;
  236.                                 if (obj.frameID >= int(jjAnimations[obj.curAnim].frameCount)) {
  237.                                         obj.state = STATE::FALL;
  238.                                         obj.determineCurAnim(ANIM::JAZZ, RABBIT::TELEPORTFALLING);
  239.                                         obj.frameID = obj.counter = 0;
  240.                                         obj.playerHandling = HANDLING::SPECIAL;
  241.                                 } else
  242.                                         obj.curFrame = jjAnimations[obj.curAnim] + obj.frameID;
  243.                                 break;
  244.                         case STATE::FALL:
  245.                                 obj.yPos += obj.ySpeed += 0.1;
  246.                                 if (jjMaskedVLine(int(obj.xPos), int(obj.yPos), 12)) {
  247.                                         obj.state = STATE::IDLE;
  248.                                         obj.determineCurAnim(ANIM::JAZZ, RABBIT::STAND);
  249.                                         obj.curFrame = jjAnimations[obj.curAnim];
  250.                                         obj.yPos -= 40;
  251.                                         obj.putOnGround(true);
  252.                                         obj.yOrg = obj.yPos;
  253.                                 } else {
  254.                                         obj.frameID = jjGameTicks >> 3;
  255.                                         obj.determineCurFrame();
  256.                                 }
  257.                                 break;
  258.                         case STATE::IDLE:
  259.                                 if (++obj.counter >= 40 && (jjRandom() & 31 == 0)) {
  260.                                         obj.state = STATE::WALK;
  261.                                         obj.determineCurAnim(ANIM::JAZZ, RABBIT::RUN1);
  262.                                         obj.xSpeed = 0;
  263.                                         obj.counter = 0;
  264.                                 }
  265.                                 break;
  266.                         case STATE::WALK:
  267.                                 if (++obj.counter > 175 && obj.counterEnd == 0 && (jjRandom() & 127 == 0)) {
  268.                                         obj.state = STATE::FIRE;
  269.                                         obj.determineCurAnim(ANIM::JAZZ, RABBIT::FIRE);
  270.                                         obj.lightType = LIGHT::LASER;
  271.                                         obj.light = 1;
  272.                                         obj.counter = 0;
  273.                                         obj.direction = BL18::Player.xPos > obj.xPos ? 1 : -1;
  274.                                 } else {
  275.                                         const float acceleration = (BL18::IsMurderer ? 0.08 : 0.05) * (BL18::Player.running ? 1.5 : 1);
  276.                                         if (BL18::Player.xPos > obj.xPos + 40) {
  277.                                                 obj.xSpeed += acceleration * ((obj.direction == 1) ? 1 : 2);
  278.                                         } else if (BL18::Player.xPos < obj.xPos - 40) {
  279.                                                 obj.xSpeed -= acceleration * ((obj.direction == -1) ? 1 : 2);
  280.                                         }
  281.                                         const float targetX = obj.xPos + obj.xSpeed;
  282.                                         if (jjMaskedPixel(int(targetX), int(obj.yPos)))
  283.                                                 obj.xSpeed = 0;
  284.                                         else {
  285.                                                 obj.xPos = targetX;
  286.                                                 obj.direction = (obj.xSpeed > 0) ? 1 : -1;
  287.                                         }
  288.                                         if (obj.counterEnd == 0) {
  289.                                                 obj.ySpeed += abs(obj.xSpeed);
  290.                                                 if (abs(obj.xSpeed) >= 0.3) {
  291.                                                         obj.frameID = int(obj.ySpeed) >> 4;
  292.                                                         obj.determineCurFrame();
  293.                                                 } else
  294.                                                         obj.curFrame = jjAnimations[jjAnimSets[ANIM::JAZZ] + RABBIT::STAND];
  295.                                                 if (abs(obj.yPos - BL18::Player.yPos) < 20 && jjRandom() & 255 == 0) {
  296.                                                         obj.counterEnd = 4;
  297.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_JUMP);
  298.                                                 }
  299.                                         } else {
  300.                                                 if (obj.counterEnd < 128)
  301.                                                         obj.curFrame = jjAnimations[jjAnimSets[ANIM::JAZZ] + RABBIT::RIGHTJUMP] + (obj.counterEnd >> 4);
  302.                                                 else
  303.                                                         obj.curFrame = jjAnimations[jjAnimSets[ANIM::JAZZ] + RABBIT::RIGHTFALL] + ((jjGameTicks >> 2) % 3);
  304.                                                 obj.yPos = obj.yOrg - jjSin((obj.counterEnd += 4) << 1) * 80;
  305.                                         }
  306.                                 }
  307.                                 break;
  308.                         case STATE::FIRE:
  309.                                 obj.curFrame = jjAnimations[obj.curAnim] + ((jjGameTicks >> 2) & 1);
  310.                                 if (++obj.counter > 70) {
  311.                                         jjOBJ@ laser = jjObjects[jjAddObject(OBJECT::LASER, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT)];
  312.                                         laser.playerHandling = HANDLING::ENEMYBULLET;
  313.                                         laser.xSpeed = obj.direction << 3;
  314.                                         laser.ySpeed = jjSin((jjGameTicks + (obj.objectID << 2)) << 5);
  315.                                         if (laser.ySpeed == 0.f) laser.ySpeed = 0.0001f; //some weird jj2 thing
  316.                                         if (obj.counter > 80 && abs(atan2(laser.ySpeed, laser.xSpeed) - atan2(BL18::Player.yPos - obj.yPos, BL18::Player.xPos - obj.xPos)) < 0.1)
  317.                                                 BL18::Player.hurt(BL18::IsMurderer ? 2 : 1);
  318.                                         obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::AMMO_LASER,obj.doesHurt);
  319.                                         if (obj.counter >= 140) {
  320.                                                 obj.state = STATE::IDLE;
  321.                                                 obj.determineCurAnim(ANIM::JAZZ, RABBIT::STAND);
  322.                                                 obj.curFrame = jjAnimations[obj.curAnim];
  323.                                                 obj.counter = 0;
  324.                                                 obj.lightType = LIGHT::NONE;
  325.                                         }
  326.                                 } else
  327.                                         obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::COMMON_SHIELD4,obj.doesHurt);
  328.                                
  329.                                 break;
  330.                 }
  331.         }
  332.         void onDraw(jjOBJ@ obj) override {
  333.                 if ((obj.creatorType == CREATOR::OBJECT) && (obj.state != STATE::START)) {
  334.                         if (obj.freeze == 0 && obj.justHit == 0) {
  335.                                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::PLAYER, BL18::FirstFriendPlayerID + BNFriendID);
  336.                                 if (obj.state == STATE::DONE) {
  337.                                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TRANSLUCENTCOLOR, 48);
  338.                                         drawHearts(obj);
  339.                                 }
  340.                         } else
  341.                                 Enemy::onDraw(obj); //meh
  342.                         jjDrawString(obj.xPos - halfNameWidth, obj.yPos - 36, (obj.state == STATE::DONE ? "1||||" : "1") + name);
  343.                 }
  344.         }
  345. }
  346.  
  347. int LastGemGravityReverse = 0;
  348. void GemBumper(jjOBJ@ obj) {
  349.         if (obj.state == STATE::START) {
  350.                 obj.var[8] = FindRoom(obj.xOrg, obj.yOrg).ID;
  351.                 obj.special = ((Rooms[obj.var[8]].LastGemColor += 1) %= 3);
  352.                 const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
  353.                 if (jjParameterGet(xTile,yTile, 8,4) != 0) {
  354.                         obj.behave(BEHAVIOR::PLATFORM, false);
  355.                         obj.var[9] = obj.var[0];
  356.                         obj.var[10] = obj.counter;
  357.                         if ((obj.age = jjParameterGet(xTile,yTile, -2, 1)) == 1 && obj.creatorType == CREATOR::LEVEL) { //mobile quad
  358.                                 obj.lightType = LIGHT::NONE;
  359.                                 int sync = jjParameterGet(xTile,yTile, 0, 2);
  360.                                 for (int i = 0; i < 3; ++i) {
  361.                                         jjParameterSet(xTile, yTile, 0,2, (sync += 1) &= 3);
  362.                                         jjAddObject(obj.eventID, obj.xOrg, obj.yOrg);
  363.                                 }
  364.                         }
  365.                 } else
  366.                         obj.deactivates = true;
  367.                 obj.xAcc = obj.xOrg;
  368.                 obj.state = STATE::SLEEP;
  369.         } else if (obj.state == STATE::ACTION) {
  370.                 jjPLAYER@ hitPlayer = jjPlayers[obj.var[0]];
  371.                 hitPlayer.buttstomp = 121;
  372.                 hitPlayer.specialMove = 0;
  373.                 obj.special = (obj.special + 1) % 3;
  374.                 /*if (PlayerRoom.Type == RoomTypes::Dethjump && jjGameTicks-LastGemGravityReverse > 10) {
  375.                         hitPlayer.antiGrav = !hitPlayer.antiGrav;
  376.                         hitPlayer.ySpeed = 0;
  377.                         jjSetLayerYSpeed(8, hitPlayer.antiGrav ? 1 : -1, true);
  378.                         LastGemGravityReverse = jjGameTicks;
  379.                 }*/
  380.         } else if (obj.state == STATE::DEACTIVATE) {
  381.                 if (obj.xAcc > 0)
  382.                         obj.xOrg = obj.xAcc;
  383.                 obj.deactivate();
  384.                 return;
  385.         }
  386.        
  387.         obj.behave(BEHAVIOR::BUMP, false);
  388.         //if (PlayerRoom.Type == RoomTypes::VutxfyCaves) {
  389.                 if (obj.var[2] != 0) {
  390.                         const auto realState = obj.state;
  391.                         const auto realVar0 = obj.var[0];
  392.                         const auto realCounter = obj.counter;
  393.                         obj.var[0] = obj.var[9];
  394.                         obj.counter = obj.var[10];
  395.                         obj.eventID = OBJECT::SPIKEBOLL; //can't be stood upon
  396.                         obj.state = STATE::ACTION;
  397.                         obj.behave(BEHAVIOR::PLATFORM, false);
  398.                         obj.eventID = OBJECT::FIVEHUNDREDBUMP; //don't hurt when touched
  399.                         obj.var[9] = obj.var[0];
  400.                         obj.var[10] = obj.counter;
  401.                         obj.state = realState;
  402.                         obj.counter = realCounter;
  403.                         obj.var[0] = realVar0;
  404.                        
  405.                         const float xStep = (obj.xPos - obj.xOrg) / obj.var[2];
  406.                         const float yStep = (obj.yPos - obj.yOrg) / obj.var[2];
  407.                         float xPos = obj.xOrg, yPos = obj.yOrg;
  408.                         for (int i = 0; i < obj.var[2]; ++i, xPos += xStep, yPos += yStep)
  409.                                 jjDrawResizedSprite(xPos, yPos, ANIM::PICKUPS, 22, obj.frameID, 0.5,0.5, SPRITE::GEM, (obj.special + i) % 3);
  410.                         if (obj.age != 0 && PlayerRoom.ID == uint(obj.var[8])) {
  411.                                 if (jjEventGet(int(obj.xOrg += obj.age) >> 5, int(obj.yOrg) >> 5) == 255)
  412.                                         obj.age *= -1;
  413.                         }
  414.                 }
  415.         //}
  416.         jjDrawSprite(obj.xPos, obj.yPos - ((obj.state == STATE::BOUNCE) ? 4 : 0), ANIM::PICKUPS, 34, obj.freeze == 0 ? jjGameTicks>>3 : 0, 1, obj.freeze == 0 ? SPRITE::GEM : SPRITE::FROZEN, obj.special);
  417. }
  418.  
  419. const uint MapFrameID = jjObjectPresets[OBJECT::SEEKERAMMO15].curFrame; //doesn't particularly matter which sprite so long as it's something otherwise unused
  420. const uint MapMultiplier = 2;
  421. jjPIXELMAP MapImage(jjLayerWidth[4] / MapMultiplier, jjLayerHeight[4] / MapMultiplier);
  422.  
  423. enum RoomTypes { Hustar, Crossroads, Dethmaze, VutxfyCaves, Dethjump, CrystalFalls, WiseGuru, FalseProphet, Desktop, Zerox };
  424. const array<string> RoomNames = {"Forth", "Charnel Crossroads", "Dethmaze", "Vutxfy Caves", "Dethjump", "Crystal Falls", "Wise Guru`s@@Cathedral"};
  425. const array<uint8> RoomMapColors = {16, 35, 40, 88, 48, 32, 42, 80, 64, 24};
  426. int LastRoomName = -1;
  427. class Room {
  428.         float Top = -1, Bottom, Left, Right;
  429.         uint Width = 0, Height;
  430.         uint ID;
  431.         bool Mapped = false;
  432.         uint LastGemColor = 0;
  433.         RoomTypes Type = RoomTypes(0);
  434.         Room(){}
  435.         void FoundEvent(uint x, uint y) {
  436.                 if (Top == -1) { //first
  437.                         Left = x*32;
  438.                         Top = y*32;
  439.                         Type = RoomTypes(jjParameterGet(x,y, 9, 4));
  440.                 } else if (Width == 0) { //second
  441.                         if (y == 194 && Type == RoomTypes::Dethjump) //bottomless pit
  442.                                 y = 195;
  443.                         Right = (x+1)*32;
  444.                         Bottom = (y+1)*32;
  445.                         Width = uint(Right-Left);
  446.                         Height = uint(Bottom-Top);
  447.                         //jjDebug(Left + "," + Top + " to " + Right + "," + Bottom);
  448.                         if (Type == 0) //maybe wasn't set by other corner
  449.                                 Type = RoomTypes(jjParameterGet(x,y, 9, 4));
  450.                 } else {
  451.                         jjDebug("Invalid room event at " + x + "," + y + "!");
  452.                 }
  453.         }
  454.         bool Contains(float x, float y) const {
  455.                 return x >= Left && x < Right && y >= Top && y < Bottom;
  456.         }
  457.         bool Map() const {
  458.                 if (!Mapped) {
  459.                         const uint
  460.                                 left = uint(Left/32),
  461.                                 right = uint(Right/32),
  462.                                 top = uint(Top/32),
  463.                                 bottom = uint(Bottom/32);
  464.                         for (uint x = left; x < right; x += MapMultiplier)
  465.                                 for (uint y = top; y < bottom; y += MapMultiplier)
  466.                                         MapImage[x / MapMultiplier,y / MapMultiplier] = jjTileGet(4,x,y) == 0 ? 0 : RoomMapColors[Type];
  467.                         MapImage.save(jjAnimFrames[MapFrameID]);
  468.                         return Rooms[ID].Mapped = true;
  469.                 }
  470.                 return false;
  471.         }
  472. }
  473. array<Room> Rooms;
  474. const Room@ PlayerRoom = null;
  475. const Room@ NextPlayerRoom = null;
  476. bool NoRoomAlertSounded = false;
  477. const Room@ FindRoom(float x, float y, const Room@ defaultRoom = null) {
  478.         if (defaultRoom !is null && defaultRoom.Contains(x,y))
  479.                 return defaultRoom;
  480.         for (uint i = 0; i < Rooms.length; ++i)
  481.                 if (Rooms[i].Contains(x,y))
  482.                         return Rooms[i];
  483.         //else...
  484.         if (!NoRoomAlertSounded) {
  485.                 NoRoomAlertSounded = true;
  486.                 jjDebug("No room found at " + x + "," + y + "!");
  487.         }
  488.         return null;
  489. }
  490.  
  491. float LastPlayerX, LastPlayerY;
  492. bool goingUpFromRoomTransition = false;
  493. class RoomTransition : BL18::Popup {
  494.         uint LastPlayerCurFrame;
  495.         int LastPlayerDirection; //just in case
  496.         int playerXSpeed = 0, playerYSpeed = 0;
  497.         RoomTransition(){
  498.                 const jjPLAYER@ player = BL18::Player;
  499.                 LastPlayerX = player.xPos;
  500.                 LastPlayerY = player.yPos;
  501.                 LastPlayerCurFrame = player.curFrame;
  502.                 LastPlayerDirection = player.direction;
  503.                 if (player.antiGrav)
  504.                         LastPlayerDirection ^= 0x40;
  505.                 BL18::Player.spriteMode = SPRITE::INVISIBLE;
  506.                 if (PlayerRoom !is null && NextPlayerRoom !is null && player.warpID == 0) {
  507.                         if (NextPlayerRoom.Left >= PlayerRoom.Right)
  508.                                 playerXSpeed = 4;
  509.                         else if (NextPlayerRoom.Right <= PlayerRoom.Left)
  510.                                 playerXSpeed = -4;
  511.                         if (NextPlayerRoom.Top >= PlayerRoom.Bottom)
  512.                                 playerYSpeed = !player.antiGrav ? 2 : -11;
  513.                         else if (NextPlayerRoom.Bottom <= PlayerRoom.Top) {
  514.                                 playerYSpeed = !player.antiGrav ? -11 : 2;
  515.                         }
  516.                 }
  517.                 jjSample(player.xPos, player.yPos, SOUND::COMMON_ITEMTRE, 63, 7000);
  518.         }
  519.        
  520.         int counter = 248;
  521.         bool Do() {
  522.                 jjPLAYER@ player = BL18::Player;
  523.                 player.xPos = LastPlayerX;
  524.                 player.yPos = LastPlayerY;
  525.                 if ((counter -= 8) == 0) { //moment of maximum opacity: do all loading here
  526.                         if (PlayerRoom.ID == 70 && player.warpID == 0) {//Type == RoomTypes::Dethmaze) {
  527.                                 LastPlayerX = PlayerRoom.Left + (PlayerRoom.Right - PlayerRoom.Left) / 2;
  528.                                 LastPlayerY = PlayerRoom.Top + (PlayerRoom.Bottom - PlayerRoom.Top) / 2 - 20;
  529.                                 LastPlayerCurFrame = jjAnimations[jjAnimSets[ANIM::JAZZ] + RABBIT::STAND];
  530.                                 playerXSpeed = playerYSpeed = 0;
  531.                                 @NextPlayerRoom = @PlayerRoom;
  532.                                 for (int i = 1; i <= 4; ++i) {
  533.                                         const uint rand = jjRandom() % 3;
  534.                                         MLLE::GetLayer("DM " + i + "G").hasTiles = (rand == 1);
  535.                                         MLLE::GetLayer("DM " + i + "B").hasTiles = (rand == 2);
  536.                                 }
  537.                                 if (
  538.                                         (NextDethmazeDir == DMD::Right && player.xPos > LastPlayerX+128) ||
  539.                                         (NextDethmazeDir == DMD::Left && player.xPos < LastPlayerX-128) ||
  540.                                         (NextDethmazeDir == DMD::Down && player.yPos > LastPlayerY+128) ||
  541.                                         (NextDethmazeDir == DMD::Up && player.yPos < LastPlayerY-128)
  542.                                 ) {
  543.                                         if ((DethmazeSuccessScore += 1) >= 13) {
  544.                                                 player.warpToID(95, true);
  545.                                                 LastPlayerX = player.xPos;
  546.                                                 LastPlayerY = player.yPos;
  547.                                                 @NextPlayerRoom = @PlayerRoom = @FindRoom(LastPlayerX,LastPlayerY);
  548.                                         }
  549.                                 } else if (DethmazeSuccessScore > 0)
  550.                                         DethmazeSuccessScore -= 1;
  551.                                 //else
  552.                                         //player.hurt(1, true);
  553.                                 GenerateShortcutList();
  554.                         } else {
  555.                                 if (NextPlayerRoom !is null)
  556.                                         ZoneTransition(PlayerRoom.Type, NextPlayerRoom.Type);
  557.                                 @PlayerRoom = @NextPlayerRoom;
  558.                                 if (NextPlayerRoom.ID == 32 && !SeenBNIntro)
  559.                                         @BL18::CurrentPopup = BNIntro();
  560.                         }
  561.                         AdjustMaxResolution();
  562.                 }
  563.                 if (counter == -248) {
  564.                         player.spriteMode = SPRITE::PLAYER;
  565.                         player.xSpeed = playerXSpeed;
  566.                         player.ySpeed = playerYSpeed;
  567.                         if (playerYSpeed < 0) {
  568.                                 goingUpFromRoomTransition = true;
  569.                                 player.keyJump = true;
  570.                         }
  571.                         return false;
  572.                 }
  573.                 jjDrawSpriteFromCurFrame(LastPlayerX,LastPlayerY, LastPlayerCurFrame,LastPlayerDirection, SPRITE::PLAYER, player.playerID);
  574.                 return true;
  575.         }
  576.         void Draw(jjCANVAS@ canvas) const {
  577.                 canvas.drawRectangle(0,0, jjSubscreenWidth,jjSubscreenHeight, 0, SPRITE::BLEND_NORMAL, uint8(248-abs(counter)));
  578.         }
  579.        
  580.         bool DrawHUD() const { return true; }
  581. }
  582.  
  583. void AdjustMaxResolution() {
  584.         int roomWidth = 800, roomHeight = 600;
  585.         if (PlayerRoom !is null) {
  586.                 if (PlayerRoom.Width < 800)
  587.                         roomWidth = PlayerRoom.Width;
  588.                 if (PlayerRoom.Height < 600)
  589.                         roomHeight = PlayerRoom.Height;
  590.         } else
  591.                 BL18::Player.cameraUnfreeze();
  592.         if (roomWidth != jjResolutionMaxWidth || roomHeight != jjResolutionMaxHeight) {
  593.                 jjChat("/maxresolution " + roomWidth + "x" + roomHeight);
  594.                 for (int i = 0; i < 8; ++i)
  595.                         jjAlert(""); //move commands out of onscreen chatlog
  596.         }
  597. }
  598.  
  599. jjPIXELMAP@ BGRotate, BGWarp;
  600. void ZoneTransition(RoomTypes old, RoomTypes new) {
  601.         //for (uint i = jjObjectCount; i-- > 0;)
  602.         //      jjObjects[i].deactivate();
  603.         {
  604.                 const uint
  605.                         left = uint(NextPlayerRoom.Left/32),
  606.                         right = uint(NextPlayerRoom.Right/32),
  607.                         top = uint(NextPlayerRoom.Top/32),
  608.                         bottom = uint(NextPlayerRoom.Bottom/32);
  609.                 for (uint x = left; x < right; ++x)
  610.                         for (uint y = top; y < bottom; ++y) {
  611.                                 const uint ev = jjParameterGet(x,y, -12,12);
  612.                                 const uint8 eventID = ev;
  613.                                 if (eventID > 32 && jjObjectPresets[eventID].isActive && !jjObjectPresets[eventID].deactivates && ev & 2048 == 0) {
  614.                                         jjAddObject(eventID, x*32+16, y*32+15, 0,CREATOR::LEVEL);
  615.                                         jjParameterSet(x,y, -1,1,1);
  616.                                 }
  617.                         }
  618.         }
  619.                
  620.         jjSetWaterLevel(jjLayerHeight[4] * 32 + 32, true);
  621.         BL18::Player.lighting = new == RoomTypes::Hustar ? 100 : new == RoomTypes::WiseGuru ? 33 : 75;
  622.        
  623.         if (old == new)
  624.                 return;
  625.                
  626.         jjWaterLighting = (new != RoomTypes::CrystalFalls || BL18::Player.xPos < 310*32) ? WATERLIGHT::GLOBAL : WATERLIGHT::LAGUNICUS;
  627.        
  628.         for (uint i = 1; i <= 4; ++i)
  629.                 MLLE::GetLayer("Waterfall " + i).hasTiles = (new == RoomTypes::CrystalFalls);
  630.         for (uint i = 1; i <= 3; ++i)
  631.                 MLLE::GetLayer("Lava " + i).hasTiles = (new == RoomTypes::Zerox);
  632.         jjLayerHasTiles[7] = (new != RoomTypes::Dethjump);
  633.        
  634.         if (/*new == RoomTypes::Hustar || */new == RoomTypes::VutxfyCaves) { //although...
  635.                 MLLE::GetLayer("Waterfall 2").hasTiles = true;
  636.                 MLLE::GetLayer("Waterfall 2").spriteMode = SPRITE::BLEND_LUMINANCE;
  637.                 MLLE::GetLayer("Waterfall 3").spriteMode = SPRITE::NORMAL;
  638.         } else if (new == RoomTypes::CrystalFalls)
  639.                 MLLE::GetLayer("Waterfall 2").spriteMode = SPRITE::NORMAL;
  640.                
  641.         MLLE::GetLayer("Bloodrayne").hasTiles = new == RoomTypes::WiseGuru;
  642.         MLLE::GetLayer("Fog").hasTiles = new == RoomTypes::FalseProphet || new == RoomTypes::Dethmaze;
  643.        
  644.         //jjCharacters[CHAR::JAZZ].airJump = (new != RoomTypes::Dethjump) ? AIR::HELICOPTER : AIR::NONE;
  645.        
  646.         jjTexturedBGUsed = true;
  647.         jjLayerYAutoSpeed[8] = 0;
  648.         jjTexturedBGFadePositionY = (new != RoomTypes::Dethjump) ? 0.5 : 0.9;
  649.         if (new == RoomTypes::Hustar) {
  650.                 jjTexturedBGTexture = TEXTURE::NORMAL;
  651.                 jjSetFadeColors(15);
  652.         } else if (new == RoomTypes::VutxfyCaves) {
  653.                 BGRotate.makeTexture();
  654.         } else if (new == RoomTypes::Dethjump) {
  655.                 BGWarp.makeTexture();
  656.                 jjSetFadeColors(79);
  657.                 jjSetLayerYSpeed(8, BL18::Player.antiGrav ? 1 : -1, true);
  658.         } else {
  659.                 jjTexturedBGUsed = false;
  660.         }
  661.                
  662.         jjTexturedBGStyle = new == RoomTypes::VutxfyCaves ? TEXTURE::TILEMENU : TEXTURE::WARPHORIZON;
  663.                
  664.         if (old == RoomTypes(-1) || old == RoomTypes::Hustar || new == RoomTypes::Hustar || old == RoomTypes::Desktop || new == RoomTypes::Desktop) { //palette change... besides those two, all use the same palette.
  665.                 if (new == RoomTypes::Desktop)
  666.                         jjPalette = DesktopPalette;
  667.                 else
  668.                         jjPalette = MLLE::Palette;
  669.                        
  670.                 if (new == RoomTypes::Hustar)
  671.                         jjPalette.gradient(jjPALCOLOR(0,127,255), jjPALCOLOR(0,31,63));
  672.                        
  673.                 if (BL18::IsMurderer)
  674.                         jjPalette.fill(jjPALCOLOR(), 0.15);
  675.                 jjPalette.apply();
  676.                 TrueColor::ProcessPalette();
  677.         }
  678.        
  679.         jjBEHAVIORINTERFACE@ intermediate;
  680.         for (uint i = jjObjectCount; --i > 0;) { //do some cleanup of orgasmed enemies because there are just so many of them
  681.                 jjOBJ@ enemy = jjObjects[i];
  682.                 if (enemy.state == STATE::DONE && ((@intermediate = cast<jjBEHAVIORINTERFACE@>(enemy.behavior)) !is null) && cast<BL18::Enemy>(intermediate) !is null)
  683.                         enemy.delete();
  684.         }
  685. }
  686.  
  687. const array<string> FallingFromParadiseStrings = {
  688.         "IT'S WHALER TIME!!!", "@You will@          leave a@        small crater", "@@@@@@@@Who@is@the@RED@KING?", "Race for the Vodka", "Tuturu!", "@@@@@@@@yAY A SHIELD THNX", "hacked@by@@@               chinese", "fun@over@safety", "@@@@@@A CERTAIN SEXY GIRL", "@I've taken the Spear of Selene", "@@@@@@can't stop@won't stop", "BOOP BEEP BEEP,@YOU CHEAP DICK.","@@@@@@@@@@@@@@lori is a bori","@@@the password is@  \"RF\""
  689. };
  690.  
  691. int lastSubscreenWidth = 0, lastSubscreenHeight = 0;
  692. bool PressingTabOneTickAgo = false;
  693. bool PressingTab = false;
  694. bool PressingJumpOneTickAgo = false;
  695. void onPlayerInput(jjPLAYER@ play) {
  696.         const bool PressingJump = play.keyJump;
  697.         if (BL18::CurrentPopup is null && (@NextPlayerRoom = @FindRoom(play.xPos, play.yPos, PlayerRoom)) !is PlayerRoom) {
  698.                 if (PlayerRoom !is null)
  699.                         @BL18::CurrentPopup = RoomTransition();
  700.                 else {
  701.                         @PlayerRoom = @NextPlayerRoom;
  702.                         ZoneTransition(RoomTypes(-1), NextPlayerRoom.Type);
  703.                 }
  704.                 NextPlayerRoom.Map();
  705.         }
  706.         if (lastSubscreenWidth != jjSubscreenWidth || lastSubscreenHeight != jjSubscreenHeight) {
  707.                 lastSubscreenWidth = jjSubscreenWidth;
  708.                 lastSubscreenHeight = jjSubscreenHeight;
  709.                 AdjustMaxResolution();
  710.         }
  711.         if (PlayerRoom !is null) {
  712.                 const int cameraWidth = lastSubscreenWidth - jjBorderWidth*2, cameraHeight = lastSubscreenHeight - jjBorderHeight*2;
  713.                 float cameraX = play.xPos - cameraWidth/2;
  714.                 float cameraY = play.yPos - cameraHeight/2;
  715.                 if (cameraX < PlayerRoom.Left) cameraX = PlayerRoom.Left;
  716.                 else if (cameraX + cameraWidth > PlayerRoom.Right) cameraX = PlayerRoom.Right - cameraWidth;
  717.                 if (cameraY < PlayerRoom.Top) cameraY = PlayerRoom.Top;
  718.                 else if (cameraY + cameraHeight > PlayerRoom.Bottom) cameraY = PlayerRoom.Bottom - cameraHeight;
  719.                 play.cameraFreeze(cameraX, cameraY, false, true);
  720.                 if (PlayerRoom.ID == 49)
  721.                         play.keyRight = false;
  722.                 else if (PlayerRoom.ID == 56) {
  723.                         play.keyDown = play.keyJump = false;
  724.                         play.ySpeed = 3.5;
  725.                         if (jjGameTicks % 21 == 5)
  726.                                 play.showText("@@@@@#" + FallingFromParadiseStrings[jjRandom() % FallingFromParadiseStrings.length]);
  727.                 }
  728.                 jjTriggers[29] = BL18::IsMurderer;
  729.                
  730.                 if (PlayerRoom.Type == RoomTypes::Dethjump) {
  731.                         /*if (play.keyJump || play.stoned != 0) {
  732.                                 play.keyJump = false;
  733.                                 if (BL18::IsPlayerOnGround() && play.ySpeed == 0) {
  734.                                         play.ySpeed = 4;
  735.                                         play.antiGrav = !play.antiGrav;
  736.                                         if (play.platform != 0) {
  737.                                                 jjObjects[play.platform].clearPlatform();
  738.                                                 play.platform = 0;
  739.                                         }
  740.                                         //if (play.platform != 0)
  741.                                         //      play.yPos += play.antiGrav ? 12 : -12;
  742.                                         jjSetLayerYSpeed(8, play.antiGrav ? 1 : -1, true);
  743.                                         jjSample(play.xPos, play.yPos, SOUND::COMMON_JUMP);
  744.                                 }
  745.                         }
  746.                         const auto curAnim = play.curAnim - jjAnimSets[play.setID];
  747.                         if ((curAnim == RABBIT::TELEPORTFALL || curAnim == RABBIT::TELEPORTSTAND) && int(play.xPos) & 31 == 0) {
  748.                                 play.xPos += 16;
  749.                                 play.yPos += 16;
  750.                         }*/
  751.                 } else if (PlayerRoom.Type == RoomTypes::CrystalFalls) {
  752.                         MLLE::GetLayer("Waterfall 4").yOffset = -jjWaterLevel + 40;
  753.                 } else if (PlayerRoom.Type == RoomTypes::Crossroads) {
  754.                         const auto interval = jjGameTicks & 255;
  755.                         if (interval == 0)
  756.                                 jjLayerHasTiles[7] = false;
  757.                         else if (interval == 8)
  758.                                 jjLayerHasTiles[7] = true;
  759.                 }
  760.                
  761.                 if (goingUpFromRoomTransition && (goingUpFromRoomTransition = (play.ySpeed <= 0))) {
  762.                         play.keyJump = true;
  763.                         BL18::PlayerLastSpeed = play.ySpeed;
  764.                 }
  765.         }
  766.        
  767.         PressingTabOneTickAgo = PressingTab;
  768.         PressingTab = jjKey[9];
  769.         if (GrailFound && PressingTab && !PressingTabOneTickAgo && !BL18::CoinWarpFound && BL18::CurrentPopup is null && play.curAnim - jjAnimSets[play.setID] == RABBIT::STAND) {
  770.                 PressingTabOneTickAgo = true;
  771.                 @BL18::CurrentPopup = PortalSelection(0);
  772.         }
  773.        
  774.         if (RaptorClawFound) {
  775.                 if (play.keyJump && !PressingJumpOneTickAgo && RaptorClawStage < 2 && (jjGameTicks - RaptorClawLastSlidingTick) < 10) {
  776.                         RaptorClawStage = 2;
  777.                         jjSample(play.xPos, play.yPos, SOUND::COMMON_JUMP);
  778.                         play.ySpeed = play.jumpStrength;
  779.                         play.helicopter = 0;
  780.                 } else if (RaptorClawStage > 1) {
  781.                         if (++RaptorClawStage == 20)
  782.                                 RaptorClawStage = 0;
  783.                         else {
  784.                                 play.keyRight = RaptorClawFacingRight;
  785.                                 play.keyLeft = !RaptorClawFacingRight;
  786.                                 play.keyJump = true;
  787.                                 play.helicopter = 0;
  788.                         }
  789.                 } else {
  790.                         const auto anim = play.curAnim - jjAnimSets[play.setID];
  791.                         const int xPosToTest = int(play.xPos) + (play.keyRight ? 13 : -13);
  792.                         uint16 tileIDToTest;
  793.                         if (
  794.                                 (anim == RABBIT::FALL || anim == RABBIT::HELICOPTER) &&
  795.                                 (
  796.                                         (play.keyRight && jjMaskedPixel(xPosToTest, int(play.yPos)-8) && jjMaskedPixel(xPosToTest, int(play.yPos)+8)) ||
  797.                                         (play.keyLeft && jjMaskedPixel(xPosToTest, int(play.yPos)-8) && jjMaskedPixel(xPosToTest, int(play.yPos)+8))
  798.                                 ) &&
  799.                                 ((tileIDToTest = jjTileGet(4, xPosToTest>>5, int(play.yPos)>>5)) < 426 || tileIDToTest > 499)
  800.                         ) {
  801.                                 play.helicopter = 0;
  802.                                 RaptorClawStage = 1;
  803.                                 RaptorClawFacingRight = play.keyLeft;
  804.                                 play.ySpeed = 0.25;
  805.                                 play.keyJump = false;
  806.                                 RaptorClawLastSlidingTick = jjGameTicks;
  807.                         } else if (RaptorClawStage == 1)
  808.                                 RaptorClawStage = 0;
  809.                 }
  810.                 if (play.invisibility = RaptorClawStage == 1) {
  811.                         const int vDir = !play.antiGrav ? 1 : -1;
  812.                         jjDrawRotatedSprite(play.xPos - play.direction * 3, play.yPos, ANIM::JAZZ, RABBIT::SKID1, jjGameTicks>>4, 256 * play.direction * vDir, -play.direction, vDir, SPRITE::PLAYER,0);
  813.                 }
  814.         }
  815.         PressingJumpOneTickAgo = PressingJump;
  816. }
  817.  
  818. void DrawMapWhenPressingTab(jjPLAYER@ play, jjCANVAS@ canvas) {
  819.         if (PressingTab) {
  820.                 if (!GrailFound) {
  821.                         canvas.drawRectangle(0, 0, jjResolutionWidth, jjResolutionHeight, 0, SPRITE::SHADOW);
  822.                         DrawMap(canvas, -1);
  823.                 }
  824.         } else if (jjTriggers[20] && !BL18::CoinWarpFound && BL18::CurrentPopup is null)
  825.                 canvas.drawString(110, jjResolutionHeight-6, "1Tab for " + (GrailFound ? "Grail" : "Map"));
  826. }
  827. void DrawMap(jjCANVAS@ canvas, int selectedPortalID) {
  828.         if (PlayerRoom !is null && (PlayerRoom.ID == 49 || PlayerRoom.ID == 58 || PlayerRoom.ID == 70)) //non-euclidean
  829.                 return;
  830.         const uint mapOriginX = jjSubscreenWidth/2 - jjLayerWidth[4]/2;
  831.         const uint mapOriginY = 16;
  832.         canvas.drawResizedSpriteFromCurFrame(mapOriginX,mapOriginY, MapFrameID, MapMultiplier,MapMultiplier);
  833.         for (int i = PortalLocations.length; i-- > 0;) {
  834.                 const auto@ location = PortalLocations[i];
  835.                 if (location !is null && (i != selectedPortalID || jjGameTicks & 7 < 4)) {
  836.                         canvas.drawTile(mapOriginX - 16 + int(location.x), mapOriginY - 16 + int(location.y), 1307);
  837.                 }
  838.         }
  839.         for (int i = BL18::CollectibleLocations.length; i-- > 0;) {
  840.                 const auto data = BL18::CollectibleLocations[i];
  841.                 if (!data.Found) {
  842.                         const auto location = data.Location;
  843.                         if (data.Type == BL18::CollectibleTypes::Coin)
  844.                                 canvas.drawResizedSpriteFromCurFrame(mapOriginX + int(location.x) / 32, mapOriginY + int(location.y) / 32, BL18::CoinFirstFrame + ((jjGameTicks >> 3) & 7), 0.33,0.33);
  845.                         else
  846.                                 canvas.drawResizedSpriteFromCurFrame(mapOriginX + int(location.x) / 32, mapOriginY + int(location.y) / 32, BL18::KeyFrame, 0.5,0.5, SPRITE::SINGLEHUE, BL18::KeyColors[data.Type-1]);
  847.                 }
  848.         }
  849.         canvas.drawResizedSprite(mapOriginX - 9 + int(BL18::Player.xPos / 32), mapOriginY + 6 + int(BL18::Player.yPos / 32), ANIM::FACES,3,0, 0.5,0.5);
  850. }
  851.  
  852. class BloodDemon : BL18::Enemy {
  853.         BloodDemon(jjOBJ@ preset) {
  854.                 super(preset);
  855.                 preset.energy = 4;
  856.                 Layer = 3;
  857.                 const array<uint> animsToReplace = {0,5,7};
  858.                 for (uint animID = 0; animID < animsToReplace.length; ++animID) {
  859.                         jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::DEVILDEVAN] + animsToReplace[animID]];
  860.                         BL18::Recolor(anim, array<uint8>={16}, BL18::RecolorReplace(136)); //green to "red"
  861.                         Resize::Resize(anim, 1.5, Resize::Method::Scale2x);
  862.                 }
  863.         }              
  864.         void onBehave(jjOBJ@ obj) override {
  865.                 if (obj.state == STATE::START) {
  866.                         obj.doesHurt = FindRoom(obj.xPos, obj.yPos).ID;
  867.                         obj.state = STATE::FLY;
  868.                         obj.behave(BEHAVIOR::DEVILDEVAN, false);
  869.                 } else if (obj.state == STATE::DEACTIVATE)
  870.                         obj.deactivate();
  871.                 else if (obj.state != STATE::DONE && BL18::CurrentPopup is null && @PlayerRoom is Rooms[obj.doesHurt]) {
  872.                         obj.behave(BEHAVIOR::DEVILDEVAN, false);
  873.                         if (BL18::IsMurderer)
  874.                                 obj.behave(BEHAVIOR::DEVILDEVAN, false);
  875.                         if (obj.state != STATE::FREEZE) {
  876.                                 jjPARTICLE@ blood = jjAddParticle(PARTICLE::ICETRAIL);
  877.                                 blood.xPos = obj.xPos + (jjRandom() & 63) - 31;
  878.                                 blood.yPos = obj.yPos - (jjRandom() & 31);
  879.                                 blood.icetrail.color = 24;
  880.                                 blood.icetrail.colorStop = 32;
  881.                         }
  882.                 }
  883.         }
  884. }
  885.  
  886.  
  887. const uint NumberOfBitsDevotedToSyncParameter = 3; //this should correspond to the number of bits you assign to the Sync parameter in your JCS.ini (or MLLE equivalent) entry for Swinging Vine. So for example if you give it a parameter Sync:2, this variable should ALSO equal 2.
  888.  
  889. void SyncedVine(jjOBJ@ obj) {
  890.         if (obj.state == STATE::START) {
  891.                 obj.var[1] = 128; //vine length--set this in whatever way appeals to you, but a constant 128 is the value that native swinging vines use.
  892.                
  893.                 if (lastSwingingVineLUTLength != obj.var[1]) { //need to generate LUT by doing the same math swinging vine objects do
  894.                         lastSwingingVineLUTLength = obj.var[1];
  895.                         PossibleVineVariableConfigurations = array<array<int>> = {{obj.var[1] * 256, 0}};
  896.                         while (true) {
  897.                                 const array<int>@ oldConfiguration = @PossibleVineVariableConfigurations[PossibleVineVariableConfigurations.length-1];
  898.                                 array<int> newConfiguration(2);
  899.                                 newConfiguration[1] = oldConfiguration[1] + ((oldConfiguration[0] > 0) ? -32 : 32);
  900.                                 newConfiguration[0] = oldConfiguration[0] + newConfiguration[1];
  901.                                 if (newConfiguration[1] == 0 && newConfiguration[0] == obj.var[1] * 256) //gone full circle
  902.                                         break;
  903.                                 PossibleVineVariableConfigurations.insertLast(newConfiguration);
  904.                         }
  905.                 }
  906.                
  907.                 const array<int>@ syncedConfiguration = PossibleVineVariableConfigurations[(jjGameTicks + (jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, NumberOfBitsDevotedToSyncParameter) * (PossibleVineVariableConfigurations.length / (1 << NumberOfBitsDevotedToSyncParameter)))) % PossibleVineVariableConfigurations.length];
  908.                 for (uint i = 0; i < 2; ++i)
  909.                         obj.var[2 + i] = syncedConfiguration[i];
  910.                        
  911.                 obj.state = STATE::ACTION;
  912.         }
  913.        
  914.         obj.playerHandling = BL18::Player.antiGrav ? HANDLING::PARTICLE : HANDLING::SPECIAL;
  915.         obj.behave(BEHAVIOR::SWINGINGVINE);
  916. }
  917. int lastSwingingVineLUTLength = -1;
  918. array<array<int>> PossibleVineVariableConfigurations;
  919.  
  920. void Batform(jjOBJ@ obj) {
  921.         if (obj.state == STATE::DEACTIVATE) {
  922.                 obj.clearPlatform();
  923.                 obj.deactivate();
  924.         } else if (obj.state == STATE::START) {
  925.                 obj.var[0] = FindRoom(obj.xOrg, obj.yOrg).ID;
  926.                 obj.state = STATE::FLY;
  927.         } else {
  928.                 const float lastX = obj.xPos, lastY = obj.yPos;
  929.                 jjDrawSpriteFromCurFrame(lastX, lastY, obj.special, obj.direction); //draw one frame behind to avoid weird jazz-on-bat visual lag
  930.                 obj.xPos = obj.xOrg + jjSin(obj.age += 4) * 80;
  931.                 obj.direction = ((obj.age + 256) & 1023) < 512 ? 1 : -1;
  932.                 obj.frameID = (obj.ySpeed < 0) ? (obj.age >> 4) : 4;
  933.                 obj.special = obj.determineCurFrame(false); //for display, but keep curFrame constant for bePlatform
  934.                 if (obj.ySpeed < 0 && obj.yPos < obj.yOrg - 96)
  935.                         obj.ySpeed = 2;
  936.                 else if (obj.ySpeed > 0 && obj.yPos > obj.yOrg + 96)
  937.                         obj.ySpeed = -1;
  938.                 else {
  939.                         obj.yPos += obj.ySpeed;
  940.                         if (obj.ySpeed < 0 && @Rooms[obj.var[0]] is @PlayerRoom)
  941.                                 obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::BAT_BATFLY1,obj.doesHurt);
  942.                 }
  943.                 obj.bePlatform(lastX, lastY);
  944.         }
  945. }
  946.  
  947. void DoublePlatform(jjOBJ@ obj) {
  948.         if (obj.creatorType == CREATOR::LEVEL) {
  949.                 const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
  950.                 if (jjParameterGet(xTile, yTile, 12,1) == 0) { //circle, not swing
  951.                         jjAddObject(obj.eventID, obj.xOrg, obj.yOrg);
  952.                         jjParameterSet(xTile,yTile, 1,1, jjParameterGet(xTile,yTile, 1,1) ^ 1); //opposite sync
  953.                 }
  954.         }
  955.         const auto@ room = FindRoom(obj.xOrg, obj.yOrg);
  956.         if (room !is null && room.Type == RoomTypes::Dethmaze) {
  957.                 obj.xOrg -= 15;
  958.                 obj.yOrg -= 15;
  959.         }
  960.         obj.behavior = BEHAVIOR::PLATFORM;
  961.         obj.behave();
  962. }
  963.  
  964. array<string> ShortcutNames = {"Jazz 2", "OMFBG", "Microsoft Works", "ImageMixer", "Star Wars Battlefront", "Unreal II"};
  965. jjPAL DesktopPalette;
  966. uint FirstShortcutFrame;
  967. uint DethmazeSuccessScore = 0;
  968. enum DMD { Right,Left,Up,Down }
  969. DMD NextDethmazeDir;
  970. const array<array<uint>> DethmazeShortcutLocations = {
  971.         {398 * 32,179 * 32},
  972.         {390 * 32,179 * 32},
  973.         {394 * 32,175 * 32},
  974.         {394 * 32,183 * 32}
  975. };
  976. array<uint> CurrentShortcutList;
  977. void GenerateShortcutList() {
  978.         CurrentShortcutList.resize(0);
  979.         const uint rand = jjRandom();
  980.         NextDethmazeDir = DMD(rand & 3);
  981.         const uint excludeFalse = (rand >> 2) & 3;
  982.         for (uint i = 0; i < 4; ++i)
  983.                 if (i != excludeFalse)
  984.                         CurrentShortcutList.insertLast(i); //first four are false
  985.         CurrentShortcutList.insertAt(NextDethmazeDir, 4 + ((rand >> 4) & 1)); //last two are true
  986.        
  987.         //jjDebug(CurrentShortcutList[0] + "," + CurrentShortcutList[1] + "," + CurrentShortcutList[2] + "," + CurrentShortcutList[3]);
  988. }
  989.  
  990. const array<string> PortalNames = {"Chasm Entrance", "False Prophet of@Charnel Crossroads", "False Prophet of@Vutxfy Caves", "False Prophet of@Crystal Falls", "False Prophet of@the Dethjump", "Wise Guru", "Hot Spring Secret"};
  991. array<BL18::Point@> PortalLocations(PortalNames.length, null);
  992. class Portal : BL18::Switch {
  993.         void onBehave(jjOBJ@ obj) override {
  994.                 if (obj.state == STATE::START) {
  995.                         const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
  996.                         obj.special = jjParameterGet(xTile, yTile, 0, 4);
  997.                         if (uint(obj.special) >= PortalLocations.length) {
  998.                                 obj.playerHandling = HANDLING::PICKUP;
  999.                                 obj.scriptedCollisions = true;
  1000.                                 if (obj.special == 15) {
  1001.                                         obj.curFrame = BL18::KeyFrame - 2;
  1002.                                         obj.behavior = Grail();
  1003.                                 } else if (obj.special == 14) {
  1004.                                         obj.curFrame = BL18::KeyFrame + 19;
  1005.                                         obj.behavior = Claw();
  1006.                                 }
  1007.                                 return;
  1008.                         }
  1009.                         @PortalLocations[obj.special] = BL18::Point(xTile, yTile);
  1010.                         obj.state = STATE::WAIT;
  1011.                 }
  1012.                 BL18::Switch::onBehave(obj);
  1013.         }
  1014.         void playerPressedT(jjOBJ@ obj) override {
  1015.                 @BL18::CurrentPopup = PortalSelection(obj.special);
  1016.         }
  1017.         void drawMe(const jjOBJ@ obj) override {
  1018.                 const int frameID = (jjGameTicks >> 3) % 3;
  1019.                 const int xOrg = (int(obj.xOrg) & ~31) - 16;
  1020.                 const int yOrg = (int(obj.yOrg) & ~31);
  1021.                 //jjDrawSpriteFromCurFrame(obj.xOrg, obj.yOrg, obj.curFrame);
  1022.                 for (int x = 0; x < 2; ++x)
  1023.                         for (int y = 0; y < 2; ++y)
  1024.                                 jjDrawTile(xOrg + x*32, yOrg + y*32, 920 + x + y*10 + frameID * 20);
  1025.         }
  1026. }
  1027. class PortalSelection : BL18::Popup {
  1028.         uint OriginalID;
  1029.         uint CurrentSelection;
  1030.         bool wasPressingSomething = true;
  1031.         array<bool> PortalsAvailable(PortalNames.length, false);
  1032.         jjTEXTAPPEARANCE Selected(STRING::SPIN);
  1033.        
  1034.         PortalSelection(uint id) {
  1035.                 CurrentSelection = OriginalID = id;
  1036.                 BL18::SavedTriggers[id + 20] = jjTriggers[id + 20] = true;
  1037.                 for (uint i = 0; i < PortalsAvailable.length; ++i)
  1038.                         PortalsAvailable[i] = jjTriggers[i + 20];
  1039.         }
  1040.        
  1041.         bool Do() override {
  1042.                 if (jjKey[8] || (PressingTab && !PressingTabOneTickAgo)) //backspace
  1043.                         return false;
  1044.                 const bool pressingSelect = BL18::Player.keyFire || BL18::Player.keySelect || jjKey[1]; //left mouse button
  1045.                 if (!wasPressingSomething) {
  1046.                         wasPressingSomething = true;
  1047.                         if (pressingSelect) {
  1048.                                 if (!PortalsAvailable[CurrentSelection]) {
  1049.                                         jjSamplePriority(SOUND::COMMON_HORN1);
  1050.                                         return true;
  1051.                                 } else {
  1052.                                         /*if (CurrentSelection != OriginalID) {
  1053.                                                 for (uint i = jjObjectCount; i-- > 0;)
  1054.                                                         jjObjects[i].deactivate();
  1055.                                         }*/
  1056.                                         BL18::Player.warpToTile(int(PortalLocations[CurrentSelection].x), int(PortalLocations[CurrentSelection].y));
  1057.                                         return false;
  1058.                                 }
  1059.                         } else if (jjKey[0x26]) { //up
  1060.                                 if (CurrentSelection == 0)
  1061.                                         CurrentSelection = PortalsAvailable.length - 1;
  1062.                                 else
  1063.                                         CurrentSelection -= 1;
  1064.                         } else if (jjKey[0x28]) { //down
  1065.                                 CurrentSelection += 1;
  1066.                                 CurrentSelection %= PortalsAvailable.length;
  1067.                         } else
  1068.                                 wasPressingSomething = false;
  1069.                 } else if (!(pressingSelect || jjKey[0x26] || jjKey[0x28]))
  1070.                         wasPressingSomething = false;
  1071.                 return true;
  1072.         }
  1073.         void Draw(jjCANVAS@ canvas) const override {
  1074.                 canvas.drawRectangle(0, 0, jjResolutionWidth, jjResolutionHeight, 0, SPRITE::SHADOW);
  1075.                 DrawMap(canvas, CurrentSelection);
  1076.                 //canvas.drawString(0x8000, 40, "||||||||Select a Portal", STRING::LARGE);
  1077.                
  1078.                 const uint xPos = jjSubscreenWidth/2 - 280;
  1079.                 const uint yPos = 230;
  1080.                 for (uint i = 0; i < PortalsAvailable.length; ++i)
  1081.                         canvas.drawString(xPos + (i / 4) * 290, yPos + (i % 4) * 55, (!PortalsAvailable[i] ? "#||||~" : i == CurrentSelection ? "#" : "") + PortalNames[i], STRING::MEDIUM, i == CurrentSelection ? Selected : BL18::ConversationAppearance);
  1082.                 canvas.drawString(10, jjResolutionHeight - 20, "|||||Press Backspace or Tab to cancel.");
  1083.         }
  1084. }
  1085.  
  1086. bool GrailFound = false;
  1087. bool RaptorClawFound = false;
  1088. uint RaptorClawStage = 0;
  1089. bool RaptorClawFacingRight;
  1090. int RaptorClawLastSlidingTick = 0;
  1091.  
  1092. class Grail : jjBEHAVIORINTERFACE {
  1093.         void onBehave(jjOBJ@ obj) {
  1094.                 if (obj.state == STATE::DEACTIVATE)
  1095.                         obj.deactivate();
  1096.                 else
  1097.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + jjSin((obj.objectID*8+jjGameTicks+int(obj.xPos+obj.yPos*256))*16) * 4, obj.curFrame, 1, SPRITE::GEM, 9);
  1098.         }
  1099.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1100.                 GrailFound = true;
  1101.                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_TELPORT2, 63, 5000);
  1102.                 jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)].curAnim = jjObjectPresets[OBJECT::GENERATOR].curAnim;
  1103.                 jjEventSet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0);
  1104.                 jjTileSet(4, 391, 170, 0); //text outside of dethmaze
  1105.                 jjEventSet(391, 170, 0);
  1106.                 for (uint x = 0; x < 3; ++x)
  1107.                         for (uint y = 0; y < 2; ++y)
  1108.                                 jjTileSet(4, 397 + x, 169 + y, 360 + x + y*10); //guru outside of dethmaze
  1109.                 for (uint x = 0; x < 5; ++x)
  1110.                         for (uint y = 0; y < 2; ++y)
  1111.                                 jjTileSet(3, 523 + x, 10 + y, 0); //jesus
  1112.                 jjEventSet(524, 8, 0);
  1113.                 jjEventSet(398, 170, OBJECT::PRETZEL);
  1114.                 obj.delete();
  1115.                 @BL18::CurrentPopup = GrailNotice();
  1116.                 return true;
  1117.         }
  1118. }
  1119. class GrailNotice : BL18::Popup {
  1120.         bool Do() override {
  1121.                 return (!jjKey[8] && !jjKey[0xD]);
  1122.         }
  1123.         void Draw(jjCANVAS@ canvas) const override {
  1124.                 canvas.drawRectangle(0, 0, jjResolutionWidth, jjResolutionHeight, 0, SPRITE::SHADOW);
  1125.                 canvas.drawString(0x8000, 45, "||got the", STRING::MEDIUM);
  1126.                 canvas.drawString(0x8000, 70, "||Unholy Grail", STRING::LARGE);
  1127.                 canvas.drawString(0x8000, 330, "||While standing still, press TAB", STRING::MEDIUM);
  1128.                 canvas.drawString(0x8000, 355, "||to megawarp to any portal.", STRING::MEDIUM);
  1129.                 canvas.drawRotatedSpriteFromCurFrame(jjSubscreenWidth/2, 230, BL18::KeyFrame-2, int(jjSin(jjGameTicks << 3) * 20), 2,2, SPRITE::GEM,9);
  1130.         }
  1131. }
  1132. class Claw : jjBEHAVIORINTERFACE {
  1133.         void onBehave(jjOBJ@ obj) {
  1134.                 if (obj.state == STATE::DEACTIVATE)
  1135.                         obj.deactivate();
  1136.                 else
  1137.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + jjSin((obj.objectID*8+jjGameTicks+int(obj.xPos+obj.yPos*256))*16) * 4, obj.curFrame);
  1138.         }
  1139.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1140.                 RaptorClawFound = true;
  1141.                 jjSample(obj.xPos, obj.yPos, SOUND::LIZARD_LIZ6);
  1142.                 jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)].curAnim = jjObjectPresets[OBJECT::GENERATOR].curAnim;
  1143.                 jjEventSet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0);
  1144.                 obj.delete();
  1145.                 @BL18::CurrentPopup = ClawNotice();
  1146.                 return true;
  1147.         }
  1148. }
  1149. class ClawNotice : BL18::Popup {
  1150.         bool Do() override {
  1151.                 return (!jjKey[8] && !jjKey[0xD]);
  1152.         }
  1153.         void Draw(jjCANVAS@ canvas) const override {
  1154.                 canvas.drawRectangle(0, 0, jjResolutionWidth, jjResolutionHeight, 0, SPRITE::SHADOW);
  1155.                 canvas.drawString(0x8000, 45, "||||got the", STRING::MEDIUM);
  1156.                 canvas.drawString(0x8000, 70, "||||Raptor Claw", STRING::LARGE);
  1157.                 canvas.drawString(0x8000, 330, "||||Press JUMP while sliding", STRING::MEDIUM);
  1158.                 canvas.drawString(0x8000, 355, "||||against a wall to jump again.", STRING::MEDIUM);
  1159.                 canvas.drawString(0x8000, 405, "||||(Can't slide on SHINY blocks.)", STRING::MEDIUM);
  1160.                 canvas.drawRotatedSpriteFromCurFrame(jjSubscreenWidth/2, 230, BL18::KeyFrame+19, int(jjSin(jjGameTicks << 3) * 20), 2,2);
  1161.         }
  1162. }
  1163.  
  1164. const array<array<string>> Passwords = {
  1165.         {"RAVEN", "Neither writing desk nor ravine,@this rantin' bird enemy from Hell@plagues those with rocket powers."},
  1166.         {"ROSE", "On Valentine's Day@you'll want this flower.@Look all around you.@Don't look beneath you."},
  1167.         {"RED", "The color of meat, of fire,@of blood, of desire."},
  1168.         {"RF", "Very quick missiles@the color of the@water behind you;@namers of a climb."},
  1169.         {"R", "What has every password@so far had in common?@Continue the pattern.@Continue the countdown."},
  1170.         {"", "Even the Wise Guru does@not speak when he has@no wisdom at all."},
  1171.         {"33121", "Seek out FIVE@UNNUMBERED MURALS@within the Dethjump area."},
  1172.         {"21332", "Where a mural shows a BLOCK,@ignore the NUMBER here."},
  1173.         {"RABBIT", "The YELLOW EYE ROOM's@password is so large,@only the GRAIL may@read its letters."}
  1174. };
  1175. array<bool> PasswordHintsFound(Passwords.length, false);
  1176. class PasswordMonitor : BL18::Switch {
  1177.         void onBehave(jjOBJ@ obj) override {
  1178.                 if (obj.state == STATE::START) {
  1179.                         obj.doesHurt = jjParameterGet(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, 4, 5); //triggerID
  1180.                         obj.state = STATE::WAIT;
  1181.                         if (obj.yOrg > 138*32) {
  1182.                                 obj.light = 0;
  1183.                                 obj.lightType = LIGHT::NONE;
  1184.                         }
  1185.                 }
  1186.                 if (!jjTriggers[obj.doesHurt])
  1187.                         BL18::Switch::onBehave(obj);
  1188.                 else
  1189.                         jjDrawRectangle(int(obj.xOrg)&~31, int(obj.yOrg)&~31, 32,32, 79);
  1190.         }
  1191.         void playerPressedT(jjOBJ@ obj) override {
  1192.                 const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
  1193.                 const int index1 = jjParameterGet(xTile, yTile, 1, 3) | (jjParameterGet(xTile, yTile, 9, 1) << 3);
  1194.                 const int index2 = jjParameterGet(xTile, yTile, 0, 1);
  1195.                 if (index2 == 1)
  1196.                         PasswordHintsFound[index1] = true;
  1197.                 @BL18::CurrentPopup = PasswordPrompt(index1, index2 == 1, obj.doesHurt);
  1198.         }
  1199.         void drawMe(const jjOBJ@ obj) override { } //tile-based
  1200. }
  1201. class PasswordPrompt : BL18::Popup {
  1202.         array<bool> WasPressingSomething(26+10, false);
  1203.         uint8 TriggerID;
  1204.         const array<string>@ Prompts;
  1205.         bool IsHint, HintFound;
  1206.         string Header,Footer;
  1207.         string Input = "";
  1208.         uint8 ExitCounter = 0;
  1209.         bool CorrectPassword;
  1210.        
  1211.         PasswordPrompt(int passwordID, bool isHint, uint8 triggerID) {
  1212.                 TriggerID = triggerID;
  1213.                 IsHint = isHint;
  1214.                 @Prompts = Passwords[passwordID];
  1215.                 HintFound = PasswordHintsFound[passwordID];
  1216.                 Header = isHint ? "Password Hint" : "Password Prompt";
  1217.                 Footer = isHint ? "Press Backspace" : "Press Enter to Submit";
  1218.                 if (isHint) WasPressingSomething[0] = true;
  1219.                 else WasPressingSomething["T"[0] - "A"[0]] = true;
  1220.         }
  1221.        
  1222.         bool Do() override {
  1223.                 if (jjKey[8]) //backspace
  1224.                         return false;
  1225.                 if (IsHint) {
  1226.                         bool isPressingSomething = false;
  1227.                         for (uint i = 1; i < 0xA5; ++i)
  1228.                                 if (jjKey[i]) {
  1229.                                         isPressingSomething = true;
  1230.                                         break;
  1231.                                 }
  1232.                         if (!WasPressingSomething[0] && isPressingSomething)
  1233.                                 return false;
  1234.                         WasPressingSomething[0] = isPressingSomething;
  1235.                 } else if (ExitCounter == 0) {
  1236.                         if (jjKey[0xD]) {
  1237.                                 ExitCounter = 1;
  1238.                                 Header = "";
  1239.                                 if (CorrectPassword = Input == Prompts[0]) {
  1240.                                         jjSamplePriority(BL18::HPTrigger);
  1241.                                         Footer = "Correct!";
  1242.                                 } else {
  1243.                                         jjSamplePriority(SOUND::COMMON_HORN1);
  1244.                                         Footer = (Input != "SWORDFISH") ? "Incorrect." : "That's-a not it. You no guess it.";
  1245.                                 }
  1246.                                 return true;
  1247.                         }
  1248.                        
  1249.                         string foo = '?';
  1250.                         for (uint i = 0; i < 26; ++i)
  1251.                                 if (jjKey['A'[0] + i]) {
  1252.                                         if (!WasPressingSomething[i]) {
  1253.                                                 foo[0] = 'A'[0] + i;
  1254.                                                 WasPressingSomething[i] = true;
  1255.                                         }
  1256.                                 } else
  1257.                                         WasPressingSomething[i] = false;
  1258.                         for (uint i = 0; i < 10; ++i)
  1259.                                 if (jjKey['0'[0] + i]) {
  1260.                                         if (!WasPressingSomething[i+26]) {
  1261.                                                 foo[0] = '0'[0] + i;
  1262.                                                 WasPressingSomething[i+26] = true;
  1263.                                         }
  1264.                                 } else
  1265.                                         WasPressingSomething[i+26] = false;
  1266.                                        
  1267.                         if (foo != '?') {
  1268.                                 Input += foo;
  1269.                                 jjSamplePriority(SOUND::MENUSOUNDS_TYPE);
  1270.                         }
  1271.                 } else if (++ExitCounter == 70) {
  1272.                         if (CorrectPassword) {
  1273.                                 jjTriggers[TriggerID] = true;
  1274.                                 if (TriggerID == 11) { //weird infinite room
  1275.                                         jjTriggers[10] = true;
  1276.                                         BL18::SavedTriggers[10] = BL18::SavedTriggers[11] = true;
  1277.                                 }
  1278.                                 else if (TriggerID == 14) { //tuf boss
  1279.                                         @BL18::CurrentPopup = NullPopup(false);
  1280.                                         BL18::Player.direction = 1;
  1281.                                         return true;
  1282.                                 }
  1283.                         }
  1284.                         return false;
  1285.                 }
  1286.                 return true;
  1287.         }
  1288.         void Draw(jjCANVAS@ canvas) const override {
  1289.                 canvas.drawRectangle(0, 0, jjResolutionWidth, jjResolutionHeight, 0, SPRITE::SHADOW);
  1290.                 canvas.drawRectangle(jjResolutionWidth/2 - 275 - 2, 180 - 2, 550 + 4, 120 + 4, 32 + (jjRandom() & 3));
  1291.                 canvas.drawRectangle(jjResolutionWidth/2 - 275, 180, 550, 120, 0);
  1292.                 canvas.drawString(jjResolutionWidth/2 - 300, 140, Header, STRING::MEDIUM, STRING::NORMAL,0, SPRITE::PALSHIFT, 40);
  1293.                 canvas.drawString(0x10000 + jjResolutionWidth/2 - 300, 340, Footer, STRING::MEDIUM, STRING::NORMAL,0, SPRITE::PALSHIFT, 40);
  1294.                 canvas.drawString(jjResolutionWidth/2 - 275, 295, "Cyberspaz OS", STRING::SMALL,STRING::NORMAL,0, SPRITE::PALSHIFT, 40);
  1295.                 if (IsHint)
  1296.                         canvas.drawString(0x8000, 205, Prompts[1], STRING::MEDIUM, BL18::ConversationAppearance,0, SPRITE::PALSHIFT, uint8(-4*8));
  1297.                 else {
  1298.                         const bool flash = (jjGameTicks & 15) < 8;
  1299.                         if (ExitCounter == 0 || flash) {
  1300.                                 string displayString = Input;
  1301.                                 if (ExitCounter == 0 && flash)
  1302.                                         displayString += "-";
  1303.                                 canvas.drawString(jjResolutionWidth/2 - 250, 230, displayString, STRING::LARGE, STRING::NORMAL,0, SPRITE::PALSHIFT, uint8((ExitCounter == 0 ? 4 : CorrectPassword ? 6 : 5) * -8));
  1304.                         }
  1305.                         if (HintFound && ExitCounter == 0)
  1306.                                 canvas.drawString(jjResolutionWidth/2, 45, "Hint:1@" + Prompts[1], STRING::SMALL, BL18::ConversationAppearance,0, SPRITE::PALSHIFT, 40);
  1307.                 }
  1308.         }
  1309. }
  1310.  
  1311. void BridgeWhenYourGravityIsNormal(jjOBJ@ obj) {
  1312.         if (!BL18::Player.antiGrav) {
  1313.                 obj.behave(BEHAVIOR::BRIDGE);
  1314.         } else {
  1315.                 const auto realYPos = BL18::Player.yPos;
  1316.                 BL18::Player.yPos = 0;
  1317.                 obj.behave(BEHAVIOR::BRIDGE);
  1318.                 BL18::Player.yPos = realYPos;
  1319.         }
  1320. }
  1321.  
  1322. void FieldName(jjOBJ@ obj) {
  1323.         if (obj.state == STATE::DEACTIVATE)
  1324.                 obj.deactivate();
  1325.         else if (obj.state == STATE::START) {
  1326.                 obj.state = STATE::WAIT;
  1327.                 obj.special = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 4);
  1328.         } else {
  1329.                 if (LastRoomName != obj.special && PlayerRoom.Type == obj.special) {
  1330.                         LastRoomName = obj.special;
  1331.                         BL18::Player.showText("@@@@@" + RoomNames[LastRoomName], STRING::LARGE);
  1332.                 }
  1333.         }
  1334. }
  1335.  
  1336. bool TreeAttacked = false;
  1337. void FireOrIce(jjOBJ@ obj) {
  1338.         obj.eventID = (obj.state == STATE::DEACTIVATE) ? OBJECT::TNTDESTRUCTSCENERY : OBJECT::DESTRUCTSCENERY;
  1339.         if (obj.state == STATE::FREEZE)
  1340.                 obj.state = STATE::KILL;
  1341.         obj.behave(BEHAVIOR::DESTRUCTSCENERY);
  1342.         if (obj.state == STATE::KILL || obj.state == STATE::DONE)
  1343.                 TreeAttacked = true;
  1344. }
  1345.  
  1346. class QuestionMarkBlock : jjBEHAVIORINTERFACE {
  1347.         void onBehave(jjOBJ@ obj) {
  1348.                 if (obj.state == STATE::DEACTIVATE)
  1349.                         obj.deactivate();
  1350.                 else if (BL18::IsMurderer) {
  1351.                         if (jjGameTicks & 31 == 0)
  1352.                                 jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos - 20, jjRandom() & 7, CREATOR::OBJECT, BL18::BoneWrapper)].ySpeed -= 1;
  1353.                         else if (jjGameTicks & 3 == 1)
  1354.                                 jjDrawTile(uint(obj.xPos)&~31, uint(obj.yPos)&~31, 1332);
  1355.                 }
  1356.         }
  1357.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1358.                 if (!BL18::IsMurderer && bullet is null && player !is null && player.yPos > obj.yPos && player.ySpeed < -1) {
  1359.                         BL18::IsMurderer = true;
  1360.                         jjPalette.fill(jjPALCOLOR(), 0.15);
  1361.                         jjPalette.apply();
  1362.                         if (!jjMusicLoad("DARKSIDE.MOD")) jjMusicStop();
  1363.                         jjSamplePriority(SOUND::INTRO_BOEM1);
  1364.                         jjAddParticleTileExplosion(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, 1322, false);
  1365.                         player.showText("#||||~@@EXODUS 30:20", STRING::LARGE);
  1366.                         //jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, 0,CREATOR::OBJECT, BL18::BloodSpatter);
  1367.                 }
  1368.                 return true;
  1369.         }
  1370. }
  1371.  
  1372. void MarioPlatform(jjOBJ@ obj) {
  1373.         if (PlayerRoom.Type == RoomTypes::WiseGuru) {// || PlayerRoom.ID >= 101) { //lol
  1374.                 const auto lastYPos = obj.yPos;
  1375.                 if (obj.state == STATE::START) {
  1376.                         obj.state = STATE::FALL;
  1377.                         obj.xPos = obj.xOrg + 16;
  1378.                         const auto@ room = FindRoom(obj.xPos, obj.yPos);
  1379.                         obj.var[0] = int(room.Top);
  1380.                         obj.var[1] = int(room.Bottom);
  1381.                         obj.yPos = ((int(obj.yOrg) - obj.var[0] + jjGameTicks) % (obj.var[1] - obj.var[0])) + obj.var[0]; //sync
  1382.                         //jjDebug("" + obj.yPos);
  1383.                 } else if ((obj.yPos += 1) >= obj.var[1]) {
  1384.                         obj.yPos = obj.var[0];
  1385.                         obj.clearPlatform();
  1386.                 } else {
  1387.                         obj.bePlatform(obj.xPos, lastYPos);
  1388.                         if (BL18::Player.platform == obj.objectID && BL18::Player.xPos < obj.xPos-50)
  1389.                                 BL18::Player.xSpeed += 0.75;
  1390.                 }
  1391.                 jjDrawSpriteFromCurFrame(obj.xPos, lastYPos, obj.curFrame, 1, SPRITE::NORMAL,0, 5);
  1392.         } else
  1393.                 obj.state = STATE::START;
  1394. }
  1395.  
  1396.  
  1397. class NullPopup : BL18::Popup {
  1398.         bool ShowHUD;
  1399.         NullPopup(bool sh) { ShowHUD = sh; }
  1400.         bool Do() override { return true; }
  1401.         void Draw(jjCANVAS@ canvas) const override { }
  1402.         bool DrawHUD() const { return ShowHUD; }
  1403. }
  1404. class Trail {
  1405.         int xPos, yPos, curFrame, direction;
  1406.         Trail(){}
  1407.         Trail(int x, int y, int c, int d) { xPos = x; yPos = y; curFrame = c; direction = d; }
  1408.         void Draw(uint8 opacity) const {
  1409.                 jjDrawSpriteFromCurFrame(xPos, yPos, curFrame, direction, SPRITE::BLEND_NORMAL,opacity, 2);
  1410.         }
  1411. }
  1412. array<Trail> TufBossTrail, FlailerangTrail;
  1413. void MyTufBoss(jjOBJ@ obj) {
  1414.         if (obj.state == STATE::START) {
  1415.                 obj.state = STATE::ACTION;
  1416.                 obj.yPos -= 10;
  1417.                 obj.xAcc = 0;
  1418.         } else if (obj.state == STATE::DEACTIVATE)
  1419.                 obj.deactivate();
  1420.         if (jjGameTicks & 7 == 0) {
  1421.                 if (TufBossTrail.length > 15) TufBossTrail.removeLast();
  1422.                 if (FlailerangTrail.length > 15) FlailerangTrail.removeLast();
  1423.                 TufBossTrail.insertAt(0, Trail(int(obj.xPos), int(obj.yPos), obj.curFrame, obj.direction));
  1424.                 if (jjGameTicks & 15 == 0)
  1425.                         obj.frameID += 1;
  1426.                 obj.determineCurFrame();
  1427.                 if (++obj.age < 80) {
  1428.                         obj.xPos -= 2;
  1429.                 } else if (obj.age == 80) {
  1430.                         obj.frameID = 0;
  1431.                         obj.curAnim -= 5; //throw
  1432.                         obj.state = STATE::ATTACK;
  1433.                         //jjSample(obj.xPos, obj.yPos, SOUND::TUFBOSS_SWING, 63, 5000);
  1434.                 } else if (obj.state == STATE::ATTACK) {
  1435.                         if (obj.frameID == 15) {
  1436.                                 obj.xAcc = obj.xPos - 36;
  1437.                                 obj.yAcc = obj.yPos;
  1438.                                 jjSample(obj.xPos, obj.yPos, SOUND::TUFBOSS_RELEASE, 63, 2500);
  1439.                                 BL18::Player.spriteMode = SPRITE::INVISIBLE;
  1440.                                 //BL18::Player.showText("@@@@@@@@@@No, Jazz.@      You are the demons.");
  1441.                         } else if (obj.frameID == 19) {
  1442.                                 obj.frameID = 0;
  1443.                                 obj.curAnim += 4; //stand
  1444.                                 obj.state = STATE::FLY;
  1445.                         }
  1446.                 } else if (obj.state == STATE::FLY) {
  1447.                         if (obj.age == 170) {
  1448.                                 @BL18::CurrentPopup = null;
  1449.                                 BL18::Player.warpToID(68);
  1450.                                 obj.deactivates = true;
  1451.                         }
  1452.                 }
  1453.                 if (obj.xAcc != 0)
  1454.                         FlailerangTrail.insertAt(0, Trail(int(obj.xAcc -= 2), int(obj.yAcc += 0.2), jjAnimations[jjAnimSets[ANIM::JAZZ] + RABBIT::FALL] + ((jjGameTicks >> 3) % 3), -1));
  1455.         }
  1456.         if (BL18::Player.spriteMode == SPRITE::INVISIBLE)
  1457.                 jjDrawSprite(BL18::Player.xPos, BL18::Player.yPos, ANIM::TUFBOSS, 3, (obj.age >> 1) & 3, 1,SPRITE::NORMAL,0, 2);
  1458.         for (int i = TufBossTrail.length; i-- > 0;)
  1459.                 TufBossTrail[i].Draw(248 - i*16);
  1460.         for (int i = FlailerangTrail.length; i-- > 0;)
  1461.                 FlailerangTrail[i].Draw(248 - i*16);
  1462. }
  1463.  
  1464. int LastGemColorWallSamplePlayed = 0;
  1465. void GemColorWall(jjOBJ@ obj) {
  1466.         const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
  1467.         if (obj.state == STATE::START) {
  1468.                 const auto@ tile = jjTiles[obj.var[0] = jjTileGet(4, xTile,yTile)];
  1469.                 const auto@ Frames = tile.getFrames();
  1470.                 while (Frames.length < 2)
  1471.                         Frames.insertLast(0);
  1472.                 obj.var[1] = Frames[0];
  1473.                 obj.var[2] = Frames[Frames.length-1];
  1474.                 obj.var[3] = jjParameterGet(xTile,yTile, 0, 2); //desired color
  1475.                 obj.var[4] = FindRoom(obj.xOrg, obj.yOrg).ID;
  1476.                 obj.var[5] = jjParameterGet(xTile,yTile, 8, 4); //number of gems in room (as remedy against memory management)
  1477.                 jjTileSet(4, xTile,yTile, obj.var[1]);
  1478.                 obj.state = STATE::WAIT;
  1479.         } else if (obj.state == STATE::DEACTIVATE) {
  1480.                 if (obj.var[0] != 0) //has been STARTed
  1481.                         jjTileSet(4, xTile,yTile, obj.var[0]);
  1482.                 obj.deactivate();
  1483.         } else {
  1484.                 const auto@ room = Rooms[obj.var[4]];
  1485.                 {//if (room is PlayerRoom) {
  1486.                         int gemCount = 0;
  1487.                         for (uint i = jjObjectCount; i-- > 0;) {
  1488.                                 const jjOBJ@ gem = jjObjects[i];
  1489.                                 if (gem.eventID == OBJECT::FIVEHUNDREDBUMP && gem.isActive && FindRoom(gem.xOrg,gem.yOrg) is room) {
  1490.                                         if (gem.special != obj.var[3])
  1491.                                                 return;
  1492.                                         gemCount += 1;
  1493.                                 }
  1494.                         }
  1495.                         if (gemCount < obj.var[5])
  1496.                                 return;
  1497.                         jjTileSet(4, xTile,yTile, obj.var[2]);
  1498.                         jjEventSet(xTile,yTile, 0); //let's be generous... solve once, solve forever
  1499.                         //if (jjEventGet(xTile,yTile - 1) == AREA::HURT)
  1500.                         //      jjTileSet(4, xTile,yTile-1, 0);
  1501.                         jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xOrg, obj.yOrg)].determineCurAnim(ANIM::CUSTOM[BL18::AnimSets::Misc], 4 | (jjRandom() & 1));
  1502.                         obj.delete();
  1503.                         if (jjGameTicks - LastGemColorWallSamplePlayed > 35) {
  1504.                                 LastGemColorWallSamplePlayed = jjGameTicks;
  1505.                                 jjSamplePriority(BL18::HPTrigger);
  1506.                         }
  1507.                 }
  1508.         }
  1509. }
  1510.  
  1511. int LastBallGemCollision = 0;
  1512. class Basketball : jjBEHAVIORINTERFACE {
  1513.         void onBehave(jjOBJ@ obj) {
  1514.                 if (obj.state == STATE::DEACTIVATE) {
  1515.                         if (jjDeactivatingBecauseOfDeath)
  1516.                                 obj.deactivate();
  1517.                         else {
  1518.                                 jjAddObject(obj.eventID, obj.xOrg, obj.yOrg, 0, CREATOR::LEVEL);
  1519.                                 obj.delete();
  1520.                         }
  1521.                 } else {
  1522.                         if (obj.isBlastable) {
  1523.                                 if (obj.yPos > jjWaterLevel && obj.state != STATE::START) {
  1524.                                         obj.isBlastable = false;
  1525.                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_WATER);
  1526.                                         jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, jjWaterLevel)].determineCurAnim(ANIM::COMMON, 3);
  1527.                                 }
  1528.                                 obj.behave(BEHAVIOR::ROTATINGROCK);
  1529.                         } else {
  1530.                                 const auto xSpeed = abs(obj.xSpeed);
  1531.                                 obj.yPos = jjWaterLevel;
  1532.                                 obj.ySpeed = 0;
  1533.                                 obj.behave(BEHAVIOR::ROTATINGROCK);
  1534.                                 if (abs(obj.xSpeed) < xSpeed) {
  1535.                                         obj.xSpeed = (obj.xSpeed < 0) ? -xSpeed : xSpeed; //don't stop
  1536.                                 }
  1537.                         }
  1538.                         /*if (PlayerRoom.Type == RoomTypes::Dethjump && jjGameTicks - LastBallGemCollision > 6)
  1539.                                 for (uint i = jjObjectCount; --i > 0;) {
  1540.                                         jjOBJ@ gem = jjObjects[i];
  1541.                                         if (gem.eventID == OBJECT::FIVEHUNDREDBUMP && gem.isActive && gem.doesCollide(obj)) {
  1542.                                                 LastBallGemCollision = jjGameTicks;
  1543.                                                 obj.xSpeed *= -1;
  1544.                                                 obj.xAcc *= -1;
  1545.                                                 gem.var[0] = 31;
  1546.                                                 gem.state = STATE::ACTION;
  1547.                                         }
  1548.                                 }*/
  1549.                         if (BL18::Player.platform == obj.objectID && !jjMaskedHLine(int(BL18::Player.xPos + obj.xSpeed) - 14, 28, int(BL18::Player.yPos)))
  1550.                                 BL18::Player.xPos += obj.xSpeed;
  1551.                 }
  1552.         }
  1553.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) { //still use normal bullet collision code, but don't hurt the player
  1554.                 if (bullet !is null) {
  1555.                         obj.scriptedCollisions = false;
  1556.                         bullet.objectHit(obj, HANDLING::SPECIAL);
  1557.                         obj.scriptedCollisions = true;
  1558.                 }
  1559.                 return true;
  1560.         }
  1561. }
  1562.  
  1563. /*class WarpSpikes : jjBEHAVIORINTERFACE {
  1564.         void onBehave(jjOBJ@ obj) {
  1565.                 if (obj.state == STATE::START) {
  1566.                         if (FindRoom(obj.xOrg, obj.yOrg).Type != RoomTypes::Dethjump)
  1567.                                 obj.behavior = BL18::Spikes;
  1568.                         else {
  1569.                                 obj.playerHandling = HANDLING::SPECIAL;
  1570.                                 obj.scriptedCollisions = true;
  1571.                         }
  1572.                 }
  1573.                 obj.behave(BL18::Spikes);
  1574.         }
  1575.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1576.                 if (bullet is null && player.warpID == 0) {
  1577.                         jjSample(player.xPos, player.yPos, SOUND::Sample(SOUND::JAZZSOUNDS_JAZZV1 + (jjRandom() & 3)));
  1578.                         if ((player.health -= 1) > 0) {
  1579.                                 //player.warpToID(PlayerRoom.ID);
  1580.                                 player.warpToTile(uint(LastPlayerX/32), uint(LastPlayerY/32));
  1581.                                 player.blink = 200;
  1582.                         }
  1583.                 }
  1584.                 return true;
  1585.         }
  1586. }*/
  1587.  
  1588. class Cow : BL18::Enemy {
  1589.         array<uint> BulletAnims = {11, 15, 25, 39, 53, 76};
  1590.         Cow(jjOBJ@ preset) {
  1591.                 super(preset, BL18::AnimSets::Cow);
  1592.                 preset.curAnim = jjAnimSets[animSetID];
  1593.                 preset.determineCurFrame();
  1594.                 preset.energy = 25;
  1595.                 gender = BL18::Gender::Female;
  1596.                 Layer = 3;
  1597.         }
  1598.         void onBehave(jjOBJ@ obj) override {
  1599.                 if (obj.state == STATE::DONE)
  1600.                         return;
  1601.                 else if (obj.state == STATE::FREEZE)
  1602.                         obj.behave(BEHAVIOR::FLOATSUCKER, false);
  1603.                 else if (obj.state == STATE::DEACTIVATE) {
  1604.                         deboss(obj);
  1605.                         if (obj.playerHandling == HANDLING::SPECIALDONE)
  1606.                                 obj.delete();
  1607.                         else
  1608.                                 obj.deactivate();
  1609.                 } else if (obj.state == STATE::START) {
  1610.                         //just hang out
  1611.                 } else {
  1612.                         obj.special = int(jjSin(jjGameTicks << 5) * 20); //angle
  1613.                         const float speed = BL18::IsMurderer ? 1.2 : 0.2;
  1614.                         if (BL18::Player.xPos > obj.xPos + 80) {
  1615.                                 obj.direction = 1;
  1616.                                 obj.xPos += speed;
  1617.                         } else if (BL18::Player.xPos < obj.xPos - 80) {
  1618.                                 obj.direction = -1;
  1619.                                 obj.xPos -= speed;
  1620.                         }
  1621.                         if (BL18::Player.yPos > obj.yPos + 30) {
  1622.                                 obj.yPos += speed;
  1623.                         } else if (BL18::Player.yPos < obj.yPos - 30) {
  1624.                                 obj.yPos -= speed;
  1625.                         }
  1626.                         if (jjGameTicks & (BL18::IsMurderer ? 15 : 31) == 10) {
  1627.                                 jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos - 12*obj.direction, obj.yPos + 50, obj.objectID, CREATOR::OBJECT, BEHAVIOR::BOUNCEONCE)];
  1628.                                 bullet.playerHandling = HANDLING::ENEMYBULLET;
  1629.                                 bullet.animSpeed = 1;
  1630.                                 bullet.killAnim = jjAnimSets[ANIM::AMMO] + 4;
  1631.                                 bullet.curAnim = jjAnimSets[ANIM::PICKUPS] + BulletAnims[jjRandom() % BulletAnims.length];
  1632.                                 bullet.behave(BEHAVIOR::BIRDFEATHER);
  1633.                                 bullet.xSpeed /= 2;
  1634.                                 jjSample(bullet.xPos, bullet.yPos, SOUND::COMMON_PLOOP1);
  1635.                         }
  1636.                 }
  1637.         }
  1638.         void deboss(const jjOBJ@ obj) const {
  1639.                 if (BL18::Player.boss == obj.objectID) {
  1640.                         BL18::Player.bossActivated = false;
  1641.                         BL18::Player.boss = 0;
  1642.                         jjMusicLoad(BL18::musicFilename);
  1643.                 }
  1644.         }
  1645.         void onDraw(jjOBJ@ obj) override {
  1646.                 if (obj.state == STATE::START)
  1647.                         return;
  1648.                 drawHearts(obj);
  1649.                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame+1, obj.special + 256*obj.direction, obj.direction,1, obj.state == STATE::DONE ? SPRITE::TINTED : obj.state == STATE::FREEZE ? SPRITE::FROZEN : obj.justHit == 0 ? SPRITE::SINGLEHUE : SPRITE::SINGLECOLOR, obj.state == STATE::DONE || obj.justHit != 0 ? 48 : ((jjRandom() % 10 + 2) << 3));
  1650.                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame+2, obj.special + 256*obj.direction, obj.direction,1, obj.state == STATE::DONE ? SPRITE::TINTED : obj.state == STATE::FREEZE ? SPRITE::FROZEN : obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR, 48);
  1651.         }
  1652.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  1653.                 BL18::Enemy::onObjectHit(obj, bullet, player, force);
  1654.                 if (obj.energy <= 0) {
  1655.                         const uint xTile = uint(obj.xOrg)>>5, yTile = uint(obj.yOrg)>>5;
  1656.                         jjSamplePriority(BL18::HPTrigger);
  1657.                         jjEventSet(xTile,yTile, OBJECT::GENERATOR);
  1658.                         jjParameterSet(xTile,yTile, 0,8, OBJECT::GRAPES); //balloon
  1659.                         jjParameterSet(xTile,yTile, 8,5, 5); //delay
  1660.                         jjAddObject(OBJECT::GENERATOR, obj.xOrg,obj.yOrg, 0,CREATOR::LEVEL);
  1661.                         deboss(obj);
  1662.                 }
  1663.                 return true;
  1664.         }
  1665. }
  1666.  
  1667.  
  1668. void onDrawLayer5(jjPLAYER@, jjCANVAS@ canvas) {
  1669.         if (PlayerRoom !is null && PlayerRoom.ID == 70) { //dethmaze
  1670.                 jjPAL backup = jjPalette;
  1671.                 jjPalette = DesktopPalette; //but don't apply; we're going to cheat instead
  1672.                 for (uint i = 0; i < 4; ++i) {
  1673.                         const auto@ loc = DethmazeShortcutLocations[i];
  1674.                         canvas.drawSpriteFromCurFrame(loc[0], loc[1], FirstShortcutFrame + CurrentShortcutList[i], 0, SPRITE::BLEND_LIGHTEN,185 + int(jjSin((jjGameTicks << 3) + (i << 8)) * 64));
  1675.                 }
  1676.                 jjPalette = backup;
  1677.         }
  1678. }
  1679.  
  1680. bool SeenBNIntro = false;
  1681. class IntroElement {
  1682.         int Age;
  1683.         void Draw(int,int, jjCANVAS@) const {}
  1684.         IntroElement(){}
  1685. }
  1686. class IntroEndScene : IntroElement {
  1687.         void Draw(int,int,jjCANVAS@) const override {
  1688.                 auto@ scenes = cast<BNIntro>(BL18::CurrentPopup).Scenes;
  1689.                 scenes.removeAt(0);
  1690.         }
  1691.         IntroEndScene(int a) { Age = -a; }
  1692. }
  1693. class IntroVerbalElement : IntroElement{
  1694.         int X, Y;
  1695. }
  1696. enum IntroLetterStyle {
  1697.         Abrupt = 0, RotateIn, JumpOut
  1698. }
  1699. class IntroLetter : IntroVerbalElement {
  1700.         uint Letter;
  1701.         IntroLetterStyle Style;
  1702.         float XSpeed = (int(jjRandom() & 31) - 16) / 5;
  1703.         IntroLetter(int a, int x, int y, STRING::Size s, IntroLetterStyle t, string l) {
  1704.                 Age = -a;
  1705.                 X = x;
  1706.                 Y = y;
  1707.                 Style = t;
  1708.                 Letter = jjAnimations[jjAnimSets[ANIM::FONT] + s] + l[0] - 32;
  1709.         }
  1710.         void Draw(int xOrg, int yOrg, jjCANVAS@ canvas) const override {
  1711.                 switch (Style) {
  1712.                         case JumpOut:
  1713.                                 if (Age < 100) {
  1714.                                         float yPos = Y;
  1715.                                         float ySpeed = -3;
  1716.                                         for (int i = 0; i < Age; ++i)
  1717.                                                 yPos += ySpeed += 0.15;
  1718.                                         canvas.drawSpriteFromCurFrame(xOrg + X + int(XSpeed * Age), yOrg + int(yPos), Letter);
  1719.                                 }
  1720.                                 return;
  1721.                         case RotateIn:
  1722.                                 if (Age < 70) {
  1723.                                         const int intensity = 70-Age;
  1724.                                         const float scale = 1 + intensity / 14.0;
  1725.                                         canvas.drawRotatedSpriteFromCurFrame(xOrg + X, yOrg + Y, Letter, int(XSpeed * intensity), scale,scale, SPRITE::BLEND_NORMAL, Age * 3 + 50);
  1726.                                         return;
  1727.                                 }
  1728.                                 break;
  1729.                 }
  1730.                 canvas.drawSpriteFromCurFrame(xOrg + X, yOrg + Y, Letter);
  1731.         }
  1732. }
  1733. enum IntroStringStyle {
  1734.         Abrupt = 0, FadeIn, Sprinkler, SpinIn, BounceIn, RandomOffset, FallDown
  1735. }
  1736. class IntroString : IntroVerbalElement {
  1737.         STRING::Size Size;
  1738.         IntroStringStyle Style;
  1739.         string Letters;
  1740.         IntroString(int a, int x, int y, STRING::Size s, IntroStringStyle t, string l) {
  1741.                 Age = -a;
  1742.                 X = x;
  1743.                 Y = y;
  1744.                 Size = s;
  1745.                 Style = t;
  1746.                 Letters = l;
  1747.         }
  1748.         void Draw(int xOrg, int yOrg, jjCANVAS@ canvas) const override {
  1749.                 switch (Style) {
  1750.                         case FadeIn:
  1751.                                 if (Age < 64) {
  1752.                                         canvas.drawString(xOrg + X, yOrg + Y, Letters, Size, STRING::NORMAL,0, SPRITE::BLEND_NORMAL, Age<<2);
  1753.                                         return;
  1754.                                 }
  1755.                                 break;
  1756.                         case SpinIn:
  1757.                         case BounceIn:
  1758.                                 if (Age < 128) {
  1759.                                         canvas.drawString(xOrg + X, yOrg + Y, Letters, Size, Style == SpinIn ? STRING::SPIN : STRING::BOUNCE,255 - (Age<<1));
  1760.                                         return;
  1761.                                 }
  1762.                                 break;
  1763.                         case Sprinkler:
  1764.                                 for (uint i = 0; i < 2; ++i) {
  1765.                                         const uint angle = (i << 9) + (Age << 1);
  1766.                                         const float xStep = jjSin(angle) * 16, yStep = jjCos(angle) * 10;
  1767.                                         for (uint j = 0; j < 5; ++j)
  1768.                                                 canvas.drawString(xOrg + X + int(xStep*j), yOrg + Y + int(yStep*j), Letters, Size, STRING::NORMAL,0, SPRITE::BLEND_NORMAL, (5-j) << 4);
  1769.                                 }
  1770.                                 break;
  1771.                         case RandomOffset: {
  1772.                                 const uint rand = jjRandom();
  1773.                                 canvas.drawString(xOrg + X - 20 + (rand & 3) * 15, yOrg + Y - 20 + ((rand >> 2) & 3) * 15, Letters, Size, STRING::NORMAL,0, SPRITE::BLEND_NORMAL, (rand >> 4) & 0xFF);
  1774.                                 return;
  1775.                         }
  1776.                         case FallDown:
  1777.                                 if (Age < 32) {
  1778.                                         canvas.drawString(xOrg + X, yOrg + Y - (32-Age) * 20, Letters, Size);
  1779.                                         return;
  1780.                                 }
  1781.                 }
  1782.                 canvas.drawString(xOrg + X, yOrg + Y, Letters, Size);
  1783.         }
  1784. }
  1785.  
  1786. class BNIntro : BL18::Popup {
  1787.         uint ticks = 0;
  1788.         array<array<IntroElement@>> Scenes = {
  1789.                 {
  1790.                         IntroString(0, 110,155, STRING::MEDIUM, IntroStringStyle::FadeIn, "For"),
  1791.                         IntroString(70, 218,210, STRING::LARGE, IntroStringStyle::Sprinkler, "14"),
  1792.                         IntroString(110, 410,108, STRING::MEDIUM, IntroStringStyle::Sprinkler, "years"),
  1793.                         IntroString(250, 498,259, STRING::MEDIUM, IntroStringStyle::Abrupt, "we've"),
  1794.                         IntroString(315, 329,346, STRING::LARGE, IntroStringStyle::SpinIn, "waited"),
  1795.                         IntroEndScene(480)
  1796.                 },
  1797.                 {
  1798.                         IntroString(0, 273,107, STRING::LARGE, IntroStringStyle::Abrupt, "You"),
  1799.                         IntroString(54, 273,107, STRING::LARGE, IntroStringStyle::Sprinkler, "You"),
  1800.                         IntroString(150, 103,185, STRING::MEDIUM, IntroStringStyle::BounceIn, "thought"),
  1801.                         IntroString(180, 288,238, STRING::MEDIUM, IntroStringStyle::BounceIn, "we"),
  1802.                         IntroString(210, 436,180, STRING::MEDIUM, IntroStringStyle::BounceIn, "were"),
  1803.                         IntroString(400, 245,365, STRING::LARGE, IntroStringStyle::FadeIn, "DEAD"),
  1804.                         IntroString(100, 273,107, STRING::LARGE, IntroStringStyle::RandomOffset, "You"),
  1805.                         IntroString(400, 103,185, STRING::MEDIUM, IntroStringStyle::RandomOffset, "thought"),
  1806.                         IntroString(400, 288,238, STRING::MEDIUM, IntroStringStyle::RandomOffset, "we"),
  1807.                         IntroString(400, 436,180, STRING::MEDIUM, IntroStringStyle::RandomOffset, "were"),
  1808.                         IntroString(500, 245,365, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1809.                         IntroString(570, 68,49, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1810.                         IntroString(570, 336,41, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1811.                         IntroString(570, 484,80, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1812.                         IntroString(570, 102,222, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1813.                         IntroString(570, 263,166, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1814.                         IntroString(570, 508,253, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1815.                         IntroString(570, 37,315, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1816.                         IntroString(570, 361,281, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1817.                         IntroString(570, 499,348, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1818.                         IntroString(570, 123,426, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1819.                         IntroString(570, 446,446, STRING::LARGE, IntroStringStyle::RandomOffset, "DEAD"),
  1820.                         IntroEndScene(666)
  1821.                 },
  1822.                 {
  1823.                         IntroEndScene(300)
  1824.                 },
  1825.                 {
  1826.                         IntroString(170, 255,237, STRING::LARGE, IntroStringStyle::FallDown, "FOO!"),
  1827.                         IntroEndScene(202)
  1828.                 },
  1829.                 {
  1830.                         IntroString(0, 255,237, STRING::LARGE, IntroStringStyle::Abrupt, "FOO!"),
  1831.                         IntroEndScene(150)
  1832.                 },
  1833.                 {
  1834.                         IntroString(0, 80,134, STRING::MEDIUM, IntroStringStyle::SpinIn, "Did"),
  1835.                         IntroString(64, 182,182, STRING::LARGE, IntroStringStyle::BounceIn, "YOU"),
  1836.                         IntroString(128, 306,233, STRING::MEDIUM, IntroStringStyle::SpinIn, "really"),
  1837.                         IntroString(192, 306,233, STRING::MEDIUM, IntroStringStyle::FadeIn, "really"),
  1838.                         IntroString(192, 435,285, STRING::MEDIUM, IntroStringStyle::BounceIn, "think"),
  1839.                         IntroString(256, 142,298, STRING::MEDIUM, IntroStringStyle::SpinIn, "it"),
  1840.                         IntroString(320, 210,258, STRING::MEDIUM, IntroStringStyle::BounceIn, "would"),
  1841.                         IntroString(384, 276,220, STRING::LARGE, IntroStringStyle::SpinIn, "really"),
  1842.                         IntroString(448, 410,195, STRING::MEDIUM, IntroStringStyle::BounceIn, "BE"),
  1843.                         IntroString(550, 33,238, STRING::LARGE, IntroStringStyle::FallDown, "SO"),
  1844.                         IntroString(550, 470,238, STRING::LARGE, IntroStringStyle::FallDown, "EASY?"),
  1845.                         IntroString(64, 80,134, STRING::MEDIUM, IntroStringStyle::FadeIn, "Did"),
  1846.                         IntroString(128, 182,182, STRING::LARGE, IntroStringStyle::FadeIn, "YOU"),
  1847.                         IntroString(256, 435,285, STRING::MEDIUM, IntroStringStyle::FadeIn, "think"),
  1848.                         IntroString(320, 142,298, STRING::MEDIUM, IntroStringStyle::FadeIn, "it"),
  1849.                         IntroString(384, 210,258, STRING::MEDIUM, IntroStringStyle::FadeIn, "would"),
  1850.                         IntroString(448, 276,220, STRING::LARGE, IntroStringStyle::FadeIn, "really"),
  1851.                         IntroString(512, 410,195, STRING::MEDIUM, IntroStringStyle::FadeIn, "BE"),
  1852.                         IntroString(550, 33,238, STRING::LARGE, IntroStringStyle::FallDown, "SO"),
  1853.                         IntroString(550, 455,238, STRING::LARGE, IntroStringStyle::FallDown, "EASY?"),
  1854.                         IntroString(580, 33,238, STRING::LARGE, IntroStringStyle::RandomOffset, "SO"),
  1855.                         IntroString(580, 455,238, STRING::LARGE, IntroStringStyle::RandomOffset, "EASY?"),
  1856.                         IntroString(580, 33,238, STRING::LARGE, IntroStringStyle::Sprinkler, "SO"),
  1857.                         IntroString(580, 455,238, STRING::LARGE, IntroStringStyle::Sprinkler, "EASY?"),
  1858.                         IntroEndScene(725)
  1859.                 },
  1860.                 {
  1861.                         IntroString(0, 33,238, STRING::LARGE, IntroStringStyle::RandomOffset, "SO"),
  1862.                         IntroString(0, 455,238, STRING::LARGE, IntroStringStyle::RandomOffset, "EASY?"),
  1863.                         IntroString(0, 33,238, STRING::LARGE, IntroStringStyle::Sprinkler, "SO"),
  1864.                         IntroString(0, 455,238, STRING::LARGE, IntroStringStyle::Sprinkler, "EASY?"),
  1865.                         IntroEndScene(100)
  1866.                 },
  1867.                 {
  1868.                         IntroLetter(100, 80,130, STRING::MEDIUM, IntroLetterStyle::RotateIn, "I"),
  1869.                         IntroLetter(110, 280,130, STRING::MEDIUM, IntroLetterStyle::RotateIn, "h"),
  1870.                         IntroLetter(120, 296,130, STRING::MEDIUM, IntroLetterStyle::RotateIn, "a"),
  1871.                         IntroLetter(130, 312,130, STRING::MEDIUM, IntroLetterStyle::RotateIn, "d"),
  1872.                         IntroLetter(140, 530,130, STRING::MEDIUM, IntroLetterStyle::RotateIn, "a"),
  1873.                         IntroString(200, 90,160, STRING::MEDIUM, IntroStringStyle::Abrupt, "I"),
  1874.                         IntroString(200, 290,160, STRING::MEDIUM, IntroStringStyle::Abrupt, "had"),
  1875.                         IntroString(200, 540,160, STRING::MEDIUM, IntroStringStyle::Abrupt, "a"),
  1876.                         IntroString(220, 100,200, STRING::MEDIUM, IntroStringStyle::Abrupt, "I"),
  1877.                         IntroString(220, 300,200, STRING::MEDIUM, IntroStringStyle::Abrupt, "had"),
  1878.                         IntroString(220, 550,200, STRING::MEDIUM, IntroStringStyle::Abrupt, "a"),
  1879.                         IntroString(240, 110,250, STRING::MEDIUM, IntroStringStyle::Abrupt, "I"),
  1880.                         IntroString(240, 310,250, STRING::MEDIUM, IntroStringStyle::Abrupt, "had"),
  1881.                         IntroString(240, 560,250, STRING::MEDIUM, IntroStringStyle::Abrupt, "a"),
  1882.                         IntroString(260, 120,310, STRING::MEDIUM, IntroStringStyle::Abrupt, "I"),
  1883.                         IntroString(260, 320,310, STRING::MEDIUM, IntroStringStyle::Abrupt, "had"),
  1884.                         IntroString(260, 570,310, STRING::MEDIUM, IntroStringStyle::Abrupt, "a"),
  1885.                         IntroString(280, 130,380, STRING::MEDIUM, IntroStringStyle::Abrupt, "I"),
  1886.                         IntroString(280, 330,380, STRING::MEDIUM, IntroStringStyle::Abrupt, "had"),
  1887.                         IntroString(280, 580,380, STRING::MEDIUM, IntroStringStyle::Abrupt, "a"),
  1888.                         IntroString(300, 140,470, STRING::MEDIUM, IntroStringStyle::Abrupt, "I"),
  1889.                         IntroString(300, 340,470, STRING::MEDIUM, IntroStringStyle::Abrupt, "had"),
  1890.                         IntroString(300, 590,470, STRING::MEDIUM, IntroStringStyle::Abrupt, "a"),
  1891.                         IntroString(340, 68,220, STRING::LARGE, IntroStringStyle::Sprinkler, "CLONE"),
  1892.                         IntroString(340, 340,220, STRING::LARGE, IntroStringStyle::Sprinkler, "MACHINE"),
  1893.                         IntroEndScene(420)
  1894.                 },{
  1895.                         IntroString(0, 68,220, STRING::LARGE, IntroStringStyle::Sprinkler, "CLONE"),
  1896.                         IntroString(0, 340,220, STRING::LARGE, IntroStringStyle::Sprinkler, "MACHINE"),
  1897.                         IntroString(400/2, 85,49, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1898.                         IntroString(200/2, 224,103, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1899.                         IntroString(800/2, 10,144, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1900.                         IntroString(650/2, 189,178, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1901.                         IntroString(350/2, 22,270, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1902.                         IntroString(100/2, 242,316, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1903.                         IntroString(700/2, 128,375, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1904.                         IntroString(600/2, 2,423, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1905.                         IntroString(250/2, 122,464, STRING::LARGE, IntroStringStyle::RandomOffset, "CLONE"),
  1906.                         IntroString(150/2, 328,47, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1907.                         IntroString(550/2, 439,132, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1908.                         IntroString(450/2, 360,186, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1909.                         IntroString(300/2, 405,275, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1910.                         IntroString(750/2, 425,350, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1911.                         IntroString(50/2, 305,400, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1912.                         IntroString(600/2, 436,453, STRING::LARGE, IntroStringStyle::RandomOffset, "MACHINE"),
  1913.                         IntroEndScene(900/2)
  1914.                 }
  1915.         };
  1916.         BNIntro() {
  1917.                 SeenBNIntro = true;
  1918.                 jjMusicLoad("scary1.mp3");
  1919.                 jjPalette.copyFrom(64, 8, 48, jjPalette); //brown to pink
  1920.                 jjPalette.apply();
  1921.                
  1922.                 for (uint i = 0; i < 100; ++i)
  1923.                         Scenes[2].insertAt(0, IntroLetter(i<<1, jjRandom()%640, jjRandom()%480, STRING::Size(jjRandom() % 3), IntroLetterStyle::JumpOut, "?"));
  1924.                        
  1925.                 string fourthScene = "Guess again,";
  1926.                 int fourthSceneXPos = 212;
  1927.                 for (uint i = 0; i < fourthScene.length; ++i) {
  1928.                         string letter = "?";
  1929.                         letter[0] = fourthScene[i];
  1930.                         Scenes[3].insertAt(0, IntroLetter(i<<3, fourthSceneXPos, 250, STRING::MEDIUM, IntroLetterStyle::RotateIn, letter));
  1931.                         Scenes[4].insertAt(0, IntroLetter(jjRandom()&3, fourthSceneXPos, 250, STRING::MEDIUM, IntroLetterStyle::JumpOut, letter));
  1932.                         if (letter == " ")
  1933.                                 fourthSceneXPos += 18;
  1934.                         else
  1935.                                 fourthSceneXPos += jjAnimFrames[jjAnimations[jjAnimSets[ANIM::FONT] + STRING::MEDIUM] + letter[0] - 32].width + 1;
  1936.                 }
  1937.         }
  1938.         bool Do() override {
  1939.                 if (Scenes.length != 0) {
  1940.                         array<IntroElement@>@ scene = Scenes[0];
  1941.                         for (uint i = 0; i < scene.length; ++i)
  1942.                                 ++scene[i].Age;
  1943.                         return true;
  1944.                 }
  1945.                 jjPalette.copyFrom(64, 8, 64, DesktopPalette);
  1946.                 jjPalette.apply();
  1947.                 jjMusicStop();
  1948.                 BL18::Player.spriteMode = SPRITE::PLAYER;
  1949.                 BL18::Player.xSpeed = -4;
  1950.                 return false;
  1951.         }
  1952.         void Draw(jjCANVAS@ canvas) const override {
  1953.                 canvas.drawRectangle(0, 0, jjResolutionWidth, jjResolutionHeight, 0);
  1954.                
  1955.                 const int xOrg = (jjResolutionWidth - 640) / 2;
  1956.                 const int yOrg = (jjResolutionHeight - 480) / 2;
  1957.                
  1958.                 if (Scenes.length != 0) {
  1959.                         const array<IntroElement@>@ scene = Scenes[0];
  1960.                         for (uint i = 0; i < scene.length; ++i) {
  1961.                                 const IntroElement@ element = scene[i];
  1962.                                 if (element.Age >= 0)
  1963.                                         element.Draw(xOrg,yOrg, canvas);
  1964.                         }
  1965.                 }
  1966.         }
  1967. }
  1968.  
  1969. array<array<string>> Conversations = {
  1970.         {
  1971.                 "S0we;re falling a biyt behind on@coin collectuin, so what@better palce than here>?",
  1972.                 "J3@Yeah!",
  1973.                 "J2Uh... although, where is@'here' exactly?",
  1974.                 "S1So here on the outskirts@of Foomania is this little@town thats called Forth",
  1975.                 "S2an d Forth is bilt on the@edge of a GIANT PIT@in the ground!!",
  1976.                 "J6So...",
  1977.                 "S6SO this huge pit is AALSO@a MINE wher all thos coins@youv found were discovared",
  1978.                 "S7In other words,@all ur coins are.......",
  1979.                 "S9@#Made in Chasm"
  1980.         },{
  1981.                 "S0BAH it looks like the path@to the Chasm is blocked@by a #WALL OF MARYJUANA",
  1982.                 "J0Gosh, it sure does!@What if we just, y'know,@went through it and...",
  1983.                 "S5@NO!",
  1984.                 "S3The devil weed is@a tool of Satan!",
  1985.                 "S2Evan if you are a bit confused@over sexual perversins@i will not let you do DRUGS",
  1986.                 "J9Uh. Well. Okay.",
  1987.                 "S6but its fine!",
  1988.                 "S7just take that KEY@thats conveniently@rigt abouve me",
  1989.                 "S1and we shjould be able to@get to th Chasn@by usin Sal's Shortcut!"
  1990.         },{
  1991.                 "S2Jazz have you ever@heard the song@99 Red Balloons?",
  1992.                 "J0Oh, yeah! It's a really catchy@song in some foreign language.",
  1993.                 "J3Sometimes I do my@morning jazzercise to it!",
  1994.                 "S0right",
  1995.                 "S3also its about@worldwide nuclear war",
  1996.                 "J8What?!",
  1997.                 "S4Yeh  in the song, some false@prophets sees the balloons@and think theyrs missiles",
  1998.                 "S1so everhybody shootso at each@other and its like a war@that goes on for 99 years",
  1999.                 "S8in the end everything is just@rubble and most ppl are dead",
  2000.                 "J5Wow. I... I never@would have guessed.",
  2001.                 "S6by the way did you know@that this adventure were on@is seven levels long?",
  2002.                 "J2No, but I'm still processing@that one of my favorite@songs is about nuclear war.",
  2003.                 "S9ANd have you heard of the@seven trumpets in ths@Book of Revelation?",
  2004.                 "J7@...",
  2005.                 "J5Am I going to like@where this is going?",
  2006.                 "S5Bloody hailfire, fiery mountain,@Wormwood, striking the starz,@a woe kitten, etc.",
  2007.                 "J9...@...@Wait, what was the last one?",
  2008.                 "S0A woe kitten.",
  2009.                 "S6Ut is written, a #WOE KITTEN~~@will descend unto earth,@saying, WOE UNTO THEE.",
  2010.                 "S7Some of my favorit podcasts@identify nuclear war as a@trumpet of Revelation",
  2011.                 "S2(Not the woe kiten@trumpet obviously)",
  2012.                 "J5@...",
  2013.                 "J2So, uh, reading between@the lines a bit...",
  2014.                 "J1You're saying this red balloon@right over there is@something very dangerous?",
  2015.                 "S4huh??? no its fine",
  2016.                 "S7it flips your gravity@thats all",
  2017.                 "S9i just tought all that@other stuff was@intresting ; D",
  2018.                 "J7Spotty...",
  2019.                 "J6Were you this morbid@BEFORE you became a ghost?"
  2020.         },{
  2021.                 "S6Do you know what that@thing up there is?",
  2022.                 "J0Well, I've played the full@version of Hocus Pocus,@so, yes.",
  2023.                 "S2oh",
  2024.                 "J5...but you should@explain it anyway@just in case!",
  2025.                 "S0Yeah sure.",
  2026.                 "S1so thag's a SMART BOMB",
  2027.                 "S3it movies around at@random nd if you hit@it or shoot it, BOOM",
  2028.                 "J3And when it goes boom,@it hurts any enemies@around it, right?",
  2029.                 "S4well yeaht echniclly but@it was placed by YOR enemy@so really it just hurts YOU",
  2030.                 "J7Oh. That's understandable,@though less convenient.",
  2031.                 "J8Placed there by my@enemy, you say?",
  2032.                 "S8YESH!by one of the@5 great ebils@of the world.",
  2033.                 "S6Abortion, marijuana,@evolution, euthanasia,@or Fooruman."
  2034.         },{
  2035.                 "S6Below us lies the CHASM.",
  2036.                 "S3A place of #||||~DEMONS~,@#||||~PSILOCYBIN~, and@#||||~BAD LANDLORDS~.",
  2037.                 "S2soooooo im not comin@with you down there ; (",
  2038.                 "J3Oh.",
  2039.                 "J5...",
  2040.                 "J8Well, um, do you have@any advice or anything@for me on my quest?",
  2041.                 "S9Yes!@thx for askong",
  2042.                 "S7The Chasm is very big,@so you should press@TAB anytim you get lost",
  2043.                 "S8Also one of the final@puzzels in teh chasm is@ the #MAZE OF DETH",
  2044.                 "S4each path within the@maze will be marked@by a SHORTCUT",
  2045.                 "S3but some shirtcats are@safwe and others wil@destroy your prograss",
  2046.                 "S6four FALSE PROPHETS are@in the chasm sumwhere",
  2047.                 "S8find them and make them@tell you which of the@shortcuts are UNSAFE",
  2048.                 "S9then u should be able@to get throrough the@deth maze alive!!",
  2049.                 "J0Okay. Four shortcut types@are dangerous, and the@false prophets know which.",
  2050.                 "S9Exactly!",
  2051.                 "S6Now you can enter the chasm!",
  2052.                 "S5Set thes witches to@#||O~N, #|~OFF,#|||||||~ON,#|~@OFF, OFF,#|||||||~ON."
  2053.         },{
  2054.                 "J4False prophet!@What shortcut@did you place?",
  2055.                 "S7My shortcut is#@",
  2056.                 "J0Great! That should help@me with the DETHMAZE."
  2057.         },{
  2058.                 "J4False prophet!@What shortcut@did you place?",
  2059.                 "S7My shortcut is#@",
  2060.                 "J0Great! That should help@me with the DETHMAZE."
  2061.         },{
  2062.                 "J4False prophet!@What shortcut@did you place?",
  2063.                 "S7My shortcut is#@",
  2064.                 "J0Great! That should help@me with the DETHMAZE."
  2065.         },{
  2066.                 "J4False prophet!@What shortcut@did you place?",
  2067.                 "S7My shortcut is#@",
  2068.                 "J0Great! That should help@me with the DETHMAZE."
  2069.         },{
  2070.                 "S0Welcome, wayfarer, to the@inner sanctum of the@Wise Guru's Cathedral!",
  2071.                 "S0May I offer you@anything? Tea?@24 Hour Coffee?",
  2072.                 "S0I am the Wise Guru. You@have shown great wisdom@to solve all my trials.",
  2073.                 "J2Yes, so, uh, I@have some questions@about those trials...",
  2074.                 "J4All the dream visions,@and the warping, was@that all necessary?",
  2075.                 "J8Did I really get@turned into a@flailerang?",
  2076.                 "J3And so much blood@everywhere...",
  2077.                 "J9I'm going to have@nightmares for weeks!",
  2078.                 "S0Yes, I knew you would,@for I am wise.",
  2079.                 "J3What does any of this@have to do with@Valentine's Day?!",
  2080.                 "S0You will understand@when you are as@wise as I am.",
  2081.                 "S0But that will not@be for a long time,@for I am wise.",
  2082.                 "J7@...",
  2083.                 "S0To explore the remaining@areas of the Chasm, you@will need to use my PORTAL.",
  2084.                 "S0My portal is back@at the entrance to@my Cathedral.",
  2085.                 "S0I have unlocked it from@the PORTAL SELECTION@MENU for you.",
  2086.                 "S0You will find a way to@megawarp to it if you@revisit #Vutxfy Caves~~.",
  2087.                 "S0I will give you this@BALLOON to help you get@there, for I am...",
  2088.                 "J7@@...wise, yes, I know.",
  2089.                 "S5I was going to say@\"for I am generous.\"",
  2090.                 "J2Oh.",
  2091.                 "S0I know that I@am generous,@for I am wise.",
  2092.                 "S0Good luck traversing@the crossroads and caves,@my wise friend!"
  2093.         },{
  2094.                 "S0I am wise."
  2095.         },{
  2096.                 "J1Spotty! I thought you@weren't coming down here@because of all the demons.",
  2097.                 "S8I wasn't, but this area@is blessd by JEsus,@soo I decided to risk it.",
  2098.                 "S5Didy ou see that@switch down there?",
  2099.                 "J7Yeah, it's a real@humdinger!",
  2100.                 "J4Whenever I flip it,@I just get locked in!",
  2101.                 "J8How am I supposed to@explore the room to@the left like this?",
  2102.                 "S0Ahh, that must be@Jesus'ss blessing that I@had just mentionrd.",
  2103.                 "S3Oonly a SUPER UNHOLY@ARTIFAXT could get you@past that magoc!",
  2104.                 "S4Soo i'm sure you@will leave it@totally alome, right??"
  2105.         },{
  2106.                 "S0Remember: Consult each of@the four false prophets.",
  2107.                 "S0Each has laid a@false shortcut@in the Dethmaze.",
  2108.                 "S0Ignore those four false@shortcuts, and you@will make it through.",
  2109.                 "J7...",
  2110.                 "S2...",
  2111.                 "J7...",
  2112.                 "S5...for I am wise."
  2113.         }
  2114. };
  2115.  
  2116. void onFunction11() { //entering the chasm... changes can be made permanently, no (significant) time will be spent above-ground after this.
  2117.         if (jjMusicLoad("nr-underthine.it"))
  2118.                 BL18::musicFilename = jjMusicFileName;
  2119.         BL18::Recolor(jjAnimations[jjAnimSets[ANIM::VINE]+1], array<uint8>={16}, BL18::RecolorReplace(80));
  2120. }
  2121. void onFunction12(jjPLAYER@ play) {
  2122.         if (play.health < jjMaxHealth) {
  2123.                 play.health = jjMaxHealth;
  2124.                 jjSample(play.xPos, play.yPos, SOUND::JAZZSOUNDS_JUMMY);
  2125.         }
  2126.         if (BL18::IsMurderer) {
  2127.                 BL18::IsMurderer = false;
  2128.                 jjMusicLoad(BL18::musicFilename);
  2129.                 MLLE::Palette.apply();
  2130.         }
  2131. }
  2132. uint8 spikeVineWarpTargetID = 50;
  2133. void onFunction13(jjPLAYER@, uint8 warpID) {
  2134.         spikeVineWarpTargetID = warpID;
  2135. }
  2136. void onFunction14(jjPLAYER@ play) {
  2137.         play.warpToID(spikeVineWarpTargetID);
  2138. }
  2139. void TurnOnTriggerThirty() {