Downloads containing CandyGoth.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Candy GothFeatured Download Violet CLM Single player 8.3 Download file

File preview

  1. const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
  2. #include "MLLE-Include-1.5.asc" ///@MLLE-Generated
  3. #pragma require "CandyGothDecs.j2t" ///@MLLE-Generated
  4. #pragma require "PezroxV.j2t" ///@MLLE-Generated
  5. #pragma require "CandyGoth.j2l" ///@MLLE-Generated
  6. #pragma require "SkullGem.j2a"
  7. #pragma require "Lubella.j2a"
  8. #pragma offer "beyond_-_creepy.it"
  9.  
  10. uint FirstTileFrameID;
  11.  
  12. void onLevelLoad() {
  13.         jjAnimations[jjObjectPresets[OBJECT::REDGEM].curAnim] = jjAnimations[jjAnimSets[ANIM::CUSTOM[0]].load(0, "SkullGem.j2a")];
  14.         Blacken(OBJECT::BAT);
  15.         Blacken(OBJECT::RAVEN);
  16.         Blacken(OBJECT::DEMON);
  17.         jjTexturedBGTexture = TEXTURE::WISETYNESS;
  18.         jjTexturedBGFadePositionY = 0.6;
  19.         jjUseLayer8Speeds = true;
  20.        
  21.         const array<array<int>> tileSpriteDimensions = {
  22.                 {0,0, 16,32*5-4, 0,-9},
  23.                 {16,0, 16,32*5-4, 0,-9},
  24.                 {0, 15*32, 64,32, -32,-32},
  25.                 {0, 16*32, 64,32, -32,0},
  26.                 {0, 17*32, 9,28, -5,-14},
  27.                 {0,0, 96,16, 0,0},
  28.                 {1*32, 17*32, 32,32, -16,-16},
  29.                 {0, 9*32, 64,64, -32,-32},
  30.                 {2*32, 7*32, 128,128, -64,-64},
  31.                 {0, 18*32, 64,16, -32,0}
  32.         };
  33.         FirstTileFrameID = jjAnimations[jjAnimSets[ANIM::CUSTOM[1]].allocate(array<uint>={tileSpriteDimensions.length})];
  34.         for (uint i = 0; i < tileSpriteDimensions.length; ++i) {
  35.                 jjANIMFRAME@ tileFrame = jjAnimFrames[FirstTileFrameID + i];
  36.                 const auto@ dimensions = tileSpriteDimensions[i];
  37.                 jjPIXELMAP(dimensions[0],dimensions[1],dimensions[2],dimensions[3], 5).save(tileFrame);
  38.                 tileFrame.hotSpotX = dimensions[4];
  39.                 tileFrame.hotSpotY = dimensions[5];
  40.         }
  41.        
  42.         ///@Event 141=Lower Floor                     |+|Trigger      |Lower|Floor|TriggerID:5
  43.         jjObjectPresets[141].behavior = LoweringFloor;
  44.         jjObjectPresets[141].playerHandling = HANDLING::PARTICLE;
  45.         jjObjectPresets[141].curFrame = FirstTileFrameID;
  46.         jjObjectPresets[141].isBlastable = false;
  47.        
  48.         ///@Event 143=Rippling Floor                     |+|Trigger      |Ripple|Floor
  49.         jjObjectPresets[143].behavior = RipplingFloor;
  50.         jjObjectPresets[143].playerHandling = HANDLING::PARTICLE;
  51.         jjObjectPresets[143].curFrame = FirstTileFrameID;
  52.         jjObjectPresets[143].deactivates = false;
  53.         jjObjectPresets[143].isBlastable = false;
  54.        
  55.         if (jjAnimSets[ANIM::BRIDGE] != 0) {
  56.                 jjObjectPresets[OBJECT::BRIDGE].behavior = BridgeWrapper;
  57.                 jjANIMATION@ bridgeAnim = jjAnimations[jjAnimSets[ANIM::BRIDGE]];
  58.                 bridgeAnim.firstFrame = FirstTileFrameID;
  59.                 bridgeAnim.frameCount = 2;
  60.         }
  61.        
  62.         ///@Event 142=Pentagram                     |+|Goodies      |Pent-|agram|EventID:8|Down:c1
  63.         jjObjectPresets[142].behavior = Pentagram;
  64.         jjObjectPresets[142].playerHandling = HANDLING::PARTICLE;
  65.         jjObjectPresets[142].curFrame = FirstTileFrameID + 2;
  66.        
  67.         ///@Event 146=Collapsible Platform                     |+|Trigger      |Collapse|Plat|TriggerID:5
  68.         jjObjectPresets[146].behavior = CollapsiblePlatform;
  69.         jjObjectPresets[146].playerHandling = HANDLING::PARTICLE;
  70.         jjObjectPresets[146].curFrame = FirstTileFrameID + 5;
  71.         jjObjectPresets[146].isBlastable = false;
  72.        
  73.         ///@Event 147=Void                     |+|Scenery      |Void
  74.         jjObjectPresets[147].behavior = Void();
  75.         jjObjectPresets[147].playerHandling = HANDLING::SPECIAL;
  76.         jjObjectPresets[147].scriptedCollisions = true;
  77.         jjObjectPresets[147].curFrame = FirstTileFrameID + 6;
  78.         jjObjectPresets[147].isFreezable = false;
  79.         jjObjectPresets[147].triggersTNT = false;
  80.         jjObjectPresets[147].isBlastable = false;
  81.        
  82.         jjAnimations[jjObjectPresets[OBJECT::SPIKEBOLL].curAnim].firstFrame = FirstTileFrameID + 8;
  83.         jjObjectPresets[OBJECT::SPIKEBOLL].behavior = Choker2D;
  84.         if (jjAnimSets[ANIM::ROCK] == 0) jjAnimSets[ANIM::ROCK].load(); //for the sound effect
  85.         jjObjectPresets[OBJECT::SPIKEBOLL].deactivates = false;
  86.        
  87.         ///@Event 170=Choker Scenery                     |+|Trigger      |Choke|Scen
  88.         jjObjectPresets[170].behavior = ChokerScenery;
  89.         jjObjectPresets[170].playerHandling = HANDLING::PARTICLE;
  90.         jjObjectPresets[170].determineCurAnim(ANIM::DESTSCEN, 2);
  91.         jjObjectPresets[170].curFrame = jjAnimations[jjObjectPresets[170].curAnim];
  92.         jjObjectPresets[170].deactivates = false; //meh
  93.        
  94.         ///@Event 177=Lubella                     |-|Boss      |Lubella
  95.         jjObjectPresets[177].behavior = Lubella();
  96.         jjObjectPresets[177].playerHandling = HANDLING::PARTICLE; //while wating for activation
  97.         jjObjectPresets[177].bulletHandling = HANDLING::DETECTBULLET;
  98.         jjObjectPresets[177].scriptedCollisions = true;
  99.         jjObjectPresets[177].determineCurAnim(ANIM::DESTSCEN, 2);
  100.         jjObjectPresets[177].frameID = 0;
  101.         jjObjectPresets[177].curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[2]].load(0, "Lubella.j2a")];
  102.         jjObjectPresets[177].isFreezable = true;
  103.         jjObjectPresets[177].triggersTNT = true;
  104.         jjObjectPresets[177].isBlastable = true;
  105.         jjObjectPresets[177].isTarget = true;
  106.         jjObjectPresets[177].energy = 100;
  107.        
  108.         jjObjectPresets[OBJECT::GRASSPLATFORM].behavior = ForceWheel;
  109.         jjObjectPresets[OBJECT::GRASSPLATFORM].playerHandling = HANDLING::PARTICLE;
  110.         jjObjectPresets[OBJECT::GRASSPLATFORM].xAcc = jjObjectPresets[OBJECT::GRASSPLATFORM].yAcc = 0;
  111.         jjObjectPresets[OBJECT::GRASSPLATFORM].deactivates = false;
  112.        
  113.         jjAnimations[jjObjectPresets[OBJECT::SPIKEBOLL3D].curAnim].firstFrame = FirstTileFrameID + 9;
  114.         jjObjectPresets[OBJECT::SPIKEBOLL3D].behavior = Platform3D;
  115.         jjObjectPresets[OBJECT::SPIKEBOLL3D].playerHandling = HANDLING::PARTICLE; //shooting can get it confused, so let's not bother
  116.        
  117.         jjObjectPresets[21].behavior = Teleportal;
  118.         jjObjectPresets[21].playerHandling = HANDLING::PARTICLE;
  119. }
  120. void onLevelReload() {
  121.         MLLE::Palette.apply();
  122.         ImmobilizePlayers = false;
  123.         jjMusicLoad("cute.mod");
  124. }
  125.  
  126. class Blacken : jjBEHAVIORINTERFACE {
  127.         jjBEHAVIOR behavior;
  128.         Blacken(OBJECT::Object eventID) {
  129.                 jjOBJ@ preset = jjObjectPresets[eventID];
  130.                 behavior = preset.behavior;
  131.                 preset.behavior = this;
  132.                 preset.lightType = LIGHT::NORMAL;
  133.                 preset.light = 10;
  134.         }
  135.         void onBehave(jjOBJ@ obj) {
  136.                 obj.behave(behavior, false);
  137.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, obj.freeze == 0 ? SPRITE::SINGLECOLOR : SPRITE::FROZEN, obj.justHit == 0 ? 0 : 15);
  138.         }
  139. }
  140.  
  141. void LoweringFloor(jjOBJ@ obj) {
  142.         obj.xPos = obj.xOrg;
  143.         if (obj.state == STATE::START) {
  144.                 if (obj.creator == CREATOR::LEVEL) {
  145.                         obj.xOrg = obj.xPos -= 15;
  146.                         obj.yPos -= 11;
  147.                         obj.var[0] = jjAddObject(obj.eventID, obj.xPos + 16, obj.yPos);
  148.                 } else {
  149.                         obj.deactivates = false;
  150.                         obj.curFrame += 1;
  151.                 }
  152.                 obj.var[1] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 5);
  153.                 obj.state = STATE::WAIT;
  154.         } else if (obj.state == STATE::DEACTIVATE) {
  155.                 jjDeleteObject(obj.var[0]);
  156.                 obj.deactivate();
  157.         } else if (obj.state == STATE::WAIT) {
  158.                 if (jjTriggers[obj.var[1]])
  159.                         obj.state = STATE::FALL;
  160.                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  161.                         jjPLAYER@ play = jjLocalPlayers[i];
  162.                         if (play.yPos > obj.yPos - 22 && play.yPos < obj.yPos + 160 && abs(obj.xPos - play.xPos) < 8) {
  163.                                 play.yPos = obj.yPos - 23;
  164.                                 play.xPos += 1;
  165.                         }
  166.                 }
  167.                 obj.bePlatform(obj.xPos, obj.yPos);
  168.         } else {
  169.                 if (int(obj.yPos) & 31 == 4 && jjMaskedHLine(int(obj.xPos), 16, int(obj.yPos - 4))) {
  170.                         obj.clearPlatform();
  171.                         obj.delete();
  172.                 } else {
  173.                         obj.yPos += 1;
  174.                         obj.bePlatform(obj.xPos, obj.yPos - 1);
  175.                 }
  176.         }
  177.         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + 5, obj.curFrame);
  178. }
  179.  
  180. uint NumberOfPrayers = 0; //static
  181. void Pentagram(jjOBJ@ obj) {
  182.         if (obj.state == STATE::START) {
  183.                 obj.yPos -= 15;
  184.                 obj.state = STATE::WAIT;
  185.                 if (jjParameterGet(uint(obj.xOrg) >> 5, (uint(obj.yOrg) >> 5), 8, 1) == 1)
  186.                         obj.yPos += 32;
  187.         } else if (obj.state == STATE::DEACTIVATE) {
  188.                 obj.deactivate();
  189.         } else if (obj.state == STATE::WAIT) {
  190.                 bool beingKneltOn = false;
  191.                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  192.                         jjPLAYER@ play = jjLocalPlayers[i];
  193.                         if (play.keyDown && play.curAnim - jjAnimSets[play.setID] == RABBIT::DIVE && abs(obj.xPos - play.xPos) < 15 && abs(obj.yPos - play.yPos) < 30) {
  194.                                 beingKneltOn = true;
  195.                                 break;
  196.                         }
  197.                 }
  198.                
  199.                 if (beingKneltOn) {
  200.                         if (obj.counter < 0)
  201.                                 obj.counter = 50;
  202.                         else if (++obj.counter == 100) {
  203.                                 NumberOfPrayers += 1;
  204.                                 bool deleteMe = true;
  205.                                 jjSample(obj.xPos, obj.yPos, SOUND::DEVILDEVAN_DRAGONFIRE);
  206.                                 const uint8 eventID = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 8);
  207.                                 if (eventID == OBJECT::BOMB) {
  208.                                         jjOBJ@ bomb = jjObjects[jjAddObject(OBJECT::BOMB, obj.xPos, obj.yPos - 16, obj.objectID, CREATOR::OBJECT, BombWrapper)];
  209.                                         bomb.ySpeed = -4;
  210.                                         bomb.state = STATE::FLY;
  211.                                         if (jjAnimSets[ANIM::LIZARD] != 0)
  212.                                                 bomb.curAnim = jjAnimSets[ANIM::LIZARD] + 1;
  213.                                         deleteMe = false;
  214.                                 } else if (eventID == AREA::TRIGGERZONE) {
  215.                                         jjTriggers[jjParameterGet(uint(obj.xOrg) >> 5, (uint(obj.yOrg) >> 5) - 1, 0, 5)] = true;
  216.                                 } else {
  217.                                         jjOBJ@ spawn = jjObjects[jjAddObject(eventID, obj.xPos, obj.yPos - 16, obj.objectID, CREATOR::OBJECT)];
  218.                                         if (spawn.behavior == BEHAVIOR::PICKUP) {
  219.                                                 spawn.var[2] = 30;
  220.                                                 spawn.ySpeed = -4;
  221.                                                 spawn.xSpeed = 1;
  222.                                                 spawn.objType = HANDLING::DELAYEDPICKUP;
  223.                                         } else if (spawn.behavior == BEHAVIOR::SPRING) {
  224.                                                 deleteMe = false;
  225.                                                 obj.state = STATE::FADEOUT; //invisible, but can be deactivated
  226.                                         } else if (eventID == 21) //teleportal
  227.                                                 deleteMe = false;
  228.                                 }
  229.                                 if (deleteMe)
  230.                                         obj.delete();
  231.                                 else
  232.                                         obj.counter = 0;
  233.                         } else {
  234.                                 const uint rand = jjRandom();
  235.                                 if (rand & 0x8000 != 0) {
  236.                                         jjAddObject(obj.eventID, obj.xPos - 32 + (rand & 63), obj.yPos - 16 + ((rand >> 6) & 31), obj.objectID, CREATOR::OBJECT, PentagramMagic);
  237.                                 }
  238.                         }
  239.                 } else if (NumberOfPrayers < 3) {
  240.                         if (--obj.counter < -400 && obj.counter & 7 < 4) {
  241.                                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  242.                                         const jjPLAYER@ play = jjLocalPlayers[i];
  243.                                         const jjANIMATION@ anim = jjAnimations[jjAnimSets[play.setID] + RABBIT::DIVE];
  244.                                         const int playerID = play.playerID;
  245.                                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos - 20, anim.firstFrame + anim.frameCount - 1, 1, SPRITE::TRANSLUCENTPLAYER, playerID, 3,4, playerID);
  246.                                 }
  247.                         }
  248.                 }
  249.         }
  250.         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, SPRITE::ALPHAMAP, 29, 4);
  251.         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + 1, 1, SPRITE::ALPHAMAP, 29, 3);
  252. }
  253.  
  254. void PentagramMagic(jjOBJ@ obj) {
  255.         if (obj.state == STATE::START) {
  256.                 obj.curFrame += 2;
  257.                 obj.state = STATE::FLOAT;
  258.         }
  259.         if (jjRandom() & 31 == 0)
  260.                 obj.delete();
  261.         else
  262.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos -= 2, obj.curFrame, 1, SPRITE::ALPHAMAP, 29, 3);
  263. }
  264.  
  265. void BombWrapper(jjOBJ@ obj) {
  266.         if (obj.state == STATE::START)
  267.                 obj.playerHandling = HANDLING::PARTICLE;
  268.         else if (obj.ySpeed > 1 && obj.playerHandling == HANDLING::PARTICLE)
  269.                 obj.playerHandling = HANDLING::SPECIAL;
  270.         obj.behave(BEHAVIOR::BOMB);
  271.         if (obj.state == STATE::EXPLODE && obj.counter == 1) {
  272.                 for (int otherObjectID = jjObjectCount; --otherObjectID != 0;) {
  273.                         jjOBJ@ other = jjObjects[otherObjectID];
  274.                         if (other.behavior == RipplingFloor && abs(obj.xPos - 8 - other.xPos) < 16)
  275.                                 other.state = STATE::BOUNCE;
  276.                 }
  277.         }
  278. }
  279.  
  280. void BridgeWrapper(jjOBJ@ obj) {
  281.         obj.xPos += 1;
  282.         obj.behavior = BEHAVIOR::BRIDGE;
  283.         obj.behave();
  284. }
  285.  
  286. void RipplingFloor(jjOBJ@ obj) {
  287.         if (obj.state == STATE::START) {
  288.                 if (obj.creator == CREATOR::LEVEL) {
  289.                         obj.xPos -= 15;
  290.                 } else {
  291.                         obj.var[0] = obj.creatorID;
  292.                         if (int(obj.xPos) & 31 == 16)
  293.                                 obj.curFrame += 1;
  294.                 }
  295.                 obj.yPos -= 11;
  296.                 if (!jjMaskedPixel(int(obj.xPos) + 16, int(obj.yPos)) || jjTileGet(4, (int(obj.xPos) + 16) >> 5, int(obj.yOrg) >> 5) == 552)
  297.                         obj.var[1] = jjAddObject(obj.eventID, obj.xPos + 16, obj.yOrg, obj.objectID);
  298.                 obj.state = STATE::WAIT;
  299.         } else if (obj.state == STATE::DEACTIVATE) {
  300.                 //jjDeleteObject(obj.var[0]);
  301.                 //obj.deactivate();
  302.         } else {
  303.                 const auto lastY = obj.yPos;
  304.                 if (obj.state == STATE::BOUNCE) {
  305.                         obj.yPos = obj.yOrg - 11 + jjSin(obj.counter += 16) * 26;
  306.                         if (obj.counter == 1024) {
  307.                                 obj.counter = 0;
  308.                                 obj.state = STATE::WAIT;
  309.                         } else if (obj.counter == 48) {
  310.                                 for (uint i = 0 ; i < 2; ++i)
  311.                                         if (obj.var[i] != 0) {
  312.                                                 jjOBJ@ other = jjObjects[obj.var[i]];
  313.                                                 if (other.state == STATE::WAIT)
  314.                                                         other.state = STATE::BOUNCE;
  315.                                         }
  316.                         } else if (obj.counter == 768 - 64) { //highest point
  317.                                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  318.                                         jjPLAYER@ play = jjLocalPlayers[i];
  319.                                         if (play.platform == obj.objectID) {
  320.                                                 const auto realCurFrame = obj.curFrame;
  321.                                                 obj.ySpeed = -25;
  322.                                                 obj.special = CREATOR::PLAYER;
  323.                                                 obj.state = STATE::SPRING;
  324.                                                 obj.behave(BEHAVIOR::SPRING, false);
  325.                                                 obj.state = STATE::BOUNCE;
  326.                                                 obj.curFrame = realCurFrame;
  327.                                         }
  328.                                 }
  329.                         }
  330.                 }
  331.                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  332.                         jjPLAYER@ play = jjLocalPlayers[i];
  333.                         if (play.yPos > obj.yPos - 22 && play.yPos < obj.yPos + 160 && abs(obj.xPos - play.xPos) < 8) {
  334.                                 play.yPos = obj.yPos - 23;
  335.                                 play.xPos += 1;
  336.                         }
  337.                 }
  338.                 obj.bePlatform(obj.xPos, lastY);
  339.         }
  340.         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + 5, obj.curFrame, 1, SPRITE::NORMAL,0, 5);
  341. }
  342.  
  343. void CollapsiblePlatform(jjOBJ@ obj) {
  344.         if (obj.state == STATE::START) {
  345.                 obj.xPos -= 15;
  346.                 obj.yPos -= 11;
  347.                 obj.state = STATE::WAIT;
  348.                 obj.counter = 30;
  349.                 if (obj.creator == CREATOR::LEVEL)
  350.                         obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 5);
  351.         } else if (obj.state == STATE::DEACTIVATE) {
  352.                 obj.deactivate();
  353.         } else {
  354.                 if (obj.counter >= 6) {
  355.                         obj.bePlatform(obj.xPos, obj.yPos);
  356.                         if (obj.counter == 30) {
  357.                                 if (obj.var[0] == 0)
  358.                                         for (int i = 0; i < jjLocalPlayerCount; ++i) {
  359.                                                 const jjPLAYER@ play = jjLocalPlayers[i];
  360.                                                 if (play.platform == obj.objectID) {
  361.                                                         obj.counter = 29;
  362.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_COLLAPS);
  363.                                                         break;
  364.                                                 }
  365.                                         }
  366.                                 else if (jjTriggers[obj.var[0]])
  367.                                         obj.counter = 29;
  368.                         }
  369.                 } else
  370.                         obj.clearPlatform();
  371.                 for (int x = 0; x < 6; ++x)
  372.                         for (int y = 0; y < 4; ++y) {
  373.                                 const int position = x + y*6;
  374.                                 const uint16 tileID =  1 + (x>>1) + (y>>1)*10;
  375.                                 const TILE::Quadrant quadrant = TILE::Quadrant((x&1) + ((y&1)<<1));
  376.                                 const float xPos = obj.xPos + (x<<4), yPos = obj.yPos + (y<<4) - 4;
  377.                                 if (position < obj.counter)
  378.                                         jjDrawTile(xPos, yPos, tileID, quadrant);
  379.                                 else if (position == obj.counter && jjGameTicks & 1 == 0) {
  380.                                         jjPARTICLE@ fragment = jjAddParticle(PARTICLE::TILE);
  381.                                         if (fragment !is null) {
  382.                                                 fragment.xPos = xPos;
  383.                                                 fragment.yPos = yPos;
  384.                                                 fragment.tile.tileID = tileID;
  385.                                                 fragment.tile.quadrant = quadrant;
  386.                                                 fragment.xSpeed = (x - 2.5) / 6;
  387.                                         }
  388.                                 }
  389.                         }
  390.                 if (obj.counter < 30 && jjGameTicks & 1 == 1 && --obj.counter < 0)
  391.                         obj.delete();
  392.         }
  393. }
  394.  
  395. class Void : jjBEHAVIORINTERFACE {
  396.         void onBehave(jjOBJ@ obj) {
  397.                 if (obj.state == STATE::START) {
  398.                         obj.state = STATE::FADEIN;
  399.                         obj.xAcc = 0; //scale
  400.                         obj.special = jjRandom();
  401.                         if (obj.creatorType == CREATOR::LEVEL) {
  402.                                 int childID = 0;
  403.                                 for (int x = -1; x <= 1; x += 2)
  404.                                         for (int y = -1; y <= 1; y += 2)
  405.                                                 obj.var[childID++] = jjAddObject(obj.eventID, obj.xOrg + (x<<3), obj.yOrg + (y<<3));
  406.                         }
  407.                         const float scale = (jjTileGet(4, int(obj.xOrg)>>5, int(obj.yOrg)>>5) != 592) ? 1.0f : 2.5f;
  408.                         obj.xPos += (int(jjRandom() & 7) - 3.5) * scale;
  409.                         obj.yPos += (int(jjRandom() & 7) - 3.5) * scale;
  410.                 } else if (obj.state == STATE::DEACTIVATE) {
  411.                         obj.deactivate();
  412.                 } else if (obj.state == STATE::FADEOUT) {
  413.                         if (obj.xAcc > 0)
  414.                                 obj.xAcc -= 0.125;
  415.                         else if (obj.xSpeed == 0 && obj.ySpeed == 0)
  416.                                 obj.state = STATE::FADEIN;
  417.                         else
  418.                                 obj.delete();
  419.                 } else if (obj.state == STATE::FADEIN) {
  420.                         if (obj.counter != 0)
  421.                                 --obj.counter;
  422.                         else if (obj.xAcc < 1)
  423.                                 obj.xAcc += 0.125;
  424.                         else {
  425.                                 obj.state = STATE::ROTATE;
  426.                                 obj.playerHandling = HANDLING::SPECIAL;
  427.                         }
  428.                 } else if (obj.state == STATE::ROTATE) {
  429.                         obj.special += 3;
  430.                 }
  431.                
  432.                 if (obj.xAcc > 0) {
  433.                         jjDrawResizedSpriteFromCurFrame(obj.xPos + jjSin(obj.special) * 3, obj.yPos + jjCos(obj.special) * 3, obj.curFrame, obj.xAcc,obj.xAcc, SPRITE::ALPHAMAP, 0, 2);
  434.                         if (obj.xSpeed != 0 || obj.ySpeed != 0) {
  435.                                 obj.xPos += obj.xSpeed;
  436.                                 obj.yPos += obj.ySpeed;
  437.                                 if (++obj.age > 1100)
  438.                                         obj.delete();
  439.                         }
  440.                 }
  441.         }
  442.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  443.                 if (bullet is null) {
  444.                         player.hurt();
  445.                 } else {
  446.                         obj.state = STATE::FADEOUT;
  447.                         obj.playerHandling = HANDLING::PARTICLE;
  448.                         obj.counter = 110 + (3 - jjDifficulty) * 30;
  449.                         if (bullet.freeze != 0) //ice
  450.                                 obj.counter += 55;
  451.                 }
  452.                 return true;
  453.         }
  454. }
  455.  
  456. void Choker2D(jjOBJ@ obj) {
  457.         int angle;
  458.         const SPRITE::Mode mode = (obj.freeze == 0) ? SPRITE::NORMAL : SPRITE::FROZEN;
  459.         if (obj.state == STATE::KILL) {
  460.                 auto xSpeed = obj.xSpeed * 2, ySpeed = obj.ySpeed * 2;
  461.                 if (xSpeed >= 0) {
  462.                         if (xSpeed < 1.5)
  463.                                 xSpeed = 1.5;
  464.                 } else if (xSpeed > -1.5)
  465.                         xSpeed = -1.5;
  466.                 obj.behave(BEHAVIOR::PLATFORM, false); //make chain fragments
  467.                 obj.energy = 0;
  468.                 obj.state = STATE::FLY;
  469.                 obj.xSpeed = xSpeed;
  470.                 obj.ySpeed = ySpeed;
  471.                 obj.playerHandling = HANDLING::SPECIAL;
  472.                 obj.behavior = Choker2D; //still!
  473.                 return;
  474.         } else if (obj.energy <= 0) {
  475.                 if (obj.yPos > 2050) { //fell out of the level
  476.                         obj.delete();
  477.                 } else {
  478.                         const auto lastX = abs(obj.xSpeed);
  479.                         const auto lastY = obj.ySpeed;
  480.                         obj.behave(BEHAVIOR::ROTATINGROCK, false);
  481.                         if (abs(obj.xSpeed) < lastX) {
  482.                                 obj.xSpeed = (obj.xSpeed >= 0) ? lastX : -lastX;
  483.                         }
  484.                         if (jjEventGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5) == AREA::PATH) {
  485.                                 if ((obj.xSpeed >= 0) != (jjParameterGet(uint(obj.xPos) >> 5, uint(obj.yPos) >> 5, 0,1) == 0))
  486.                                         obj.xSpeed = -obj.xSpeed;
  487.                         }
  488.                         if (obj.state == STATE::DONE) {
  489.                                 obj.ySpeed = lastY;
  490.                                 obj.state = STATE::FLY;
  491.                         }
  492.                         angle = obj.var[0] / 2;
  493.                         obj.deactivates = false;
  494.        
  495.                         //this would work fine if available for ::PLATFORM chokers too, except that the level layout offers no such opportunities.
  496.                         for (int otherObjectID = jjObjectCount; --otherObjectID != 0;) {
  497.                                 jjOBJ@ other = jjObjects[otherObjectID];
  498.                                 if (other.behavior == ChokerScenery && other.state == STATE::IDLE && other.doesCollide(obj))
  499.                                         other.state = STATE::KILL;
  500.                         }
  501.                 }
  502.         } else {
  503.                 const auto lastX = obj.xPos, lastY = obj.yPos;
  504.                 obj.behave(BEHAVIOR::PLATFORM, false);
  505.                
  506.                 const int chainFrameID = jjAnimations[obj.killAnim];
  507.                 const int radadd=jjAnimFrames[chainFrameID].width-1;
  508.                 const int angleadd = -obj.var[5];
  509.                 int radius = 0;
  510.                 angle = obj.var[4] - angleadd * obj.var[2];
  511.  
  512.                 for (int n = 0; n < obj.var[2]; ++n) {
  513.                         jjDrawSpriteFromCurFrame(
  514.                                 obj.xOrg + radius * jjSin(angle),
  515.                                 obj.yOrg + radius * jjCos(angle),
  516.                                 chainFrameID, 1,
  517.                                 mode
  518.                         );
  519.                         angle += angleadd;
  520.                         radius += radadd;
  521.                 };
  522.                
  523.                 radius += radadd * 2;
  524.  
  525.                 obj.xPos = obj.xOrg + radius * jjSin(angle);
  526.                 obj.yPos = obj.yOrg + radius * jjCos(angle);
  527.                 obj.xSpeed = obj.xPos - lastX;
  528.                 obj.ySpeed = obj.yPos - lastY;
  529.         }
  530.         jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, -angle << 1, 1,1, mode);
  531.         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame - 1, 1, mode);
  532. }
  533.  
  534. void ChokerScenery(jjOBJ@ obj) {
  535.         if (obj.state == STATE::START) {
  536.                 obj.state = STATE::IDLE;
  537.                 obj.var[0] = int(obj.xPos) >> 5;
  538.                 obj.var[1] = int(obj.yPos) >> 5;
  539.                 obj.var[2] = jjTileGet(4, obj.var[0], obj.var[1]);
  540.                 const auto@ frames = jjTiles[obj.var[2]].getFrames();
  541.                 obj.var[3] = frames.length <= 1 ? 0 : frames[1];
  542.                 obj.var[4] = frames[(frames.length > 2) ? (frames.length - 1) : 0];
  543.         } else if (obj.state == STATE::KILL) {
  544.                 jjAddParticleTileExplosion(obj.var[0], obj.var[1], obj.var[4], false);
  545.                 jjTileSet(4, obj.var[0], obj.var[1], obj.var[3]);
  546.                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_DAMPED1);
  547.                 obj.state = STATE::DONE;
  548.                 obj.deactivates = false;
  549.         } else if (obj.state == STATE::DEACTIVATE) {
  550.                 if (jjDeactivatingBecauseOfDeath)
  551.                         jjTileSet(4, obj.var[0], obj.var[1], obj.var[2]);
  552.                 obj.deactivate();
  553.         }
  554. }
  555.  
  556. int DesiredModSpeed;
  557. class Lubella : jjBEHAVIORINTERFACE {
  558.         jjPLAYER@ nearestPlayer(const jjOBJ@ obj) const {
  559.                 return jjPlayers[obj.findNearestPlayer(0x7FFFFFFF)];
  560.         }
  561.         void onBehave(jjOBJ@ obj) {
  562.                 if (obj.justHit == 0 && obj.doesHurt > 1) {
  563.                         obj.justHit = (obj.doesHurt -= 2);
  564.                 }
  565.                 //breathing counter:
  566.                 ++obj.age;
  567.                 if (obj.state != STATE::FREEZE) {
  568.                         if (obj.state == STATE::ATTACK || obj.state == STATE::FIRE) {
  569.                                 if (obj.frameID < 15) {
  570.                                         obj.frameID += 1;
  571.                                         obj.yPos -= 1;
  572.                                         obj.yOrg -= 1;
  573.                                 }
  574.                         } else {
  575.                                 if (obj.frameID > 0) {
  576.                                         obj.frameID -= 1;
  577.                                         obj.yPos += 1;
  578.                                         obj.yOrg += 1;
  579.                                 }
  580.                         }
  581.                 }
  582.                
  583.                 if (obj.energy < 100 && jjGetModSpeed() != DesiredModSpeed)
  584.                         jjSetModSpeed(DesiredModSpeed);
  585.                
  586.                 switch (obj.state) {
  587.                 case STATE::START:
  588.                         obj.state = STATE::DELAYEDSTART;
  589.                         obj.var[1] = 75; //next energy point to descend at
  590.                         obj.yPos += 200;
  591.                         return;
  592.                 case STATE::DELAYEDSTART:
  593.                         for (int i = 0; i < jjLocalPlayerCount; ++i) {
  594.                                 jjPLAYER@ localPlayer = jjLocalPlayers[i];
  595.                                 if (localPlayer.bossActivated) {
  596.                                         localPlayer.boss = obj.objectID;
  597.                                         obj.state = STATE::FADEIN;
  598.                                         break;
  599.                                 }
  600.                         }
  601.                         return;
  602.                 case STATE::DEACTIVATE:
  603.                         obj.deactivate(); //sure
  604.                         return;
  605.                 case STATE::FREEZE:
  606.                         if (--obj.freeze == 0) {
  607.                                 obj.unfreeze(0);
  608.                                 obj.state = obj.oldState;
  609.                         }
  610.                         return;
  611.                 case STATE::FADEIN:
  612.                         obj.yPos = obj.yOrg + 200 - jjSin(obj.counter += 4) * 256;
  613.                         if (obj.counter >= 256) {
  614.                                 obj.justHit = 3;
  615.                                 obj.var[0] = 1;
  616.                                 if (obj.counter == 360) {
  617.                                         obj.playerHandling = HANDLING::SPECIAL;
  618.                                         obj.state = STATE::WALK;
  619.                                         obj.doesHurt = 10;
  620.                                         obj.counter = 0;
  621.                                         jjSample(obj.xPos, obj.yPos, SOUND::PICKUPS_BOING_CHECK);
  622.                                         for (int i = 0; i < jjLocalPlayerCount; ++i) {
  623.                                                 jjPLAYER@ localPlayer = jjLocalPlayers[i];
  624.                                                 if (localPlayer.doesCollide(obj)) {
  625.                                                         localPlayer.hurt();
  626.                                                         localPlayer.yPos = obj.yPos - 25;
  627.                                                         localPlayer.ballTime = 50;
  628.                                                 }
  629.                                         }
  630.                                 }
  631.                         }
  632.                         return;
  633.                 case STATE::FADEOUT:
  634.                         obj.yPos = obj.yOrg + 200 - jjSin(obj.counter -= 4) * 256;
  635.                         if (obj.counter <= 256) {
  636.                                 obj.var[0] = 0;
  637.                                 obj.justHit = 4;
  638.                                 if (obj.counter == 0) {
  639.                                         obj.state = STATE::DUCK;
  640.                                         obj.playerHandling = HANDLING::PARTICLE;
  641.                                 }
  642.                         }
  643.                         return;
  644.                 case STATE::DUCK:
  645.                         if (obj.energy <= 0) {
  646.                                 obj.state = STATE::DONE;
  647.                                 jjNxt();
  648.                         } else if (++obj.counter == 700) {
  649.                                 obj.counter = 0;
  650.                                 obj.state = STATE::FADEIN;
  651.                         } else {
  652.                                 if (obj.var[1] != 50 && obj.counter < 80 && obj.counter & 3 == 1)
  653.                                         jjAddObject(OBJECT::BAT, obj.xPos - 32 + ((obj.counter & 15) << 4), obj.yPos + ((obj.counter >> 4) << 5), (obj.special < 0) ? 2 : 0, CREATOR::OBJECT, BossBat);
  654.                                 if (obj.var[1] != 25 && obj.counter & 63 == 5) {
  655.                                         const jjPLAYER@ target = nearestPlayer(obj);
  656.                                         jjAddObject(OBJECT::SPIKEBOLL, target.xPos, target.cameraY - 130, obj.objectID, CREATOR::OBJECT, BossChoker);
  657.                                 }
  658.                         }
  659.                         break;
  660.                 case STATE::WALK:
  661.                         if (obj.energy <= obj.var[1]) { //regular (health-based) lowering intervals
  662.                                 obj.var[1] = obj.var[1] - 25;
  663.                                 obj.counter = 360;
  664.                                 obj.state = STATE::FADEOUT;
  665.                                 DesiredModSpeed -= 1;
  666.                         } else if (++obj.counter >= 165 - (jjDifficulty * 15)) { //attack patterns
  667.                                 obj.counter = 0;
  668.                                 obj.counterEnd = 0;
  669.                                 if ((obj.var[2] = obj.var[2] ^ 1) == 1) {
  670.                                         obj.state = STATE::ATTACK;
  671.                                         if ((obj.var[3] = (obj.var[3] + 1) % 3) == 2) //attack both sides
  672.                                                 obj.var[4] = obj.var[6] = 1;
  673.                                         else { //attack player only
  674.                                                 const jjPLAYER@ play = nearestPlayer(obj);
  675.                                                 obj.var[4] = (play.xPos < obj.xPos) ? 1 : 0;
  676.                                                 obj.var[6] = obj.var[4] ^ 1;
  677.                                         }
  678.                                 } else {
  679.                                         obj.state = STATE::FIRE;
  680.                                 }
  681.                         }
  682.                         break;
  683.                 case STATE::ATTACK:
  684.                         if (obj.counterEnd < 92)
  685.                                 obj.counterEnd += 1;
  686.                         else if (++obj.counter == 320) {
  687.                                 obj.state = STATE::WALK;
  688.                                 obj.counter = 0;
  689.                         } else if (obj.counter > 40 && obj.counter & 15 == 0) {
  690.                                 const auto headYPos = obj.yPos - 66 - 92 + jjSin(obj.age << 2) * 3;
  691.                                 for (int hornDir = -1; hornDir <= 1; hornDir += 2)
  692.                                         if (obj.var[5 + hornDir] == 1)
  693.                                                 for (int dec = 0; dec < 8; ++dec) {
  694.                                                         jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::FIREBALLBULLETPU, obj.xPos + (38 + dec * 8) * hornDir, headYPos - dec * 7, obj.objectID)];
  695.                                                         bullet.xPos = bullet.xOrg;
  696.                                                         bullet.ySpeed = 5;
  697.                                                         bullet.xSpeed = (dec + 1) * 0.7 * hornDir;
  698.                                                         bullet.xAcc = bullet.yAcc = 0;
  699.                                                         bullet.playerHandling = HANDLING::ENEMYBULLET;
  700.                                                         bullet.animSpeed = (jjDifficulty != 0) ? 2 : 1;
  701.                                                 }
  702.                         }
  703.                         return;
  704.                 case STATE::FIRE:
  705.                         if (++obj.counter == 520) {
  706.                                 obj.state = STATE::WALK;
  707.                                 obj.counter = 0;
  708.                         } else if ((obj.counterEnd += (1 << jjDifficulty)) == 240) {
  709.                                 const jjPLAYER@ play = nearestPlayer(obj);
  710.                                 const auto spawnY = obj.yPos - 50;
  711.                                 const auto angle = atan2(play.yPos - spawnY, play.xPos - obj.xPos);
  712.                                 const float xSpeed = cos(angle) * 3;
  713.                                 const float ySpeed = sin(angle) * 3;
  714.                                 jjOBJ@ voidHub = jjObjects[jjAddObject(147, obj.xPos, spawnY, obj.objectID, CREATOR::LEVEL)];
  715.                                 voidHub.xSpeed = xSpeed;
  716.                                 voidHub.ySpeed = ySpeed;
  717.                                 for (int i = 0; i < 4; ++i) {
  718.                                         jjOBJ@ voidChild = jjObjects[voidHub.var[i]];
  719.                                         voidChild.xSpeed = xSpeed;
  720.                                         voidChild.ySpeed = ySpeed;
  721.                                 }
  722.                         }
  723.                         return;
  724.                 case STATE::DONE:
  725.                         obj.yPos += 1;
  726.                         return;
  727.                 }
  728.                
  729.                 //if break instead of return, track you horizontally/adjust hair angle:
  730.                 const jjPLAYER@ play = nearestPlayer(obj);
  731.                 if (play.xPos > obj.xPos + 70) {
  732.                         obj.xPos += 0.5;
  733.                         if (obj.special > -9)
  734.                                 obj.special -= 1;
  735.                 } else if (play.xPos < obj.xPos + -70) {
  736.                         obj.xPos -= 0.5;
  737.                         if (obj.special < 9)
  738.                                 obj.special += 1;
  739.                 } else if (obj.special > 0)
  740.                         obj.special -= 1;
  741.                 else if (obj.special < 0)
  742.                         obj.special += 1;
  743.         }
  744.         void onDraw(jjOBJ@ obj) {
  745.                 const bool isFrozen = obj.freeze != 0;
  746.                 const SPRITE::Mode mode = !isFrozen ? SPRITE::NORMAL : SPRITE::FROZEN;
  747.                 float yBreathing = jjSin(obj.age << 2);
  748.                 const auto titsYPos = obj.yPos - obj.justHit - yBreathing * 4;
  749.                 if (isFrozen) yBreathing = 0;
  750.                 const auto hairYPos = obj.yPos - 136 + (!isFrozen ? (jjSin((obj.age << 2) - 96) * 6) : 0);
  751.                 for (int hairFrameID = 3; hairFrameID <= 8; hairFrameID += 5)
  752.                         jjDrawRotatedSpriteFromCurFrame(obj.xPos, hairYPos, obj.curFrame + hairFrameID, obj.special,1,1, mode,0, 5); //hair
  753.                 const auto state = (obj.state != STATE::FREEZE) ? obj.state : obj.oldState;
  754.                 const bool summoningArms = state == STATE::FIRE && obj.counter > 16;
  755.                 const auto armY = obj.yPos - 30;
  756.                 for (int armDir = -1; armDir <= 1; armDir += 2) {
  757.                         const bool hornyArm = state == STATE::ATTACK && obj.var[5 + armDir] == 1;
  758.                         const int armAngle =
  759.                                 !summoningArms ?
  760.                                         !hornyArm ?
  761.                                                 (16 + int(yBreathing * 16) /*+ (!isFrozen ? (obj.justHit << 1) : 0)*/) :
  762.                                         ((50 - int(abs(int(obj.counterEnd) - 50))) * 7 + (obj.counterEnd == 92 ? int(yBreathing * 6) : 0)) :
  763.                                 (64 + int(yBreathing * 7));
  764.                         const int armFrame = obj.curFrame + (!summoningArms && !hornyArm ? 2 : 9);
  765.                         const auto armX = obj.xPos + 28 * armDir;
  766.                         jjDrawRotatedSpriteFromCurFrame(armX, armY, armFrame, armDir * armAngle, armDir,1, mode,0, 5);
  767.                         if (summoningArms || hornyArm) {
  768.                                 const float sin = jjSin(armAngle);
  769.                                 const float cos = jjCos(armAngle);
  770.                                 jjDrawRotatedSpriteFromCurFrame(armX + (cos*23 + sin*53) * armDir, armY + (sin*-23 + cos*53), armFrame + 1, -armDir * (summoningArms ? ((armAngle * 3) - int(abs(int(obj.counterEnd) - 128))) : (armAngle >> 1)), -armDir,1, mode,0, 4);
  771.                         }
  772.                 }
  773.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + 1, 0, mode,0, 5); //torso
  774.                 const auto headYPos = obj.yPos - 66 + yBreathing * 3;
  775.                 const bool frowning = obj.justHit != 0 && !isFrozen; //maybe
  776.                 if (!frowning) {
  777.                         jjDrawSpriteFromCurFrame(obj.xPos, headYPos, obj.curFrame + 5, 0, mode,0, 5); //eye holes
  778.                         if (!isFrozen) {
  779.                                 const auto eyeYPos = headYPos - 35;
  780.                                 const jjPLAYER@ play = nearestPlayer(obj);
  781.                                 for (int i = -18; i <= 18; i += 36) {
  782.                                         const auto eyeXPos = obj.xPos + i;
  783.                                         const auto eyeAngle = atan2(play.yPos - eyeYPos, play.xPos - eyeXPos);
  784.                                         jjDrawSpriteFromCurFrame(eyeXPos + cos(eyeAngle) * 6, eyeYPos + sin(eyeAngle) * 6, obj.curFrame + 6, i, SPRITE::NORMAL,0, 5);
  785.                                 }
  786.                         }
  787.                 }
  788.                 jjDrawSpriteFromCurFrame(obj.xPos, headYPos, obj.curFrame + 4, 0, mode,0, 5); //head
  789.                 if (frowning)
  790.                         jjDrawSpriteFromCurFrame(obj.xPos, headYPos, obj.curFrame + 7, 0, mode,0, 5); //grouchy face
  791.                 if (state == STATE::ATTACK && obj.counterEnd > 40) {
  792.                         for (int hornDir = -1; hornDir <= 1; hornDir += 2)
  793.                                 if (obj.var[5 + hornDir] == 1)
  794.                                         for (int dec = 0; dec < 8; ++dec) {
  795.                                                 const float scale = 1.35 - dec * 0.05;
  796.                                                 jjDrawRotatedSpriteFromCurFrame(obj.xPos + (38 + dec * 8) * hornDir, headYPos - 92 - dec * 7, obj.curFrame + 11, (30 + dec * 7) * hornDir, hornDir * scale, scale, !isFrozen ? SPRITE::BRIGHTNESS : SPRITE::FROZEN, 128 - (dec << 3), 5);
  797.                                         }
  798.                 }
  799.                 const bool emerged = obj.var[0] == 1;
  800.                 if (emerged)
  801.                         jjDrawSpriteFromCurFrame(obj.xPos, titsYPos + 5, obj.curFrame, 1, SPRITE::SHADOW,0, 4); //tits
  802.                 jjDrawSpriteFromCurFrame(obj.xPos, titsYPos, obj.curFrame, 1, mode,0, emerged ? 3 : 5); //tits
  803.                 if (summoningArms)
  804.                         for (int half = 0; half < 2; ++half)
  805.                                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos - 45, FirstTileFrameID + 2 + half, jjGameTicks << 1, 1,1, SPRITE::ALPHAMAP, 0, 3);
  806.         }
  807.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  808.                 if (bullet is null) {
  809.                         if (player.ballTime < 30) {
  810.                                 player.ballTime = 35;
  811.                                 player.xSpeed = (player.xPos - obj.xPos) / 7;
  812.                                 player.ySpeed = (player.yPos - obj.yPos) / 6;
  813.                                 player.buttstomp = 121;
  814.                                 obj.justHit = 8;
  815.                                 jjSample(obj.xPos, obj.yPos, SOUND::PICKUPS_BOING_CHECK);
  816.                         }
  817.                 } else {
  818.                         if (obj.justHit == 0) {
  819.                                 if (obj.freeze != 0) {
  820.                                         obj.freeze = 0;
  821.                                         obj.unfreeze(1);
  822.                                         obj.state = obj.oldState;
  823.                                         force += 3;
  824.                                 } else {
  825.                                         if (jjIsTSF)
  826.                                                 jjSample(obj.xPos, obj.yPos - 70, SOUND::Sample(SOUND::LORISOUNDS_HURT0 + (jjRandom() & 7)), 63, 25000);
  827.                                         else
  828.                                                 jjSample(obj.xPos, obj.yPos - 70, SOUND::Sample(SOUND::JAZZSOUNDS_HEY1 + (jjRandom() & 3)), 63, 17500);
  829.                                 }
  830.                                 obj.justHit = 7;
  831.                                 obj.energy -= force;
  832.                                 obj.doesHurt = 6;
  833.                         }
  834.                         bullet.state = STATE::EXPLODE; //no fireballs in level
  835.                 }
  836.                 return true;
  837.         }
  838. }
  839.  
  840. void BossBat(jjOBJ@ obj) {
  841.         if (obj.state == STATE::START) {
  842.                 obj.direction = obj.creatorID - 1;
  843.                 obj.determineCurAnim(ANIM::BAT, 0);
  844.                 obj.state = STATE::WAKE;
  845.                 //obj.points = 0;
  846.         } if (obj.state == STATE::WAKE) {
  847.                 if (++obj.counter < 200) {
  848.                         obj.xPos += obj.direction;
  849.                         obj.yPos -= 2.5;
  850.                         obj.frameID = obj.counter >> 3;
  851.                         obj.determineCurFrame();
  852.                 } else
  853.                         obj.state = STATE::FLY;
  854.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLECOLOR, 0, 3);
  855.         } else {
  856.                 obj.xOrg = jjPlayers[obj.var[0]].xPos;
  857.                 obj.yOrg = jjPlayers[obj.var[0]].yPos;
  858.                 obj.behave(jjObjectPresets[OBJECT::BAT].behavior);
  859.         }
  860. }
  861.  
  862. void BossChoker(jjOBJ@ obj) {
  863.         if (obj.state == STATE::START) {
  864.                 obj.energy = 0;
  865.                 obj.bulletHandling = HANDLING::DESTROYBULLET;
  866.                 obj.isFreezable = true; //this is fine
  867.                 obj.isBlastable = false;
  868.                 obj.state = STATE::FALL;
  869.                 obj.ySpeed = 3 + jjDifficulty;
  870.                 obj.curFrame = jjAnimations[obj.curAnim];
  871.         } else if (obj.freeze != 0) {
  872.                 if (--obj.freeze == 0) {
  873.                         obj.unfreeze(0);
  874.                         obj.state = obj.oldState;
  875.                 }
  876.         } else if (++obj.age >= 200) {
  877.                 obj.deactivate();
  878.         } else
  879.                 obj.yPos += obj.ySpeed;
  880.         jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, -(obj.objectID + obj.age) << 3, 1,1, obj.freeze == 0 ? SPRITE::NORMAL : SPRITE::FROZEN, 0, 3);
  881.         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame - 1, 1, obj.freeze == 0 ? SPRITE::NORMAL : SPRITE::FROZEN, 0, 3);
  882. }
  883.  
  884. ///@Event 211=Force Wheel                  |-|Platform  |Force  |Wheel   |Sync:2|Speed:-6|Length:4|Swing:{Circle,Pendulum}1|Angle:{R,D,UR,DR}2
  885. void ForceWheel(jjOBJ@ obj) {
  886.         if (obj.state == STATE::START) {
  887.                 obj.xAcc = obj.xOrg;
  888.                 obj.yAcc = obj.yOrg;
  889.                 const int angle = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 13, 2);
  890.                 if (angle != 1)
  891.                         obj.var[9] = 1;
  892.                 if (angle == 2)
  893.                         obj.var[10] = -1;
  894.                 else if (angle & 1 == 1)
  895.                         obj.var[10] = 1;
  896.                 obj.doesHurt = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 2);
  897.         } else if (obj.state == STATE::DEACTIVATE) {
  898.                 if (obj.xAcc != 0 && obj.yAcc != 0) {
  899.                         obj.xOrg = obj.xAcc;
  900.                         obj.yOrg = obj.yAcc;
  901.                 }
  902.         }
  903.         obj.behave(BEHAVIOR::PLATFORM);
  904.         bool stoodOn = false;
  905.         for (int i = 0; i < jjLocalPlayerCount; ++i) {
  906.                 const jjPLAYER@ play = jjLocalPlayers[i];
  907.                 if (play.platform == obj.objectID) {
  908.                         stoodOn = true;
  909.                         if (play.spriteMode == SPRITE::INVISIBLE && (play.blink == 0 || (jjRenderFrame & 4) == 4))
  910.                                 jjDrawSprite(play.xPos, play.yPos, play.setID, play.keyRun ? RABBIT::RUN3 : RABBIT::RUN1, jjGameTicks >> 2, play.direction, SPRITE::PLAYER, play.playerID, 3);
  911.                 }
  912.         }
  913.         if (!stoodOn && obj.doesHurt != 0) {
  914.                 if (obj.xOrg > obj.xAcc + 0.5) obj.xOrg -= 1;
  915.                 else if (obj.xOrg < obj.xAcc - 0.5) obj.xOrg += 1;
  916.                 if (obj.yOrg > obj.yAcc + 0.5) obj.yOrg -= 1;
  917.                 else if (obj.yOrg < obj.yAcc - 0.5) obj.yOrg += 1;
  918.         }
  919. }
  920. void onPlayer(jjPLAYER@ play) {
  921.         if (ImmobilizePlayers) {
  922.                 //play.spriteMode = SPRITE::INVISIBLE;
  923.                 play.keyFire = play.keyJump = play.keyRun = play.keySelect = play.keyLeft = play.keyRight = play.keyUp = play.keyDown = false;
  924.                 play.xSpeed = 0;
  925.                 play.invincibility = -2;
  926.                 return;
  927.         } //else play.spriteMode = SPRITE::PLAYER;
  928.         jjOBJ@ obj = jjObjects[play.platform];
  929.         if (obj.behavior == ForceWheel) {
  930.                 int negacc;
  931.                                 if (obj.counter>256*511) {
  932.                                         negacc=obj.counter/256-1024;
  933.                                 } else {
  934.                                         negacc=obj.counter/256;
  935.                                 }
  936.                                 if ((obj.var[3]>32) && (negacc>0)) {
  937.                                         negacc+=32;
  938.                                 } else if ((obj.var[3]<-32) && (negacc<0)) {
  939.                                         negacc-=32;
  940.                                 }
  941.                 const int maxChange = int(abs(negacc)) + 34;
  942.                 play.spriteMode = SPRITE::INVISIBLE;
  943.                 play.idle = 0;
  944.                 if (play.keyRight) {
  945.                         obj.var[3] = obj.var[3] + maxChange;
  946.                         if (play.direction == 1)
  947.                                 play.keyRight = false;
  948.                 } else if (play.keyLeft) {
  949.                         obj.var[3] = obj.var[3] - maxChange;
  950.                         if (play.direction == -1)
  951.                                 play.keyLeft = false;
  952.                 } else
  953.                         play.spriteMode = SPRITE::PLAYER;
  954.                 obj.var[8] = obj.var[8] + maxChange - 34;
  955.                 while (obj.var[8] >= 256) {
  956.                         obj.var[8] = obj.var[8] - 256;
  957.                         const int
  958.                                 xTarget = int(obj.xOrg) + play.direction * obj.var[9],
  959.                                 yTarget = int(obj.yOrg) + play.direction * obj.var[10];
  960.                         if (!jjMaskedPixel(xTarget, yTarget) && jjEventGet(xTarget>>5, yTarget>>5) != AREA::STOPENEMY) {
  961.                                 obj.xOrg = xTarget;
  962.                                 obj.yOrg = yTarget;
  963.                         }
  964.                 }
  965.                 play.xSpeed = 0;
  966.         } else
  967.                 play.spriteMode = SPRITE::PLAYER;
  968.                
  969.         if (play.boss != 0 && jjSubscreenHeight >= 320)
  970.                 play.cameraFreeze(387*32 + 16, 360, true, true);
  971.         else
  972.                 play.cameraUnfreeze();
  973. }
  974.  
  975. void Platform3D(jjOBJ@ obj) {
  976.         if (obj.state == STATE::START && jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 1, 1) == 1)
  977.                 obj.special = 512;
  978.         obj.behave(BEHAVIOR::SPIKEBOLL3D);
  979.         if (obj.curFrame != 0 && obj.state != STATE::DEACTIVATE) {
  980.                 obj.bePlatform(obj.xPos, obj.yPos);
  981.                 if (obj.counterEnd < 50) {
  982.                         ++obj.counterEnd;
  983.                         --obj.counter;
  984.                         obj.var[4] = obj.special - obj.var[5];
  985.                 }
  986.         } else {
  987.                 obj.clearPlatform();
  988.                 if (obj.counterEnd != 0) {
  989.                         obj.counterEnd = 0;
  990.                         obj.special = obj.special ^ 512;
  991.                 }
  992.         }
  993. }
  994.  
  995. bool ImmobilizePlayers = false;
  996. class TileFragment {
  997.         uint16 tileID;
  998.         uint8 quadrant;
  999.         int xOrg, yOrg, angle;
  1000.         TileFragment(){}
  1001.         TileFragment(uint16 i, uint8 q, int x, int y, int a){ tileID = i; quadrant = q; xOrg = x; yOrg = y; angle = a; }
  1002. }
  1003. array<TileFragment> TileFragments;
  1004. void GatherTileFragments() {
  1005.         const int screenWidthTiles = (jjSubscreenWidth + 31) >> 5;
  1006.         const int screenHeightTiles = (jjSubscreenHeight + 31) >> 5;
  1007.         for (int i = 0; i < jjLocalPlayerCount; ++i) {
  1008.                 const jjPLAYER@ play = jjLocalPlayers[i];
  1009.                 const int cameraX = int(play.cameraX), cameraY = int(play.cameraY);
  1010.                 for (int layerID = 6; layerID >= 3; --layerID) {
  1011.                         const jjLAYER@ layer = jjLayers[layerID];
  1012.                         int layerCameraX, layerCameraY;
  1013.                         if (layerID != 6) {
  1014.                                 layerCameraX = cameraX & ~31;
  1015.                                 layerCameraY = cameraY & ~31;
  1016.                         } else {
  1017.                                 layerCameraX = int(layer.getXPosition(play));
  1018.                                 layerCameraY = int(layer.getYPosition(play));
  1019.                         }
  1020.                         const int layerXOrg = layerCameraX >> 5, layerYOrg = layerCameraY >> 5;
  1021.                         if (layerID == 6) {
  1022.                                 layerCameraX = cameraX - ((layerCameraX & 31));
  1023.                                 layerCameraY = cameraY - ((layerCameraY & 31));
  1024.                         }
  1025.                         for (int x = 0; x <= screenWidthTiles; ++x)
  1026.                                 for (int y = 0; y <= screenHeightTiles; ++y) {
  1027.                                         const uint16 tileID = jjTileGet(layerID, layerXOrg + x, layerYOrg + y);
  1028.                                         if (tileID != 0)
  1029.                                                 for (int q = 0; q < 4; ++q)
  1030.                                                         TileFragments.insertLast(TileFragment(tileID, q, layerCameraX + (x << 5) + ((q & 1) << 4), layerCameraY + (y << 5) + ((q & 2) << 3), jjRandom()));
  1031.                                 }
  1032.                         }
  1033.         }
  1034. }
  1035. void Teleportal(jjOBJ@ obj) {
  1036.         if (obj.counter == 0) {
  1037.                 ImmobilizePlayers = true;
  1038.                 GatherTileFragments();
  1039.                 for (int layerID = 6; layerID >= 3; --layerID)
  1040.                         jjLayerHasTiles[layerID] = false;
  1041.                 //jjLayers[6].spriteMode = SPRITE::BLEND_NORMAL;
  1042.                 jjSamplePriority(SOUND::COMMON_TELPORT1);
  1043.         } else if (obj.counter == 256) {
  1044.                 const uint8 warpID = jjParameterGet(uint(obj.xOrg) >> 5, (uint(obj.yOrg) >> 5) - 1, 0, 8);
  1045.                 jjSetLayerXSpeed(7, 0, false);
  1046.                 jjSetLayerXSpeed(8, 0, false);
  1047.                 jjSetLayerYSpeed(8, 0, false);
  1048.                 for (int i = 0; i < jjLocalPlayerCount; ++i)
  1049.                         jjLocalPlayers[i].warpToID(warpID, true);
  1050.                 jjSetLayerXSpeed(7, 0.1963043, false);
  1051.                 jjSetLayerXSpeed(8, 0.6, false);
  1052.                 jjSetLayerYSpeed(8, 0.6, false);
  1053.                 TileFragments.resize(0);
  1054.         } else if (obj.counter == 258) {
  1055.                 GatherTileFragments();
  1056.         } else if (obj.counter >= 516) {
  1057.                 for (int layerID = 6; layerID >= 3; --layerID)
  1058.                         jjLayerHasTiles[layerID] = true;
  1059.                 //jjLayers[6].spriteMode = SPRITE::NORMAL;
  1060.                 ImmobilizePlayers = false;
  1061.                 TileFragments.resize(0);
  1062.                 jjSamplePriority(SOUND::COMMON_TELPORT2);
  1063.                 obj.delete();
  1064.         } else {
  1065.                 const int radius = ((obj.counter < 256) ? obj.counter * 4 : (obj.counter < 510) ? (510 - obj.counter) * 4 : (517 - obj.counter) / 2);
  1066.                 //jjLayers[6].spriteParam = (radius / 4 < 256) ? (255 - radius / 4) : 0;
  1067.                 for (uint i = 0; i < TileFragments.length; ++i) {
  1068.                         TileFragment@ fragment = TileFragments[i];
  1069.                         fragment.angle += 4;
  1070.                         jjDrawTile(fragment.xOrg + jjSin(fragment.angle) * radius, fragment.yOrg + jjCos(fragment.angle) * radius, fragment.tileID, TILE::Quadrant(fragment.quadrant));
  1071.                 }
  1072.         }
  1073.         obj.counter += (obj.counter < 500) ? 2 : 1;
  1074. }
  1075.  
  1076. void onFunction0(jjPLAYER@ play, bool left) {
  1077.         if (abs(play.xSpeed) > 3 && ((play.xSpeed < 0) == left))
  1078.                 play.ySpeed = 5;
  1079. }
  1080.  
  1081. void onFunction1(jjPLAYER@ play) {
  1082.         if (!play.bossActivated) {
  1083.                 play.activateBoss();
  1084.                 if (jjMusicLoad("beyond_-_creepy.it", true))
  1085.                         jjSetModSpeed(DesiredModSpeed = 6);
  1086.         }
  1087. }