Downloads containing EmeraldusV.j2as

Downloads
Name Author Game Mode Rating
JJ2+ Only: EmeraldusVFeatured Download Violet CLM Multiple 8.9 Download file

File preview

  1. #include "MLLE-Include-1.4.asc"
  2. const bool MLLESetupSuccessful = MLLE::Setup();
  3. #pragma require "EmeraldusV-MLLE-Data-1.j2l"
  4. #pragma require "EmeraldusV.j2l"
  5. #pragma require "EmeraldusV.j2a"
  6.  
  7. enum AnimSets { BALL, BOSS, BRIDGE, BUZZBOMBER, CHOPPER, CRABMEAT, MOTOBUG, NEWTRON, PLATFORM, TREES, TURTLETTE, _LAST };
  8.  
  9. array<jjLAYER@> Ripples;
  10. jjLAYER@ Clouds;
  11.  
  12. const uint8 CornerEventID = OBJECT::APPLE; //whatever
  13.  
  14. void onLevelLoad() {
  15.         jjLAYER@ ripples = MLLE::GetLayer("Ripples");
  16.         auto layerOrder = jjLayerOrderGet();
  17.         const auto index = layerOrder.findByRef(ripples);
  18.         int numberOfLayersToAdd = 24; //adjust as needed based on layer speed/level height
  19.         while (true) {
  20.                 Ripples.insertLast(ripples);
  21.                 layerOrder.insertAt(index, ripples);
  22.                 if (--numberOfLayersToAdd > 0) {
  23.                         @ripples = jjLAYER(ripples);
  24.                         ripples.xOffset = jjRandom() & ((16*32) - 1);
  25.                         ripples.yOffset -= 16;
  26.                 } else
  27.                         break;
  28.         }
  29.         jjLayerOrderSet(layerOrder);
  30.         jjUseLayer8Speeds = true;
  31.        
  32.         @Clouds = MLLE::GetLayer("Clouds");
  33.        
  34.         jjObjectPresets[CornerEventID].behavior = BEHAVIOR::INACTIVE; //zonify
  35.         jjObjectPresets[OBJECT::ORANGE].behavior = LoopObject;
  36.         jjObjectPresets[OBJECT::ORANGE].playerHandling = HANDLING::PARTICLE;
  37.         AddCornerEvents();
  38.        
  39.         for (uint i = 0; i < AnimSets::_LAST; ++i)
  40.                 jjAnimSets[ANIM::CUSTOM[i]].load(i, "EmeraldusV.j2a");
  41.        
  42.         if (jjAnimSets[ANIM::BRIDGE] == 0)
  43.                 jjAnimSets[ANIM::BRIDGE].load();
  44.         jjAnimations[jjAnimSets[ANIM::BRIDGE] + 4] = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::BRIDGE]]];
  45.         jjObjectPresets[OBJECT::BRIDGE].behavior = MyBridge;
  46.        
  47.         Crabmeat(jjObjectPresets[OBJECT::BANANA]);
  48.         Motobug(jjObjectPresets[OBJECT::CHEESE]);
  49.         Ball(jjObjectPresets[OBJECT::BURGER]);
  50.         GreenNewtron(jjObjectPresets[OBJECT::CHICKENLEG]);
  51.         BlueNewtron(jjObjectPresets[OBJECT::CHIPS]);
  52.         Chopper(jjObjectPresets[OBJECT::EGGPLANT]);
  53.         BuzzBomber(jjObjectPresets[OBJECT::FRIES]);
  54.         Turtlette(jjObjectPresets[OBJECT::COKE]);
  55.        
  56.         jjOBJ@ tree = jjObjectPresets[OBJECT::CHERRY];
  57.         tree.behavior = Tree;
  58.         tree.playerHandling = HANDLING::PARTICLE;
  59.         tree.determineCurAnim(ANIM::CUSTOM[AnimSets::TREES], 0);
  60.         MakeTreeObjects();
  61.        
  62.         if (jjAnimSets[ANIM::PINKPLAT] == 0)
  63.                 jjAnimSets[ANIM::PINKPLAT].load();
  64.         for (int i = 0; i < 2; ++i)
  65.                 jjAnimations[jjAnimSets[ANIM::PINKPLAT] + i] = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::PLATFORM]] + i];
  66.         jjObjectPresets[OBJECT::PINKPLATFORM].behavior = PlatformWrapper;
  67.        
  68.         jjObjectPresets[OBJECT::GRASSPLATFORM].curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::PLATFORM]] + 3];
  69.         jjObjectPresets[OBJECT::GRASSPLATFORM].behavior = GrassPlatform;
  70.         jjObjectPresets[OBJECT::GRASSPLATFORM].playerHandling = HANDLING::PARTICLE;
  71.        
  72.         jjObjectPresets[OBJECT::WEENIE].behavior = Ropeway();
  73.         jjObjectPresets[OBJECT::WEENIE].scriptedCollisions = true;
  74.         jjObjectPresets[OBJECT::WEENIE].curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::PLATFORM]] + 4];
  75.        
  76.         jjObjectPresets[OBJECT::GREENGEM].behavior = BEHAVIOR::INACTIVE;
  77.         jjObjectPresets[OBJECT::BLUEGEM].behavior = BEHAVIOR::INACTIVE;
  78.        
  79.         jjObjectPresets[OBJECT::FROZENSPRING].freeze = 0;
  80.         jjObjectPresets[OBJECT::FROZENSPRING].ySpeed /= 3;
  81.         jjObjectPresets[OBJECT::FROZENSPRING].behavior = InvisibleSpring;
  82.        
  83.         Boss(jjObjectPresets[OBJECT::CAKE]);
  84. }
  85. void onDrawLayer8(jjPLAYER@ player, jjCANVAS@) {
  86.         if (player.localPlayerID != 0 && player.isLocal)
  87.                 return;
  88.         for (uint i = 0; i < Ripples.length; ++i)
  89.                 Ripples[i].xOffset += 0.03 * (i + 1);
  90.         Clouds.xOffset += 0.25;
  91. }
  92.  
  93. const float RightEdgeOfLevel = jjLayerWidth[4] * 32;
  94. class Enemy : jjBEHAVIORINTERFACE {
  95.         Enemy(jjOBJ@ preset, int animSpeed = 1) {
  96.                 preset.behavior = this;
  97.                 preset.playerHandling = HANDLING::ENEMY;
  98.                 preset.bulletHandling = HANDLING::HURTBYBULLET;
  99.                 preset.direction = 1;
  100.                 preset.animSpeed = animSpeed;
  101.                 preset.curFrame = jjAnimations[preset.curAnim] + preset.frameID;
  102.                 preset.isTarget = true;
  103.                 if (jjDifficulty >= 3)
  104.                         preset.energy += 1;
  105.         }
  106.         void onBehave(jjOBJ@ obj) {}
  107.         void onDraw(jjOBJ@ obj) {}
  108.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) { return true; }
  109.        
  110.         void spawnTurtlette(jjOBJ@ obj) const {
  111.                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_GEMSMSH1, 63, 5000);
  112.                 jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)].determineCurAnim(ANIM::AMMO, 2);
  113.                 jjAddObject(OBJECT::COKE, obj.xPos, obj.yPos, obj.creatorID, CREATOR::OBJECT);
  114.         }
  115. }
  116. class WalkingEnemy : Enemy {
  117.         int yDist;
  118.         int halfFrameWidth;
  119.         int walkingAnimationSpeed;
  120.         WalkingEnemy(jjOBJ@ preset, int animSpeed) {
  121.                 super(preset, animSpeed);
  122.                 walkingAnimationSpeed = animSpeed;
  123.                 const jjANIMFRAME@ frame = jjAnimFrames[preset.curFrame];
  124.                 halfFrameWidth = frame.width / 2;
  125.                 yDist = int(abs(frame.coldSpotY - frame.hotSpotY)) + 2;
  126.         }
  127.         void onBehave(jjOBJ@ obj) {
  128.                 switch (obj.state) {
  129.                         case STATE::START:
  130.                                 obj.putOnGround(true);
  131.                                 obj.state = STATE::WALK;
  132.                                 break;
  133.                         case STATE::KILL:
  134.                                 spawnTurtlette(obj);
  135.                                 obj.delete();
  136.                                 break;
  137.                         case STATE::DEACTIVATE:
  138.                                 obj.deactivate();
  139.                                 break;
  140.                         case STATE::FREEZE:
  141.                                 if (--obj.freeze == 0)
  142.                                         obj.state = obj.oldState;
  143.                                 break;
  144.                         default: {
  145.                                 obj.xAcc += obj.xSpeed;
  146.                                 while (obj.xAcc >= 1.f) {
  147.                                         const float testHPos = obj.xPos + obj.direction;
  148.                                         if (testHPos <= 0 || testHPos >= RightEdgeOfLevel)
  149.                                                 obj.direction = -obj.direction;
  150.                                         else {
  151.                                                 const int topYPos = jjMaskedTopVLine(int(testHPos), int(obj.yPos), yDist);
  152.                                                 //jjDrawRectangle(testHPos, obj.yPos, 3,3, 16, SPRITE::NORMAL,0,2);
  153.                                                 bool falling = false;
  154.                                                 if (topYPos <= 1 || ((falling = (topYPos >= yDist)) && obj.yAcc <= 0) || jjEventAtLastMaskedPixel == AREA::STOPENEMY) {
  155.                                                         obj.direction = -obj.direction;
  156.                                                 } else { //walk forwards
  157.                                                         obj.xPos = testHPos;
  158.                                                         if (falling)
  159.                                                                 obj.state = STATE::FALL;
  160.                                                         else
  161.                                                                 obj.yPos += topYPos - (yDist - 2);
  162.                                                 }
  163.                                         }
  164.                                         obj.xAcc -= 1;
  165.                                 }
  166.                                
  167.                                 if (obj.state == STATE::FALL) {
  168.                                         obj.yPos += obj.ySpeed += obj.yAcc;
  169.                                         while (jjMaskedTopVLine(int(obj.xPos), int(obj.yPos), yDist) < yDist) {
  170.                                                 obj.state = STATE::WALK;
  171.                                                 obj.yPos -= 1;
  172.                                         }
  173.                                         if (obj.state == STATE::WALK) {
  174.                                                 obj.yPos += 1;
  175.                                                 obj.ySpeed = 0;
  176.                                         }
  177.                                 }
  178.                                
  179.                                 obj.special = 0; //drawing angle
  180.                                 if (obj.state == STATE::WALK) {
  181.                                         const int yDist1 = jjMaskedTopVLine(int(obj.xPos - 9), int(obj.yPos), 50);
  182.                                         if (yDist1 != 1 && yDist1 != 51) {
  183.                                                 const int yDist2 = jjMaskedTopVLine(int(obj.xPos + 9), int(obj.yPos), 50);
  184.                                                 if (yDist2 != 1 && yDist2 != 51) {
  185.                                                         obj.special = int(atan2(yDist1 - yDist2, 19) / 6.28318531 * 1024);
  186.                                                 }
  187.                                         }
  188.                                 }
  189.                                
  190.                                 if (--obj.animSpeed < 0) {
  191.                                         obj.animSpeed = walkingAnimationSpeed;
  192.                                         obj.frameID += 1;
  193.                                         obj.determineCurFrame();
  194.                                 }
  195.                                
  196.                                 break; }
  197.                 }
  198.         }
  199.         void onDraw(jjOBJ@ obj) {
  200.                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.special, obj.direction,1, (obj.justHit == 0) ? (obj.state != STATE::FREEZE) ? SPRITE::NORMAL : SPRITE::FROZEN : SPRITE::SINGLECOLOR, 15);
  201.         }
  202. }
  203.  
  204. void AddEnemyBullet(const jjOBJ@ obj, float xSpeed, float ySpeed, float yAcc, bool sample = true) {
  205.         const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
  206.         jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos + obj.direction * (frame.hotSpotX - frame.gunSpotX), obj.yPos + frame.hotSpotY - frame.gunSpotY, obj.objectID, CREATOR::OBJECT, EnemyBullet)];
  207.         bullet.xSpeed = xSpeed;
  208.         bullet.ySpeed = ySpeed;
  209.         bullet.yAcc = yAcc;
  210.         bullet.curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::NEWTRON]] + 1];
  211.         bullet.animSpeed = 1;
  212.         bullet.playerHandling = HANDLING::ENEMYBULLET;
  213.         bullet.light = 1;
  214.         bullet.lightType = LIGHT::POINT2;
  215.         if (sample)
  216.                 jjSample(bullet.xPos, bullet.yPos, SOUND::COMMON_GLASS2, 63, 30000);
  217.         else
  218.                 bullet.counterEnd = 172;
  219. }
  220. void EnemyBullet(jjOBJ@ obj) {
  221.         if (obj.state == STATE::START)
  222.                 obj.state = STATE::FLY;
  223.         else if (obj.state == STATE::DEACTIVATE)
  224.                 obj.delete();
  225.         else if (obj.state == STATE::EXPLODE || ++obj.counterEnd >= 210) {
  226.                 obj.curAnim = jjAnimSets[ANIM::AMMO] + 72;
  227.                 obj.behavior = BEHAVIOR::EXPLOSION;
  228.                 obj.playerHandling = HANDLING::EXPLOSION;
  229.                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_EXPSM1);
  230.         } else {
  231.                 obj.xPos += obj.xSpeed;
  232.                 obj.yPos += obj.ySpeed += obj.yAcc;
  233.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos + jjSin(obj.counterEnd << 4) * 2, obj.curFrame + ((jjGameTicks >> 1) & 3), int(obj.xSpeed), SPRITE::NORMAL,0, 3);
  234.         }
  235. }
  236.  
  237. class Motobug : WalkingEnemy {
  238.         Motobug(jjOBJ@ preset) {
  239.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::MOTOBUG], 0);
  240.                 preset.energy = 3;
  241.                 preset.points = 200;
  242.                 preset.xSpeed = 1.25;
  243.                 super(preset, 9);
  244.         }
  245.         void onBehave(jjOBJ@ obj) override {
  246.                 if (obj.state == STATE::WALK) {
  247.                         if (jjGameTicks & 31 == 0)
  248.                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_LANDCAN1 + ((jjGameTicks >> 5) & 1)));
  249.                         if (obj.counter == 0) {
  250.                                 const auto nearestPlayerID = obj.findNearestPlayer(160*160);
  251.                                 if (nearestPlayerID >= 0) {
  252.                                         obj.counter = 1;
  253.                                         obj.direction = (jjPlayers[nearestPlayerID].xPos > obj.xPos) ? 1 : -1;
  254.                                         jjSample(obj.xPos, obj.yPos + yDist, SOUND::COMMON_REVUP);
  255.                                 }
  256.                         } else if (obj.counter < 70) {
  257.                                 obj.curFrame = jjAnimations[obj.curAnim] + (++obj.counter & 1);
  258.                                 jjPARTICLE@ part = jjAddParticle(PARTICLE::SPARK);
  259.                                 if (part !is null) {
  260.                                         part.xPos = obj.xPos;
  261.                                         part.yPos = obj.yPos + yDist;
  262.                                         part.xSpeed = obj.direction * (-1 - (jjRandom() & 3) / 2.f);
  263.                                         part.ySpeed = (jjRandom() & 31) / 32.f;
  264.                                 }
  265.                                 return;
  266.                         } else if (obj.counter == 70) {
  267.                                 obj.counter = 71;
  268.                                 obj.xSpeed = 10;
  269.                                 obj.yAcc = 0.2;
  270.                         } else {
  271.                                 if (obj.xSpeed > jjObjectPresets[obj.eventID].xSpeed)
  272.                                         obj.xSpeed -= 0.1;
  273.                                 else {
  274.                                         obj.counter = 0;
  275.                                         obj.yAcc = 0;
  276.                                 }
  277.                         }
  278.                 }
  279.                 WalkingEnemy::onBehave(obj);
  280.         }
  281. }
  282. class Crabmeat : WalkingEnemy {
  283.         Crabmeat(jjOBJ@ preset) {
  284.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::CRABMEAT], 0);
  285.                 preset.energy = 1;
  286.                 preset.points = 500;
  287.                 preset.xSpeed = 1;
  288.                 super(preset, 9);
  289.         }
  290.         void onBehave(jjOBJ@ obj) override {
  291.                 if (obj.state != STATE::WALK || ++obj.counter < 140) {
  292.                         WalkingEnemy::onBehave(obj);
  293.                         if (obj.state == STATE::WALK && jjGameTicks & 31 == 0)
  294.                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::UTERUS_SCISSORS1 + (jjRandom() & 3)));
  295.                 } else if (obj.counter == 140)
  296.                         obj.curFrame = jjAnimations[obj.curAnim + 1];
  297.                 else if (obj.counter == 155)
  298.                         for (int i = -1; i < 2; i += 2)
  299.                                 AddEnemyBullet(obj, i * 1.5, -6, 0.2);
  300.                 else if (obj.counter == 170) {
  301.                         obj.counter = 0;
  302.                         obj.animSpeed = 0; //start animating again
  303.                 }
  304.         }
  305. }
  306.  
  307. void Tree(jjOBJ@ obj) {
  308.         if (jjLowDetail && !obj.deactivates)
  309.                 return;
  310.         if (obj.state == STATE::START) {
  311.                 const uint xTile = uint(obj.xOrg) >> 5, yTile = uint(obj.yOrg) >> 5;
  312.                 obj.frameID = jjParameterGet(xTile,yTile, 0, 3);
  313.                 obj.curFrame = jjAnimations[obj.curAnim] + obj.frameID;
  314.                 obj.var[0] = int(jjParameterGet(xTile,yTile, 3, -5) * -8.53333333); //angle
  315.                 obj.var[1] = jjParameterGet(xTile,yTile, 8, -3) + 5; //layer... shouldn't be -4 in this level, but I guess I don't want to show a warning message?
  316.                 obj.deactivates = obj.var[1] == 5 || obj.var[1] == 6;
  317.                 obj.state = STATE::IDLE;
  318.         } else if (obj.state == STATE::DEACTIVATE) {
  319.                 obj.deactivate();
  320.         } else {
  321.                 const auto spriteMode = obj.var[1] <= 6 ? SPRITE::NORMAL : SPRITE::PALSHIFT;
  322.                 if (obj.frameID == 5) { //bendy tree
  323.                         if (!jjLowDetail) {
  324.                                 uint trunkPieceCount = 0;
  325.                                 float xPos = obj.xOrg, yPos = obj.yOrg;
  326.                                 uint curFrame = obj.curFrame;
  327.                                 float angle = obj.var[0];
  328.                                 const float angleChange = jjSin(jjGameTicks << 1) * 3;
  329.                                 while (true) {
  330.                                         if (trunkPieceCount == 15)
  331.                                                 curFrame += 1;
  332.                                         jjDrawRotatedSpriteFromCurFrame(xPos,yPos, curFrame, int(angle), 1,1, spriteMode,16, obj.var[1],obj.var[1]-1);
  333.                                         if (trunkPieceCount == 15)
  334.                                                 break;
  335.                                         ++trunkPieceCount;
  336.                                         angle += angleChange;
  337.                                         xPos -= jjSin(int(angle)) * 5;
  338.                                         yPos -= jjCos(int(angle)) * 5;
  339.                                 }
  340.                         }
  341.                 } else { //single sprite
  342.                         if (obj.frameID >= 3 && jjGameTicks % 35 == 0) //sunflower
  343.                                 obj.curFrame = jjAnimations[obj.curAnim] + (obj.frameID ^= 7); //animate... ^=7 switches between 3 and 4.
  344.                         jjDrawRotatedSpriteFromCurFrame(obj.xOrg, obj.yOrg, obj.curFrame, obj.var[0], 1,1, spriteMode,16, obj.var[1],obj.var[1]-1);
  345.                 }
  346.         }
  347. }
  348. void MakeTreeObjects() {
  349.         for (int x = jjLayerWidth[4]; --x >= 0;)
  350.                 for (int y = jjLayerWidth[4]; --y >= 0;)
  351.                         if (jjEventGet(x,y) == OBJECT::CHERRY) {
  352.                                 jjParameterSet(x,y, -1,1, 1);
  353.                                 jjAddObject(OBJECT::CHERRY, x*32 + 15, y*32 + 15, 0, CREATOR::LEVEL);
  354.                         }
  355. }
  356. void onLevelReload() {
  357.         MakeTreeObjects();
  358. }
  359.  
  360. enum BridgeVariables { PhysicalWidth, MaximumSagDistance, VisualWidth, FirstObjectIDOfPlatformForSplitscreenPlayers, Angle = FirstObjectIDOfPlatformForSplitscreenPlayers + 3 };
  361. void MyBridge(jjOBJ@ obj) {
  362. //first, check collision with bridge
  363.  
  364.         if (obj.state==STATE::START) {
  365.                 obj.state=STATE::STILL;
  366.  
  367.                 const uint xTile = uint(obj.xOrg) >> 5, yTile = uint(obj.yOrg) >> 5;
  368.                 obj.var[BridgeVariables::PhysicalWidth] = 32 * jjParameterGet(xTile,yTile, 0,4);
  369.                 obj.curAnim = jjAnimSets[ANIM::BRIDGE].firstAnim + (jjParameterGet(xTile,yTile, 4,3) % 7); //"Type" parameter... % 7 because there are only seven bridge types for some reason.
  370.  
  371.                 int toughness = jjParameterGet(xTile,yTile, 7,4);
  372.                 if (toughness == 0) toughness = 4; //default toughness of 4, to avoid dividing by zero
  373.                 obj.var[BridgeVariables::MaximumSagDistance] = obj.var[BridgeVariables::PhysicalWidth] / toughness;
  374.                
  375.                 int heightInTiles = jjParameterGet(xTile,yTile, 11,-5);
  376.                 obj.var[BridgeVariables::Angle] = int(atan2(heightInTiles, obj.var[BridgeVariables::PhysicalWidth] / 32) * 162.974662f);
  377.                 obj.xAcc = jjCos(obj.var[BridgeVariables::Angle]);
  378.                 obj.yAcc = jjSin(obj.var[BridgeVariables::Angle]);
  379.                
  380.                 { //determine how wide the bridge is, in drawn pixels (will always be >= how wide it is in mask pixels)
  381.                         int frameID = 0;
  382.                         int bridge_len = 0;
  383.                         const int numberOfFramesUsedByAnimation = jjAnimations[obj.curAnim].frameCount;
  384.                         const uint firstBridgeFrameID = jjAnimations[obj.curAnim].firstFrame;
  385.                         while (true) {
  386.                                 if ((bridge_len += jjAnimFrames[firstBridgeFrameID + frameID].width) >= obj.var[BridgeVariables::PhysicalWidth])
  387.                                         break;
  388.                                        
  389.                                 if (++frameID >= numberOfFramesUsedByAnimation)
  390.                                         frameID = 0;
  391.                         }
  392.                         obj.var[BridgeVariables::VisualWidth] = bridge_len;
  393.                 }
  394.  
  395.                 obj.xOrg -= 16; //start at left edge of tile, not center
  396.                 obj.yOrg -= 6; //worth noting that bridges are never deactivated, so we don't need to worry about where this gets moved to at all
  397.                
  398.                 for (int i = 1; i < jjLocalPlayerCount; ++i) { //this portion has no native JJ2 counterpart, because the API for platforms is still pretty limited
  399.                         const int platformObjectID = jjAddObject(OBJECT::BRIDGE, 0,0, obj.objectID,CREATOR::OBJECT, BEHAVIOR::BEES);
  400.                         jjOBJ@ platform = jjObjects[platformObjectID];
  401.                         platform.deactivates = false;
  402.                         platform.curFrame = jjAnimations[obj.curAnim].firstFrame;
  403.                         obj.var[BridgeVariables::FirstObjectIDOfPlatformForSplitscreenPlayers - 1 + i] = platformObjectID;
  404.                 }
  405.         }
  406.        
  407.         obj.clearPlatform();
  408.         for (int i = 1; i < jjLocalPlayerCount; ++i)
  409.                 jjObjects[obj.var[BridgeVariables::FirstObjectIDOfPlatformForSplitscreenPlayers - 1 + i]].clearPlatform();
  410.  
  411.         array<int> pressXPosition;
  412.         array<jjPLAYER@> pressPlayer;
  413.         for (int playerID = 0; playerID < 32; ++playerID) {
  414.                 jjPLAYER@ play = jjPlayers[playerID];
  415.                 if (play.isActive && jjObjects[play.platform].eventID != OBJECT::BURGER) { //all active players are valid, even if isLocal is false
  416.                         const int tx = int(play.xPos-obj.xOrg);
  417.                         const int ty = int(play.yPos-obj.yOrg - obj.yAcc / obj.xAcc * tx);
  418.  
  419.                         if ((tx >= 0) && (tx <= obj.var[BridgeVariables::PhysicalWidth]) && //player is within bridge area (horizontal)
  420.                                 (ty > -32) && (ty < obj.var[BridgeVariables::MaximumSagDistance]) && //(and vertical) //-32 was -24
  421.                                 (play.ySpeed > -1.f)) //not jumping, using a spring, etc.
  422.                         {
  423.                                 pressXPosition.insertLast(tx);
  424.                                 pressPlayer.insertLast(play);
  425.                         }
  426.                 }
  427.         }
  428.        
  429.         float max, amp, leftamp, rightamp;
  430.         int     leftmostPressedX, rightmostPressedX;
  431.  
  432.         if (pressPlayer.length != 0) {
  433.                 if (pressPlayer.length > 1) {
  434.                         leftmostPressedX=12312312;
  435.                         rightmostPressedX=0;
  436.                         uint t = 0;
  437.                         do {
  438.                                 const int pressedX = pressXPosition[t];
  439.                                 if (pressedX < leftmostPressedX)
  440.                                         leftmostPressedX = pressedX;
  441.                                 if (pressedX > rightmostPressedX)
  442.                                         rightmostPressedX = pressedX;
  443.                         } while (++t < pressPlayer.length);
  444.  
  445.                         leftamp =  obj.var[BridgeVariables::MaximumSagDistance]*jjSin((512* leftmostPressedX)/obj.var[BridgeVariables::PhysicalWidth]);
  446.                         rightamp = obj.var[BridgeVariables::MaximumSagDistance]*jjSin((512*rightmostPressedX)/obj.var[BridgeVariables::PhysicalWidth]);
  447.                 }
  448.                
  449.                 uint t = 0;
  450.                 uint numberOfLocalPlayersNeedingPlatforms = 0;
  451.                 do {
  452.                         const auto pressedPosition = pressXPosition[t];
  453.                         if (pressPlayer.length == 1)
  454.                                 max = obj.var[BridgeVariables::MaximumSagDistance] * jjSin((512 * pressedPosition) / obj.var[BridgeVariables::PhysicalWidth]); //same formula as side amps above, but for single player bridges
  455.                         else if ((pressedPosition>leftmostPressedX) && (pressedPosition<rightmostPressedX))
  456.                                 max = leftamp+(rightamp-leftamp)*(pressedPosition-leftmostPressedX)/(rightmostPressedX-leftmostPressedX);
  457.                         else
  458.                                 max = obj.var[BridgeVariables::MaximumSagDistance]*jjSin((512 * pressedPosition)/obj.var[BridgeVariables::PhysicalWidth]);
  459.  
  460.                         jjPLAYER@ play = pressPlayer[t];
  461.                         play.yPos = obj.yOrg + obj.yAcc / obj.xAcc * pressedPosition + max - 24;
  462.                         if (play.isLocal) {
  463.                                 jjOBJ@ platform = (numberOfLocalPlayersNeedingPlatforms == 0) ? obj : jjObjects[obj.var[BridgeVariables::FirstObjectIDOfPlatformForSplitscreenPlayers - 1 + numberOfLocalPlayersNeedingPlatforms]];
  464.                                 platform.bePlatform(
  465.                                         platform.xPos = play.xPos,
  466.                                         platform.yPos = play.yPos + 24
  467.                                 );
  468.                                 //platform.draw();
  469.                                 if (play.buttstomp < 120)
  470.                                         play.buttstomp = 120;
  471.                                 numberOfLocalPlayersNeedingPlatforms += 1;
  472.                         }
  473.                 } while (++t < pressPlayer.length);
  474.         }
  475.  
  476.         //draw
  477.         float bridge_len_x = 0, bridge_len_y = 0;
  478.         int frameID = 0;
  479.         const int numberOfFramesUsedByAnimation = jjAnimations[obj.curAnim].frameCount;
  480.         while (true) {
  481.                 obj.curFrame = jjAnimations[obj.curAnim].firstFrame + frameID;
  482.                 const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
  483.                
  484.                 float plankOffset = 0; //"straight bridge, or terugveren"
  485.                 if (pressPlayer.length == 1) {
  486.                         const auto pressedPosition = pressXPosition[0];
  487.                         plankOffset = ((bridge_len_x<pressedPosition) ?
  488.                                 (max*jjSin(int(256*bridge_len_x)/pressedPosition)) : //left
  489.                                 (max*jjCos(int(256*(bridge_len_x-pressedPosition))/(obj.var[BridgeVariables::VisualWidth]-pressedPosition) ))
  490.                         );
  491.                 } else if (pressPlayer.length > 1) {
  492.                         if (bridge_len_x < leftmostPressedX)
  493.                                 plankOffset = (leftamp*jjSin(int(256*bridge_len_x)/leftmostPressedX));
  494.                         else if (bridge_len_x > rightmostPressedX)
  495.                                 plankOffset = (rightamp*jjCos(int(256*(bridge_len_x-rightmostPressedX))/(obj.var[BridgeVariables::VisualWidth]-rightmostPressedX) ));
  496.                         else
  497.                                 plankOffset = leftamp+(rightamp-leftamp)*(bridge_len_x-leftmostPressedX)/(rightmostPressedX-leftmostPressedX);
  498.                 }
  499.                 jjDrawRotatedSpriteFromCurFrame(
  500.                         obj.xOrg + bridge_len_x - frame.hotSpotX,
  501.                         obj.yOrg + bridge_len_y + plankOffset,
  502.                         obj.curFrame,
  503.                         -obj.var[BridgeVariables::Angle]
  504.                 );
  505.  
  506.                 if (int(bridge_len_x += obj.xAcc * frame.width) >= obj.var[BridgeVariables::PhysicalWidth])
  507.                         break;
  508.                 bridge_len_y += obj.yAcc * frame.width;
  509.                        
  510.                 if (++frameID >= numberOfFramesUsedByAnimation)
  511.                         frameID = 0;
  512.         }
  513. }
  514.  
  515. /*bool onDrawScore(jjPLAYER@, jjCANVAS@ canvas) {
  516.         for (int x = jjResolutionWidth; --x >= 0;)
  517.                 for (int y = jjResolutionHeight; --y >= 0;)
  518.                         canvas.drawPixel(x,y, x^y);
  519.         return false;
  520. }*/
  521.  
  522. void PlatformWrapper(jjOBJ@ obj) {
  523.         obj.behave(BEHAVIOR::PLATFORM, true);
  524.         jjDrawSpriteFromCurFrame(obj.xOrg, obj.yOrg, obj.curFrame+2);
  525. }
  526. void GrassPlatform(jjOBJ@ obj) {
  527.         if (obj.state == STATE::DEACTIVATE)
  528.                 obj.deactivate();
  529.         else if (obj.state == STATE::START) {
  530.                 obj.state = STATE::FLY;
  531.                 obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5,uint(obj.yOrg) >> 5, 2,3);
  532.         } else {
  533.                 const auto lastY = obj.yPos;
  534.                 obj.yPos = obj.yOrg + jjSin(obj.age += 4) * 6;
  535.                 const int stage = obj.var[0] == 0 ? 256 : ((obj.var[0] * 256 + jjGameTicks * 8) & 2047);
  536.                 const uint8 opacity = stage < 256 ? stage : stage > 1280 ? 0 : stage > 1024 ? (1280 - stage) : 255;
  537.                 bool stoodOn = false;
  538.                 if (opacity < 100)
  539.                         obj.clearPlatform();
  540.                 else {
  541.                         obj.bePlatform(obj.xPos, lastY);
  542.                         for (int i = 0; i < jjLocalPlayerCount; ++i)
  543.                                 if (jjLocalPlayers[i].platform == obj.objectID) {
  544.                                         stoodOn = true;
  545.                                         break;
  546.                                 }
  547.                 }
  548.                 if (stoodOn) {
  549.                         if (obj.counterEnd < 10) obj.counterEnd += 2;
  550.                 } else {
  551.                         if (obj.counterEnd != 0) obj.counterEnd -= 1;
  552.                 }
  553.                 obj.yPos += obj.counterEnd;
  554.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame,1, SPRITE::BLEND_NORMAL, opacity);
  555.         }
  556. }
  557.  
  558. class Ball : WalkingEnemy {
  559.         float lastBallX, lastBallY; //make these globalish so they persist between onBehave and onDraw, meaning the ball can be drawn exactly underneath the player (while falling) instead of one frame ahead.
  560.         Ball(jjOBJ@ preset) {
  561.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::BALL], 0);
  562.                 preset.energy = 5;
  563.                 preset.points = 0;
  564.                 preset.xSpeed = 1;
  565.                 super(preset, 12);
  566.                 preset.playerHandling = HANDLING::SPECIAL;
  567.                 preset.scriptedCollisions = true;
  568.                 preset.bulletHandling = HANDLING::DETECTBULLET;
  569.                 preset.yAcc = 0.1;
  570.         }
  571.         void onBehave(jjOBJ@ obj) override {
  572.                 if (obj.state == STATE::KILL) {
  573.                         obj.clearPlatform();
  574.                         obj.delete(); //no turtle
  575.                 } else {
  576.                         lastBallX = obj.xPos;
  577.                         lastBallY = obj.yPos;
  578.                         WalkingEnemy::onBehave(obj);
  579.                         obj.bePlatform(lastBallX, lastBallY);
  580.                 }
  581.         }
  582.         void onDraw( jjOBJ@ obj) override {
  583.                 jjDrawSpriteFromCurFrame(lastBallX,lastBallY, obj.curFrame, 1, (obj.justHit == 0) ? (obj.state != STATE::FREEZE) ? SPRITE::NORMAL : SPRITE::FROZEN : SPRITE::SINGLECOLOR, 15); //don't rotate, don't mirror
  584.         }
  585.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) override {
  586.                 obj.scriptedCollisions = false;
  587.                 if (bullet !is null) {
  588.                         obj.bulletHandling = HANDLING::HURTBYBULLET;
  589.                         bullet.objectHit(obj, HANDLING::ENEMY);
  590.                         obj.bulletHandling = HANDLING::DETECTBULLET;
  591.                 } else {
  592.                         if (play.yPos > obj.yPos && ((play.xPos > obj.xPos) == (obj.direction == 1)))
  593.                                 play.objectHit(obj, force, HANDLING::ENEMY);
  594.                 }
  595.                 obj.scriptedCollisions = true;
  596.                 return true;
  597.         }
  598. }
  599.  
  600.  
  601. class Newtron : Enemy {
  602.         Newtron(jjOBJ@ preset) {
  603.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::NEWTRON], 0);
  604.                 preset.energy = 1;
  605.                 preset.points = 200;
  606.                 super(preset);
  607.                 preset.playerHandling = HANDLING::PARTICLE;
  608.         }
  609.         void onBehave(jjOBJ@ obj) {
  610.                 switch (obj.state) {
  611.                         case STATE::START:
  612.                                 obj.state = STATE::WAIT;
  613.                                 {
  614.                                         const uint xTile = uint(obj.xPos) >> 5, yTile = uint(obj.yPos) >> 5;
  615.                                         obj.var[0] = jjTileGet(4, xTile,yTile);
  616.                                         if (obj.var[0] == 0) obj.var[0] = jjTileGet(5, xTile,yTile);
  617.                                 }
  618.                                 break;
  619.                         case STATE::KILL:
  620.                                 spawnTurtlette(obj);
  621.                                 for (int i = 0; i < 7; ++i)
  622.                                         jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos)].determineCurAnim(ANIM::CUSTOM[AnimSets::NEWTRON], 3);
  623.                                 obj.delete();
  624.                                 break;
  625.                         case STATE::DEACTIVATE:
  626.                                 obj.deactivate();
  627.                                 break;
  628.                         case STATE::FREEZE:
  629.                                 if (--obj.freeze == 0)
  630.                                         obj.state = obj.oldState;
  631.                                 break;
  632.                         case STATE::WAIT: {
  633.                                 if (findNearbyPlayer(obj)) {
  634.                                         if (obj.counterEnd == 0) {
  635.                                                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_COLLAPS, 63, 10000);
  636.                                                 if (obj.var[0] != 0)
  637.                                                         jjAddParticleTileExplosion(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, obj.var[0], false);
  638.                                         }
  639.                                         if ((obj.counterEnd += 6) >= 200) {
  640.                                                 obj.playerHandling = HANDLING::ENEMY;
  641.                                                 obj.counterEnd = 252;
  642.                                                 obj.state = STATE::ACTION;
  643.                                         }
  644.                                 } else if (obj.counterEnd > 0)
  645.                                         obj.counterEnd -= 6;
  646.                         }
  647.                         break;
  648.                         case STATE::ACTION:
  649.                                 action(obj);
  650.                                 break;
  651.                 }
  652.         }
  653.         bool findNearbyPlayer(jjOBJ@ obj) {
  654.                 const auto nearestPlayerID = obj.findNearestPlayer(160*160);
  655.                 if (nearestPlayerID >= 0) {
  656.                         obj.direction = (jjPlayers[nearestPlayerID].xPos > obj.xPos) ? 1 : -1;
  657.                         return true;
  658.                 }
  659.                 return false;
  660.         }
  661.         void action(jjOBJ@ obj) {
  662.         }
  663.         void onDraw(jjOBJ@ obj) {
  664.                 int curFrame = obj.curFrame;
  665.                 if (obj.frameID == 0)
  666.                         curFrame += (jjGameTicks >> 1) & 7;
  667.                 if (obj.playerHandling == HANDLING::PARTICLE)
  668.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, curFrame, obj.direction, SPRITE::BLEND_NORMAL, obj.counterEnd);
  669.                 else
  670.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, curFrame, obj.direction, (obj.justHit == 0) ? (obj.state != STATE::FREEZE) ? SPRITE::NORMAL : SPRITE::FROZEN : SPRITE::SINGLECOLOR, 15);
  671.         }
  672. }
  673. class GreenNewtron : Newtron {
  674.         GreenNewtron(jjOBJ@ preset) {
  675.                 super(preset);
  676.         }
  677.         void action(jjOBJ@ obj) override {
  678.                 if (obj.counterEnd-- == 252)
  679.                         AddEnemyBullet(obj, obj.direction * (2 + (jjRandom() & 7) / 10.0), 0,0);
  680.                 else if (obj.counterEnd == 204) {
  681.                         if (findNearbyPlayer(obj))
  682.                                 obj.counterEnd = 252;
  683.                         else {
  684.                                 obj.state = STATE::WAIT;
  685.                                 obj.playerHandling = HANDLING::PARTICLE;
  686.                         }
  687.                 }
  688.         }
  689. }
  690.  
  691. class BlueNewtron : Newtron {
  692.         BlueNewtron(jjOBJ@ preset) {
  693.                 super(preset);
  694.                 preset.curFrame += 20;
  695.                 preset.curAnim += 2;
  696.                 preset.frameID = 8;
  697.         }
  698.         void action(jjOBJ@ obj) override {
  699.                 if (obj.counterEnd == 252) {
  700.                         obj.frameID = 0;
  701.                         obj.curFrame -= 8;
  702.                         obj.counterEnd = 251;
  703.                 } else if (obj.counterEnd == 251) {
  704.                         if (jjMaskedPixel(int(obj.xPos), int(obj.yPos) + 8)) {
  705.                                 obj.counterEnd = 0;
  706.                                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_SWISH1);
  707.                         } else
  708.                                 obj.yPos += 4;
  709.                 } else {
  710.                         obj.xPos += obj.direction * 3;
  711.                 }
  712.         }
  713. }
  714.  
  715. class Chopper : Enemy {
  716.         Chopper(jjOBJ@ preset) {
  717.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::CHOPPER], 0);
  718.                 preset.energy = 2;
  719.                 preset.points = 500;
  720.                 super(preset);
  721.         }
  722.         void onBehave(jjOBJ@ obj) {
  723.                 switch (obj.state) {
  724.                         case STATE::START:
  725.                                 obj.state = STATE::BOUNCE;
  726.                                 obj.var[0] = jjTileGet(4, uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5);
  727.                                 break;
  728.                         case STATE::KILL:
  729.                                 spawnTurtlette(obj);
  730.                                 obj.delete();
  731.                                 break;
  732.                         case STATE::DEACTIVATE:
  733.                                 obj.deactivate();
  734.                                 break;
  735.                         case STATE::FREEZE:
  736.                                 if (--obj.freeze == 0)
  737.                                         obj.state = obj.oldState;
  738.                                 break;
  739.                         case STATE::BOUNCE:
  740.                                 obj.curFrame = jjAnimations[obj.curAnim] + ((++obj.age >> 3) & 3);
  741.                                 if (obj.special == 0) {
  742.                                         for (int i = 0; i < jjLocalPlayerCount; ++i) {
  743.                                                 if (jjLocalPlayers[i].yPos < obj.yPos) {
  744.                                                         const auto xDelta = obj.xPos - jjLocalPlayers[i].xPos;
  745.                                                         if (abs(xDelta) < 128) {
  746.                                                                 obj.direction = xDelta > 0 ? 1 : -1;
  747.                                                                 if (jjTileGet(4, uint(obj.xPos - 32 * obj.direction) >> 5, uint(obj.yOrg) >> 5) != uint(obj.var[0]))
  748.                                                                         obj.direction = -obj.direction;
  749.                                                                 obj.special = 4;
  750.                                                                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_WATER);
  751.                                                         }
  752.                                                 }
  753.                                         }
  754.                                 } else if ((obj.special += 4) == 512) {
  755.                                         obj.special = 0;
  756.                                 } else {
  757.                                         obj.yPos = obj.yOrg - jjSin(obj.special) * 192;
  758.                                         obj.xPos -= obj.direction / 4.f;
  759.                                 }
  760.                                 break;
  761.                 }
  762.         }
  763.         void onDraw(jjOBJ@ obj) {
  764.                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.special * obj.direction, obj.direction,1, (obj.justHit == 0) ? (obj.state != STATE::FREEZE) ? SPRITE::NORMAL : SPRITE::FROZEN : SPRITE::SINGLECOLOR, 15);
  765.         }
  766. }
  767.  
  768. class BuzzBomber : Enemy {
  769.         BuzzBomber(jjOBJ@ preset) {
  770.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::BUZZBOMBER], 0);
  771.                 preset.energy = 1;
  772.                 preset.points = 1000;
  773.                 preset.state = STATE::IDLE; //no particular setup needed
  774.                 super(preset);
  775.         }
  776.         void onBehave(jjOBJ@ obj) {
  777.                 obj.age += 4;
  778.                 switch (obj.state) {
  779.                         case STATE::KILL:
  780.                                 spawnTurtlette(obj);
  781.                                 obj.delete();
  782.                                 break;
  783.                         case STATE::DEACTIVATE:
  784.                                 obj.deactivate();
  785.                                 break;
  786.                         case STATE::FREEZE:
  787.                                 if (--obj.freeze == 0)
  788.                                         obj.state = obj.oldState;
  789.                                 break;
  790.                         case STATE::IDLE:
  791.                                 obj.var[0] = obj.findNearestPlayer(200*200);
  792.                                 if (obj.var[0] >= 0) {
  793.                                         obj.state = STATE::FLY;
  794.                                         obj.xSpeed = 0.75 * obj.direction; //some deaccel maybe
  795.                                 } else {
  796.                                         obj.yPos = obj.yOrg + jjSin(obj.age) * 8;
  797.                                         if (jjMaskedVLine(int(obj.xPos + obj.direction * 12), int(obj.yPos) - 8, 16))
  798.                                                 obj.direction = -obj.direction;
  799.                                         else
  800.                                                 obj.xPos += 0.75 * obj.direction;
  801.                                         break;
  802.                                 }
  803.                         case STATE::FLY: {
  804.                                 const jjPLAYER@ target = jjPlayers[obj.var[0]]; //once a target has been found, never abandon it (short of deactivation), even if there are multiple local players
  805.                                 obj.direction = (target.xPos > obj.xPos) ? 1 : -1;
  806.                                 const int distance = 150 + int(jjSin(obj.age) * 25);
  807.                                 const float targetX = target.xPos - distance * obj.direction, targetY = target.yPos - distance;
  808.                                 if (obj.xPos < targetX) {
  809.                                         if (obj.xSpeed < 0.8) obj.xSpeed += 0.075;
  810.                                 } else {
  811.                                         if (obj.xSpeed > -0.8) obj.xSpeed -= 0.075;
  812.                                 }
  813.                                 if (obj.yPos < targetY) {
  814.                                         if (obj.ySpeed < 0.8) obj.ySpeed += 0.075;
  815.                                 } else {
  816.                                         if (obj.ySpeed > -0.8) obj.ySpeed -= 0.075;
  817.                                 }
  818.                                 obj.ySpeed += (int(jjRandom() & 63) - 31) / 310.f;
  819.                                 obj.xPos += obj.xSpeed;
  820.                                 obj.yPos += obj.ySpeed;
  821.                                 if (++obj.counter > 70 && abs(abs(target.xPos - obj.xPos) - (target.yPos - obj.yPos)) < 40) { //a poor man's atan
  822.                                         obj.curFrame = jjAnimations[obj.curAnim] + 2;
  823.                                         obj.state = STATE::ATTACK;
  824.                                         obj.counter = 40;
  825.                                 }
  826.                                 break; }
  827.                         case STATE::ATTACK:
  828.                                 if (--obj.counter == 15)
  829.                                         AddEnemyBullet(obj, obj.direction * 2, 2, 0);
  830.                                 else if (obj.counter == 0)
  831.                                         obj.state = STATE::FLY;
  832.                 }
  833.                 if (obj.state != STATE::ATTACK && obj.state != STATE::FREEZE) {
  834.                         obj.curFrame = jjAnimations[obj.curAnim] + ((obj.age >> 4) & 1);
  835.                         obj.counterEnd = jjSampleLooped(obj.xPos, obj.yPos, SOUND::DRAGFLY_BEELOOP, obj.counterEnd);
  836.                 }
  837.         }
  838.         void onDraw(jjOBJ@ obj) {
  839.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, (obj.state != STATE::FREEZE) ? (obj.justHit == 0) ? SPRITE::NORMAL : SPRITE::SINGLECOLOR : SPRITE::FROZEN,15, 3);
  840.         }
  841. }
  842.  
  843. enum PlayerState { Normal, Loop, Ropeway };
  844. enum LoopAngle { Floor, WallOnLeft, Ceiling, WallOnRight };
  845. class PlayerX {
  846.         PlayerX(){}
  847.         PlayerState State = PlayerState::Normal;
  848.         LoopAngle CurrentAngle = LoopAngle::Floor, NextAngle;
  849.         float CornerXOrg, CornerYOrg, CornerSpeed, CornerElapsed = -1,/* CornerLength,*/ LastX, LastY;
  850.         //int LastLoopEventParameter = -1;
  851.         int LastTile;
  852.         int CornerAngleChange, CornerRepositioningMultiplier, CornerAngleMultiplier;
  853.         int LastHealth = -1;
  854.         int LastGems = 0;
  855. }
  856. array<PlayerX> PlayerXs(jjLocalPlayerCount);
  857. class CornerEventResult {
  858.         LoopAngle Angle;
  859.         int XOrg, YOrg;
  860.         CornerEventResult(LoopAngle a, int x, int y) { Angle = a; XOrg = x; YOrg = y; }
  861. }
  862. const array<const CornerEventResult@> NextLoopAngles = {
  863.         null, CornerEventResult(WallOnRight, 0, -1), //floor, turn left, loop
  864.         null, CornerEventResult(Floor, 2, 0), //wallonleft, turn left, loop
  865.         null, CornerEventResult(WallOnLeft, 1, 2), //ceiling, turn left, loop
  866.         null, CornerEventResult(Ceiling, -1, 1), //wallonright, turn left, loop
  867.  
  868.         CornerEventResult(WallOnLeft, 1, -1), null, //floor, turn right, loop
  869.         CornerEventResult(Ceiling, 2, 1), null, //wallonleft, turn right, loop
  870.         CornerEventResult(WallOnRight, 0, 2), null, //ceiling, turn right, loop
  871.         CornerEventResult(Floor, -1, 0), null, //wallonright, turn right, loop
  872.  
  873.         CornerEventResult(WallOnRight, 1, 4), null, //floor, turn left, corner
  874.         CornerEventResult(Floor, -3, 1), null, //wallonleft, turn left, corner
  875.         CornerEventResult(WallOnLeft, 0, -3), null, //ceiling, turn left, corner
  876.         CornerEventResult(Ceiling, 4, 0), null, //wallonright, turn left, corner
  877.  
  878.         null, CornerEventResult(WallOnLeft, 0, 4), //floor, turn right, corner
  879.         null, CornerEventResult(Ceiling, -3, 0), //wallonleft, turn right, corner
  880.         null, CornerEventResult(WallOnRight, 1, -3), //ceiling, turn right, corner
  881.         null, CornerEventResult(Floor, 4, 1) //wallonright, turn right, corner
  882. };
  883. void onPlayer(jjPLAYER@ play) {
  884.         PlayerX@ playX = PlayerXs[play.localPlayerID];
  885.         if (playX.LastHealth != play.health) {
  886.                 if (playX.LastHealth > play.health) { //injured
  887.                         playX.State = PlayerState::Normal;
  888.                         playX.CurrentAngle = LoopAngle::Floor;
  889.                         playX.LastTile = -1;
  890.                         play.invisibility = false;
  891.                         play.cameraUnfreeze();
  892.                         const int maxGemsLost = 25 - (jjDifficulty * 5); //25, 20, 15, 10
  893.                         if (play.gems[GEM::RED] == 0) {
  894.                                 if (play.health > 0) {
  895.                                         play.health = 0;
  896.                                         play.lives -= 1;
  897.                                 }
  898.                         } else for (int i = 0; i < maxGemsLost && i < play.gems[GEM::RED]; ++i) {
  899.                                 jjOBJ@ gem = jjObjects[jjAddObject(OBJECT::FLICKERGEM, play.xPos, play.yPos, play.playerID, CREATOR::PLAYER)];
  900.                                 uint rand = jjRandom();
  901.                                 gem.xSpeed = (int(rand & 15) - 7);
  902.                                 gem.direction = int((rand >>= 4) & 1) * 2 - 1;
  903.                                 gem.ySpeed = 0.025 - int((rand >> 1) & 7);
  904.                                 gem.playerHandling = HANDLING::DELAYEDPICKUP;
  905.                                 gem.var[2] = 100; //time spent unpickupable
  906.                                 gem.counter /= 3; //time till death
  907.                         }
  908.                         play.gems[GEM::RED] = 0;
  909.                 }
  910.                 if (playX.LastHealth <= 0) //respawning at checkpoint
  911.                         play.gems[GEM::RED] = 0;
  912.                 playX.LastHealth = play.health;
  913.                 return;
  914.         } else if (playX.LastGems / 100 < play.gems[GEM::RED] / 100) {
  915.                 play.lives += 1;
  916.                 jjSamplePriority(SOUND::COMMON_HARP1);
  917.         }
  918.         playX.LastGems = play.gems[GEM::RED];
  919.        
  920.         if (playX.State == PlayerState::Ropeway) {
  921.                 play.keyLeft = play.keyRight = play.keyRun = play.keyDown = false;
  922.                 play.xSpeed = play.ySpeed = 0;
  923.                 //play.invincibility = -2;
  924.                 return;
  925.         }
  926.        
  927.         const uint xTile = uint(play.xPos) >> 5, yTile = uint(play.yPos) >> 5;
  928.         const uint8 currEvent = jjEventGet(xTile,yTile);
  929.         if (currEvent == CornerEventID) {
  930.                 const int loopEventParameter = jjParameterGet(xTile, yTile, 0, 4);
  931.                 if ((loopEventParameter >> 2) == playX.CurrentAngle) {
  932.                         const auto curAnim = play.curAnim - jjAnimSets[play.setID];
  933.                         if (play.currTile != playX.LastTile && (playX.State == PlayerState::Loop || (curAnim >= RABBIT::RUN1 && curAnim <= RABBIT::RUN3))) { //running
  934.                                 //if (loopEventParameter != playX.LastLoopEventParameter) {
  935.                                         const int circumstance = ((loopEventParameter & 3) << 3) | (playX.CurrentAngle << 1) | (play.direction == 1 ? 1 : 0);
  936.                                         const CornerEventResult@ result = NextLoopAngles[circumstance];
  937.                                         if (result !is null) {
  938.                                                 playX.LastTile = play.currTile;
  939.                                                 //playX.LastLoopEventParameter = loopEventParameter;
  940.                                                 //jjAlert(result.Angle + "," + result.XOrg + "," + result.YOrg);
  941.                                                 playX.NextAngle = result.Angle;
  942.                                                 playX.CornerXOrg = (int(xTile) + result.XOrg) * 32;
  943.                                                 playX.CornerYOrg = (int(yTile) + result.YOrg) * 32;
  944.                                                 const bool isLoop = loopEventParameter & 2 == 0;
  945.                                                 //playX.CornerLength = isLoop ? 256 : 256; //todo
  946.                                                 playX.CornerElapsed = 0; //todo?
  947.                                                 playX.CornerRepositioningMultiplier = isLoop ? 44 : 120; //40:120
  948.                                                 playX.CornerAngleMultiplier = isLoop ? 1 : -1;
  949.                                                 playX.CornerSpeed = isLoop ? 30 : 18;
  950.                                                 playX.CornerAngleChange = int(playX.NextAngle - playX.CurrentAngle);
  951.                                                 if (abs(playX.CornerAngleChange) == 3)
  952.                                                         playX.CornerAngleChange /= -3;
  953.                                                 playX.State = PlayerState::Loop;
  954.                                                 play.invisibility = true;
  955.                                                 //play.noclipMode = true;
  956.                                                 play.xSpeed = play.ySpeed = 0;
  957.                                                 if (jjParameterGet(xTile, yTile, -2, 1) == 1)
  958.                                                         play.cameraFreeze(play.cameraX, play.cameraY, false, true);
  959.                                         }
  960.                                 //}
  961.                         }
  962.                 }
  963.         } else if (currEvent == AREA::FLYOFF) { //this feels like the sensible choice
  964.                 if (playX.State == PlayerState::Loop)
  965.                         play.keyJump = true;
  966.         } else
  967.                 playX.LastTile = -1;
  968.        
  969.         int angle = playX.CurrentAngle << 8;
  970.         const int speedDivider = play.keyRun ? 1 : 4;
  971.         if (playX.CornerElapsed >= 0) {
  972.                 playX.CornerElapsed += playX.CornerSpeed / speedDivider;
  973.                 if (playX.CornerElapsed > 256)
  974.                         playX.CornerElapsed = 256;
  975.                 angle += int(playX.CornerElapsed * playX.CornerAngleChange);
  976.                 play.xPos = playX.LastX = playX.CornerXOrg + jjSin(angle) * playX.CornerRepositioningMultiplier * -playX.CornerAngleMultiplier;
  977.                 play.yPos = playX.LastY = playX.CornerYOrg - jjCos(angle) * playX.CornerRepositioningMultiplier * -playX.CornerAngleMultiplier;
  978.                 if (playX.CornerElapsed == 256) {
  979.                         playX.CornerElapsed = -1;
  980.                         playX.CurrentAngle = playX.NextAngle;
  981.                 }
  982.         } else if (playX.State == PlayerState::Loop) {
  983.                 const int speedMultiplier = 16 / speedDivider * play.direction;
  984.                 play.xPos = playX.LastX += jjCos(angle) * speedMultiplier;
  985.                 play.yPos = playX.LastY += jjSin(angle) * speedMultiplier;
  986.         }
  987.        
  988.         if (playX.State == PlayerState::Loop) {
  989.                 if (playX.CornerAngleMultiplier == 1 && playX.CornerElapsed >= 0) //quarter pipe
  990.                         play.keyJump = false;
  991.                 if ((playX.CornerElapsed < 0 && playX.CurrentAngle == LoopAngle::Floor) || play.keyJump || (play.direction == 1 && !play.keyRight) || (play.direction == -1 && !play.keyLeft)) {
  992.                         playX.State = PlayerState::Normal;
  993.                         playX.CurrentAngle = LoopAngle::Floor;
  994.                         playX.CornerElapsed = -1;
  995.                         play.invisibility = false;
  996.                         //play.noclipMode = false;
  997.                         play.xSpeed = play.direction * jjCos(angle) * 16 / speedDivider;
  998.                         play.ySpeed = play.direction * jjSin(angle) * 16 / speedDivider;
  999.                         play.cameraUnfreeze();
  1000.                 } else {
  1001.                         play.keyLeft = play.keyRight = play.keyFire = false;
  1002.                         play.xSpeed = play.ySpeed = 0;
  1003.                         jjDrawRotatedSprite(play.xPos, play.yPos, play.setID, play.keyRun ? RABBIT::RUN3 : RABBIT::RUN1, jjGameTicks >> 2, -angle, play.direction,1, SPRITE::PLAYER, play.playerID);
  1004.                 }
  1005.         }
  1006. }
  1007.  
  1008. void LoopObject(jjOBJ@ obj) {
  1009.         if (obj.state == STATE::START) {
  1010.                 obj.state = STATE::ROTATE;
  1011.                 obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5,uint(obj.yOrg) >> 5, 0,1);
  1012.                 obj.var[1] = jjParameterGet(uint(obj.xOrg) >> 5,uint(obj.yOrg) >> 5, 1,4);
  1013.                 obj.doesHurt = 0; //starts at 0, so (LoopsUnmaskedOnLeft != leftSideUnmasked) below will always be true the first time
  1014.         } if (obj.state == STATE::DEACTIVATE)
  1015.                 obj.deactivate();
  1016.         else {
  1017.                 const auto nearestPlayerID = obj.findNearestPlayer(960*960);
  1018.                 if (nearestPlayerID >= 0) {
  1019.                         const jjPLAYER@ play = jjPlayers[nearestPlayerID];
  1020.                         const uint spacing = obj.var[1];
  1021.                         uint8 leftSideUnmasked;
  1022.                         if (play.xPos < obj.xOrg - 32)
  1023.                                 leftSideUnmasked = 2;
  1024.                         else if (play.xPos > obj.xOrg + spacing*32 + 128)
  1025.                                 leftSideUnmasked = 1;
  1026.                         else if (PlayerXs[play.localPlayerID].CurrentAngle == LoopAngle::Ceiling)
  1027.                                 leftSideUnmasked = play.direction == -1 ? 2 : 1;
  1028.                         else
  1029.                                 return;
  1030.                         if (obj.doesHurt != leftSideUnmasked) {
  1031.                                 obj.doesHurt = leftSideUnmasked;
  1032.                                 const uint xTile = uint(obj.xOrg) >> 5, yTile = uint(obj.yOrg) >> 5;
  1033.                                 const bool grassOnRight = obj.var[0] == 1;
  1034.                                 if (leftSideUnmasked == 2) {
  1035.                                         jjTileSet(4, xTile + 0, yTile, 0);
  1036.                                         jjTileSet(4, xTile + 1, yTile, 0);
  1037.                                         jjTileSet(4, xTile + spacing + 2, yTile, grassOnRight ? 323 : 306);
  1038.                                         jjTileSet(4, xTile + spacing + 3, yTile, grassOnRight ? 324 : 307);
  1039.                                         jjTileSet(4, xTile + 0, yTile - 1, 0);
  1040.                                         jjTileSet(4, xTile + spacing + 3, yTile - 1, 297);
  1041.                                         jjEventSet(xTile + 1, yTile, 0);
  1042.                                         jjEventSet(xTile + spacing + 2, yTile, CornerEventID);
  1043.                                 } else {
  1044.                                         jjTileSet(4, xTile + 0, yTile, grassOnRight ? 304 : 325);
  1045.                                         jjTileSet(4, xTile + 1, yTile, grassOnRight ? 305 : 326);
  1046.                                         jjTileSet(4, xTile + spacing + 2, yTile, 0);
  1047.                                         jjTileSet(4, xTile + spacing + 3, yTile, 0);
  1048.                                         jjTileSet(4, xTile + 0, yTile - 1, 294);
  1049.                                         jjTileSet(4, xTile + spacing + 3, yTile - 1, 0);
  1050.                                         jjEventSet(xTile + 1, yTile, CornerEventID);
  1051.                                         jjEventSet(xTile + spacing + 2, yTile, 0);
  1052.                                 }
  1053.                         }
  1054.                 }
  1055.         }
  1056. }
  1057.  
  1058. /*void onMain() {
  1059.         for (int xTile = jjLayerWidth[4]; --xTile >= 0;)
  1060.                 for (int yTile = jjLayerHeight[4]; --yTile >= 0;)
  1061.                         if (jjEventGet(xTile,yTile) == CornerEventID)
  1062.                                 jjDrawString(xTile*32 + 10, yTile*32 + 10, "" + jjParameterGet(xTile,yTile, 0,4));
  1063. }*/
  1064.  
  1065. void AddCornerEvents() {
  1066.         for (int xTile = jjLayerWidth[4]; --xTile >= 0;)
  1067.                 for (int yTile = jjLayerHeight[4]; --yTile >= 0;) {
  1068.                         LoopAngle inputAngle = LoopAngle::Floor;
  1069.                         bool turnRight = false;
  1070.                         bool isLoop = false;
  1071.                         int xOffset = 0, yOffset = 0;
  1072.                         switch (jjTileGet(4, xTile, yTile)) {
  1073.                                 case 262:
  1074.                                 case 328:
  1075.                                 case 354:
  1076.                                         yOffset = -1;
  1077.                                         break;
  1078.                                 case 280:
  1079.                                 case 372:
  1080.                                         xOffset = -1;
  1081.                                         turnRight = true;
  1082.                                         inputAngle = LoopAngle::WallOnRight;
  1083.                                         break;
  1084.                                 case 272:
  1085.                                         xOffset = 1;
  1086.                                         turnRight = true;
  1087.                                         inputAngle = LoopAngle::WallOnLeft;
  1088.                                         break;
  1089.                                 case 290:
  1090.                                         yOffset = 1;
  1091.                                         inputAngle = LoopAngle::Ceiling;
  1092.                                         break;
  1093.                                 case 300:
  1094.                                 case 337:
  1095.                                 case 355:
  1096.                                         yOffset = -1;
  1097.                                         turnRight = true;
  1098.                                         break;
  1099.                                 case 322:
  1100.                                 case 377:
  1101.                                         xOffset = 1;
  1102.                                         inputAngle = LoopAngle::WallOnLeft;
  1103.                                         break;
  1104.                                 case 310:
  1105.                                         xOffset = -1;
  1106.                                         inputAngle = LoopAngle::WallOnRight;
  1107.                                         break;
  1108.                                 case 332:
  1109.                                         yOffset = 1;
  1110.                                         turnRight = true;
  1111.                                         inputAngle = LoopAngle::Ceiling;
  1112.                                         break;
  1113.                                 case 276:
  1114.                                         isLoop = true;
  1115.                                         turnRight = true;
  1116.                                         inputAngle = LoopAngle::Ceiling;
  1117.                                         break;
  1118.                                 case 287:
  1119.                                         isLoop = true;
  1120.                                         inputAngle = LoopAngle::WallOnRight;
  1121.                                         break;
  1122.                                 case 297:
  1123.                                 case 351:
  1124.                                         isLoop = true;
  1125.                                         turnRight = true;
  1126.                                         inputAngle = LoopAngle::WallOnRight;
  1127.                                         break;
  1128.                                 case 306:
  1129.                                 case 323:
  1130.                                 case 360:
  1131.                                         isLoop = true;
  1132.                                         break;
  1133.                                 case 305:
  1134.                                 case 326:
  1135.                                 case 369:
  1136.                                         isLoop = true;
  1137.                                         turnRight = true;
  1138.                                         break;
  1139.                                 case 294:
  1140.                                 case 358:
  1141.                                         isLoop = true;
  1142.                                         inputAngle = LoopAngle::WallOnLeft;
  1143.                                         break;
  1144.                                 case 284:
  1145.                                         isLoop = true;
  1146.                                         turnRight = true;
  1147.                                         inputAngle = LoopAngle::WallOnLeft;
  1148.                                         break;
  1149.                                 case 275:
  1150.                                         isLoop = true;
  1151.                                         inputAngle = LoopAngle::Ceiling;
  1152.                                         break;
  1153.                                 default:
  1154.                                         continue;
  1155.                         }
  1156.                         if (jjEventGet(xTile + xOffset, yTile + yOffset) == 0) {
  1157.                                 jjEventSet(xTile + xOffset, yTile + yOffset, CornerEventID);
  1158.                                 jjParameterSet(xTile + xOffset, yTile + yOffset, 0,4, (turnRight ? 1 : 0) | (isLoop ? 0 : 2) | (inputAngle << 2));
  1159.                         }
  1160.                 }
  1161. }
  1162.  
  1163. class Ropeway : jjBEHAVIORINTERFACE {
  1164.         void onBehave(jjOBJ@ obj) {
  1165.                 jjPLAYER@ play = jjPlayers[obj.var[0]];
  1166.                 bool isRealPlayer = obj.var[0] >= 0 && PlayerXs[play.localPlayerID].State == PlayerState::Ropeway;
  1167.                 if (obj.state == STATE::START) {
  1168.                         obj.state = STATE::WAIT;
  1169.                         obj.var[0] = -1; //no player yet
  1170.                         const uint xTile = uint(obj.xOrg) >> 5, yTile = uint(obj.yOrg) >> 5;
  1171.                         obj.direction = jjParameterGet(xTile,yTile, 0,1) * 2 - 1;
  1172.                         obj.special = jjParameterGet(xTile,yTile, 1,1); //rotates
  1173.                 } else if (obj.state == STATE::DEACTIVATE) {
  1174.                         reset(obj);
  1175.                 } else if (obj.state == STATE::ROTATE) {
  1176.                         obj.special += int(obj.xAcc * 3 * obj.direction);
  1177.                         const float sin = jjSin(obj.special), cos = jjCos(obj.special);
  1178.                         jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos - 24, obj.curFrame, obj.special);
  1179.                         if (isRealPlayer) {
  1180.                                 jjDrawRotatedSprite(play.xPos = (obj.xPos + sin * 30), play.yPos = (obj.yPos + cos * 30 - 24), play.setID, RABBIT::HANGIDLE2, jjGameTicks>>3, obj.special, play.direction = obj.direction,1, SPRITE::PLAYER, obj.var[0]);
  1181.                                 if (play.keyJump) {
  1182.                                         release(obj);
  1183.                                         obj.counter = 200;
  1184.                                 }
  1185.                                 obj.counterEnd = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_FOEW5, obj.counterEnd, int(abs(cos) * 46) + 15);
  1186.                         } else
  1187.                                 release(obj);
  1188.                 } else {
  1189.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos - 24, obj.curFrame);
  1190.                         if (obj.state == STATE::ACTION) {
  1191.                                 if (isRealPlayer) {
  1192.                                         jjDrawSprite(play.xPos = obj.xPos, (play.yPos = obj.yPos) + 6, play.setID, play.keyFire ? RABBIT::HANGINGFIRERIGHT : RABBIT::HANGIDLE2, jjGameTicks>>3, play.direction = obj.direction, SPRITE::PLAYER, obj.var[0]);
  1193.                                         if (play.keyJump && obj.xAcc > 3.f) {
  1194.                                                 release(obj);
  1195.                                                 play.ySpeed = -5;
  1196.                                                 obj.state = STATE::ACTION;
  1197.                                         }
  1198.                                 }
  1199.                                 if (obj.xAcc < 8.f)
  1200.                                         obj.xAcc += 0.1f;
  1201.                                 if (jjGameTicks & 1 == 0) {
  1202.                                         jjPARTICLE@ part = jjAddParticle(PARTICLE::SPARK);
  1203.                                         if (part !is null) {
  1204.                                                 part.xPos = obj.xPos - obj.direction*7;
  1205.                                                 part.yPos = obj.yPos - 21;
  1206.                                                 part.xSpeed = -obj.direction;
  1207.                                                 part.ySpeed = 0.5;
  1208.                                         }
  1209.                                 }
  1210.                                 obj.xPos += obj.xAcc * obj.direction;
  1211.                                 obj.yPos += obj.xAcc / 2;
  1212.                                 obj.counterEnd = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_FOEW5, obj.counterEnd);
  1213.                                 const auto newTileID = jjTileGet(4, uint(obj.xPos)>>5, uint(obj.yPos)>>5);
  1214.                                 if (newTileID == 496 || newTileID == 498) { //side of a tree
  1215.                                         obj.xPos += obj.xAcc * obj.direction * 2;
  1216.                                         obj.yPos += obj.xAcc;
  1217.                                         if (obj.special == 1 && isRealPlayer)
  1218.                                                 obj.state = STATE::ROTATE;
  1219.                                         else
  1220.                                                 release(obj);
  1221.                                 }
  1222.                         } else if (obj.state == STATE::DONE) {
  1223.                                 if (++obj.counter > 210) {
  1224.                                         obj.particlePixelExplosion(0);
  1225.                                         reset(obj);
  1226.                                 }
  1227.                         }
  1228.                 }
  1229.         }
  1230.         void reset(jjOBJ@ obj) const {
  1231.                 obj.state = STATE::START;
  1232.                 obj.xPos = obj.xOrg;
  1233.                 obj.yPos = obj.yOrg;
  1234.                 obj.playerHandling = HANDLING::PICKUP;
  1235.                 obj.counter = 0;
  1236.         }
  1237.         void release(jjOBJ@ obj) const {
  1238.                 if (obj.var[0] >= 0) {
  1239.                         jjPLAYER@ play = jjPlayers[obj.var[0]];
  1240.                         PlayerX@ playX = PlayerXs[play.localPlayerID];
  1241.                         if (playX.State == PlayerState::Ropeway) {
  1242.                                 playX.State = PlayerState::Normal;
  1243.                                 const float xThrust = obj.xAcc * obj.direction * 2;
  1244.                                 play.xAcc = jjCos(obj.special) * xThrust;
  1245.                                 play.ySpeed = -jjSin(obj.special) * xThrust;
  1246.                                 play.direction = obj.direction;
  1247.                                 play.invisibility = false;
  1248.                         }
  1249.                 }
  1250.                 obj.state = STATE::DONE;
  1251.         }
  1252.         bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ play, int force) {
  1253.                 PlayerX@ playX = PlayerXs[play.localPlayerID];
  1254.                 if (obj.var[0] == -1 && playX.State == PlayerState::Normal) {
  1255.                         obj.var[0] = play.playerID;
  1256.                         obj.state = STATE::ACTION;
  1257.                         obj.playerHandling = HANDLING::PARTICLE; //no further collisions
  1258.                         obj.xAcc = 0;
  1259.                         playX.State = PlayerState::Ropeway;
  1260.                         play.invisibility = true;
  1261.                 }
  1262.                 return true;
  1263.         }
  1264. }
  1265.  
  1266.  
  1267. class Turtlette : WalkingEnemy {
  1268.         Turtlette(jjOBJ@ preset) {
  1269.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::TURTLETTE], 0);
  1270.                 preset.xSpeed = 2.5;
  1271.                 preset.ySpeed = -4;
  1272.                 preset.yAcc = 0.3;
  1273.                 preset.deactivates = false;
  1274.                 super(preset, 6);
  1275.                 preset.playerHandling = HANDLING::SPECIALDONE;
  1276.                 preset.isTarget = false;
  1277.                 preset.state = STATE::FALL;
  1278.         }
  1279.         void onBehave(jjOBJ@ obj) override {
  1280.                 if (obj.state == STATE::START && obj.creatorType == CREATOR::OBJECT)
  1281.                         obj.direction = jjObjects[obj.creatorID].direction;
  1282.                 if (obj.state == STATE::WALK)
  1283.                         obj.yAcc = 0;
  1284.                 WalkingEnemy::onBehave(obj);
  1285.         }
  1286. }
  1287.  
  1288. bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas) {
  1289.         return player.gems[GEM::RED] == 0 && jjRenderFrame & 3 < 2;
  1290. }
  1291.  
  1292. void InvisibleSpring(jjOBJ@ obj) { obj.behave(BEHAVIOR::SPRING, false); }
  1293.  
  1294. uint ChainAnim, BallAnim;
  1295. const jjANIMFRAME@ BallCollisionFrame;
  1296. class BossBall {
  1297.         int xPos = 0, yPos = 0;
  1298.         float scale = 1;
  1299.         void method(const jjOBJ@ obj, bool draw) const {
  1300.                 float chainX = obj.xPos, chainY = obj.yPos;
  1301.                 const float deltaX = xPos - chainX, deltaY = yPos - chainY;
  1302.                 const auto angle = atan2(deltaY, deltaX);
  1303.                 int stepCount = int(sqrt(deltaY*deltaY + deltaX*deltaX) / 10);
  1304.                 const float stepX = cos(angle) * 10, stepY = sin(angle) * 10;
  1305.                 if (draw) {
  1306.                         const auto ChainFrame = jjAnimations[ChainAnim].firstFrame;
  1307.                         const auto BallFrame = jjAnimations[BallAnim].firstFrame;
  1308.                         while (stepCount-- > 0)
  1309.                                 jjDrawResizedSpriteFromCurFrame(chainX += stepX, chainY += stepY, ChainFrame, scale,scale);
  1310.                         jjDrawResizedSpriteFromCurFrame(xPos, yPos - 26, BallFrame + obj.frameID, scale,scale, SPRITE::NORMAL,0, scale < 1.f ? 4 : 3);
  1311.                 } else {
  1312.                         while (stepCount-- > 0)
  1313.                                 jjObjects[jjAddObject(OBJECT::SHARD, chainX += stepX, chainY += stepY)].curAnim = ChainAnim;
  1314.                         jjObjects[jjAddObject(OBJECT::SHARD, xPos, yPos - 26)].curAnim = BallAnim;
  1315.                 }
  1316.         }
  1317. }
  1318. array<BossBall> BossBalls;
  1319.  
  1320. class Boss : Enemy {
  1321.         Boss(jjOBJ@ preset) {
  1322.                 preset.determineCurAnim(ANIM::CUSTOM[AnimSets::BOSS], 0);
  1323.                 preset.energy = 100;
  1324.                 super(preset, 1);
  1325.                 preset.deactivates = false;
  1326.                 ChainAnim = jjObjectPresets[OBJECT::PINKPLATFORM].curAnim + 1;
  1327.                 BallAnim = jjObjectPresets[OBJECT::BURGER].curAnim;
  1328.                 @BallCollisionFrame = jjAnimFrames[jjAnimations[BallAnim]];
  1329.         }
  1330.         void onBehave(jjOBJ@ obj) {
  1331.                 switch (obj.state) {
  1332.                         case STATE::START:
  1333.                                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  1334.                                         jjPLAYER@ localPlayer = jjLocalPlayers[i];
  1335.                                         if (localPlayer.bossActivated) {
  1336.                                                 localPlayer.boss = obj.objectID;
  1337.                                                 obj.state = STATE::BOUNCE;
  1338.                                                 obj.age = 256;
  1339.                                                 BossBalls.resize(0);
  1340.                                                 BossBalls.insertLast(BossBall());
  1341.                                                 BossBalls[0].xPos = int(obj.xPos);
  1342.                                                 BossBalls[0].yPos = int(obj.yPos);
  1343.                                                 break;
  1344.                                         }
  1345.                                 }
  1346.                                 return;
  1347.                         case STATE::FREEZE:
  1348.                                 if (--obj.freeze == 0)
  1349.                                         obj.state = obj.oldState;
  1350.                                 break;
  1351.                         case STATE::BOUNCE:
  1352.                                 if (obj.counter < 180) ++obj.counter;
  1353.                                 obj.age += 2;
  1354.                                 obj.xPos = obj.xOrg + jjSin(obj.age) * 140;
  1355.                                 obj.yPos = obj.yOrg + jjSin(obj.age << 2) * 5;
  1356.                                 obj.direction = ((obj.age + 256) & 1023) < 511 ? 1 : -1;
  1357.                                 obj.frameID = (obj.age >> 4) & 3; //ball
  1358.                                 {
  1359.                                         BossBall@ ball = BossBalls[0];
  1360.                                         ball.xPos = int(obj.xPos + jjSin(obj.age) * obj.counter);
  1361.                                         ball.yPos = int(obj.yPos + abs(jjCos(obj.age)) * obj.counter * 5 / 6);
  1362.                                 }
  1363.                                 if (obj.age & 511 == 256 && obj.energy <= 60)
  1364.                                         obj.state = STATE::EXTRA;
  1365.                                 break;
  1366.                         case STATE::EXTRA:
  1367.                                 if (++obj.counterEnd < 80) { //shake
  1368.                                         obj.xPos += int(jjRandom() & 3) - 1.5;
  1369.                                         obj.yPos += int(jjRandom() & 3) - 1.5;
  1370.                                 } else if (obj.counterEnd == 80) {
  1371.                                         jjAddParticlePixelExplosion(obj.xPos, obj.yPos, obj.curFrame + 2, obj.direction, 2); //screen explodes
  1372.                                         jjSamplePriority(SOUND::COMMON_ICECRUSH);
  1373.                                         obj.state = STATE::ATTACK;
  1374.                                 }
  1375.                                 break;
  1376.                         case STATE::ATTACK: {
  1377.                                 float targetX, targetY;
  1378.                                 const auto nearestPlayerID = obj.findNearestPlayer(400*400);
  1379.                                 if (nearestPlayerID >= 0) {
  1380.                                         const jjPLAYER@ play = jjPlayers[nearestPlayerID];
  1381.                                         targetX = play.xPos;
  1382.                                         targetY = play.yPos;
  1383.                                 } else {
  1384.                                         targetX = obj.xOrg;
  1385.                                         targetY = obj.yOrg;
  1386.                                 }
  1387.                                
  1388.                                 const float maxXSpeed = jjDifficulty + 1; //1, 2, 3, 4
  1389.                                 if (obj.xPos < targetX) {
  1390.                                         obj.direction = 1;
  1391.                                         if (obj.xSpeed < maxXSpeed) obj.xSpeed += 0.075;
  1392.                                 } else {
  1393.                                         obj.direction = -1;
  1394.                                         if (obj.xSpeed > -maxXSpeed) obj.xSpeed -= 0.075;
  1395.                                 }
  1396.                                 if (obj.yPos < targetY) {
  1397.                                         if (obj.ySpeed < 0.8) obj.ySpeed += 0.075;
  1398.                                 } else {
  1399.                                         if (obj.ySpeed > -0.8) obj.ySpeed -= 0.075;
  1400.                                 }
  1401.                                
  1402.                                 obj.xPos += obj.xSpeed;
  1403.                                 obj.yPos += obj.ySpeed;
  1404.                                 const float absXSpeed = abs(obj.xSpeed);
  1405.                                 int targetAngle = int(atan2(obj.ySpeed, absXSpeed > 1.f ? absXSpeed : 1.f) * 162.974662f);
  1406.                                 if (obj.xSpeed > 0) targetAngle *= -1;
  1407.                                 if (targetAngle > obj.special) obj.special += 2;
  1408.                                 else obj.special -= 2;
  1409.                                
  1410.                                 if (jjDifficulty > 1 && obj.energy <= 30 && BossBalls.length == 1)
  1411.                                         BossBalls.insertLast(BossBall());
  1412.                                
  1413.                                 obj.age += 8;
  1414.                                 obj.frameID = (obj.age >> 6) & 3; //ball
  1415.                                 for (uint i = 0; i < BossBalls.length; ++i) {
  1416.                                         const auto@ ball = BossBalls[i];
  1417.                                         const int age = obj.age + (i << 9);
  1418.                                         const float dist = jjSin(age) * obj.counter;
  1419.                                         ball.scale = 1 + jjCos(age) / 4;
  1420.                                         ball.xPos = int(obj.xPos + jjCos(obj.special) * dist);
  1421.                                         ball.yPos = int(obj.yPos - jjSin(obj.special) * dist);
  1422.                                 }
  1423.                                 break; }
  1424.                         case STATE::KILL:
  1425.                                 obj.state = STATE::DONE;
  1426.                                 obj.playerHandling = HANDLING::PARTICLE;
  1427.                                 obj.counterEnd = 0;
  1428.                         case STATE::DONE:
  1429.                                 if (++obj.counterEnd < 80) { //shake
  1430.                                         obj.xPos += int(jjRandom() & 3) - 1.5;
  1431.                                         obj.yPos += int(jjRandom() & 3) - 1.5;
  1432.                                 } else if (obj.counterEnd == 80) {
  1433.                                         for (uint i = 0; i < BossBalls.length; ++i)
  1434.                                                 BossBalls[i].method(obj, false);
  1435.                                 } else if (obj.counterEnd == 255) {
  1436.                                         obj.delete();
  1437.                                         jjNxt();
  1438.                                 } else {
  1439.                                         obj.doesHurt = jjSampleLooped(obj.xPos, obj.yPos, SOUND::COMMON_BENZIN1, obj.doesHurt);
  1440.                                         if (jjGameTicks & 3 == 1) {
  1441.                                                 const float xPos = obj.xPos + int(jjRandom() & 63) - 31;
  1442.                                                 const float yPos = obj.yPos + int(jjRandom() & 63) - 31;
  1443.                                                 jjObjects[jjAddObject(OBJECT::EXPLOSION, xPos, yPos)].determineCurAnim(ANIM::AMMO, 3 + (jjRandom() & 2));
  1444.                                                 jjOBJ@ turtlette = jjObjects[jjAddObject(OBJECT::COKE, xPos, yPos, obj.creatorID, CREATOR::OBJECT)];
  1445.                                                 if (jjRandom() & 1 == 1) turtlette.direction = -1;
  1446.                                         }
  1447.                                 }
  1448.                                 return;
  1449.                 }
  1450.                
  1451.                 for (uint i = 0; i < BossBalls.length; ++i) {
  1452.                         const auto@ ball = BossBalls[i];
  1453.                         const auto ballYPos = ball.yPos - 26;
  1454.                         if (abs(ball.scale - 1.f) < 0.15f) {
  1455.                                 for (int j = 0; j < jjLocalPlayerCount; ++j) {
  1456.                                         jjPLAYER@ localPlayer = jjLocalPlayers[j];
  1457.                                         if (localPlayer.blink == 0 && BallCollisionFrame.doesCollide(ball.xPos, ballYPos, 1, jjAnimFrames[localPlayer.curFrame], int(localPlayer.xPos), int(localPlayer.yPos), localPlayer.direction))
  1458.                                                 localPlayer.hurt();
  1459.                                 }
  1460.                                 for (uint j = jjObjectCount; --j != 0;) {
  1461.                                         jjOBJ@ bullet = jjObjects[j];
  1462.                                         if (bullet.isActive && bullet.playerHandling == HANDLING::PLAYERBULLET && bullet.state != STATE::EXPLODE && BallCollisionFrame.doesCollide(ball.xPos, ballYPos, 1, jjAnimFrames[bullet.curFrame], int(bullet.xPos), int(bullet.yPos), bullet.direction, true))
  1463.                                                 bullet.state = STATE::EXPLODE;
  1464.                                 }
  1465.                         }
  1466.                 }
  1467.                
  1468.                 if (obj.state == STATE::FREEZE)
  1469.                         return;
  1470.                        
  1471.                 for (int i = 0; i < jjLocalPlayerCount; ++i) {
  1472.                         jjPLAYER@ localPlayer = jjLocalPlayers[i];
  1473.                         if (localPlayer.invincibility < -2)
  1474.                                 localPlayer.invincibility = -2; //antibuttstomp
  1475.                         if (obj.justHit != 0)
  1476.                                 localPlayer.specialMove = 0;
  1477.                 }
  1478.                
  1479.                 if (jjDifficulty > 0 && obj.energy < 75 && jjGameTicks & 31 == 0)
  1480.                         for (int i = -1; i < 2; ++i)
  1481.                                 AddEnemyBullet(obj, i, -4, 0.25, false);
  1482.         }
  1483.         void onDraw(jjOBJ@ obj) {
  1484.                 if (obj.state == STATE::START) //not yet activated
  1485.                         return;
  1486.                 if (obj.state == STATE::DONE && obj.counterEnd >= 80)
  1487.                         return;
  1488.                 for (uint i = 0; i < BossBalls.length; ++i)
  1489.                         BossBalls[i].method(obj, true);
  1490.                 const SPRITE::Mode mainDrawingMode = (obj.state != STATE::FREEZE) ? (obj.justHit == 0) ? SPRITE::NORMAL : SPRITE::SINGLECOLOR : SPRITE::FROZEN; //ship
  1491.                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.special, 1,1, mainDrawingMode,15);
  1492.                 if (jjGameTicks & 3 < 2)
  1493.                         jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + 1, obj.special, 1,1, mainDrawingMode,15); //fire
  1494.                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + 3, obj.special, obj.direction,1, mainDrawingMode,15); //devan
  1495.                 if (obj.state == STATE::BOUNCE || (obj.state == STATE::EXTRA && obj.counterEnd < 80))
  1496.                         jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame + 2, obj.special, 1,1, (obj.state != STATE::FREEZE) ? (obj.justHit == 0) ? SPRITE::TRANSLUCENT : SPRITE::TRANSLUCENTCOLOR : SPRITE::FROZEN,15); //screen
  1497.         }
  1498. }
  1499.  
  1500.  
  1501. bool onCheat(string &in cheat) {
  1502.         if (cheat == "jjgems") {
  1503.                 for (int i = 0; i < jjLocalPlayerCount; ++i)
  1504.                         jjLocalPlayers[i].gems[GEM::RED] = jjLocalPlayers[i].gems[GEM::RED] + 10;
  1505.                 jjAlert("jjgems", false, STRING::MEDIUM);
  1506.                 return true;
  1507.         }
  1508.         return false;
  1509.                        
  1510. }