Downloads containing BL18.asc

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

File preview

  1. #include "Resize v10.asc"
  2.  
  3. #pragma require "BL18.j2a"
  4. #pragma require "BL18.bmp"
  5. #pragma require "HPYeahoo.wav"
  6. #pragma require "HPCrystal.wav"
  7. #pragma require "HPSwitch.wav"
  8. #pragma require "HPTreasure.wav"
  9. #pragma require "HPTrigger.wav"
  10. #pragma require "HPWarp.wav"
  11. #pragma require "ESJM1.wav"
  12. #pragma require "ESJM2.wav"
  13. #pragma require "ESJM3.wav"
  14. #pragma require "ESJF1.wav"
  15. #pragma require "ESJF2.wav"
  16. #pragma require "ESJF3.wav"
  17.  
  18. namespace BL18 {
  19.         enum AnimSets {
  20.                 Basketball, Bridge, Canopy, Cow, Duet, Egg, FatChick, Font, Fooruman, Gear, LavaLamp, Misc, Monk, Muttshroom, PurpleDragon, SmartBomb, Trogdor, TubeTurle,
  21.                 _LAST
  22.         };
  23.        
  24.         const array<string> FriendNames = {
  25.                 "pttDM", "|UR|Ja|zz|CC", "BeckY", "|l<rB|g|B", "evilmike", "Ma|tt|W LD CC", "DreamaGpW", "LbRTk|TF", "Rd|im", "|Blk j", "violetclm DM", "||||||||'", "", "",
  26.                 "Jzz", "KJAZZ", "||||Hugela", "Trafton", "|||Bean", "|L||||k||*", "|et", "||||Blurred||D", "The Server", "|||Smoke|[NC]", "-t3>PJ", "blacky[si]", "cooba[si]", "slmnDR DM"
  27.         };
  28.         const array<bool> FriendSpaz = {
  29.                 true, false, true, false, true, true, true, true, false, false, true, true, false, false,
  30.                 true, false, true, true, true, false, false, true, false, true, true, true, true, true
  31.                
  32.         };
  33.         const uint FirstFriendPlayerID = 32 - FriendNames.length;
  34.         uint FriendColor(uint8 a, uint8 b, uint8 c, uint8 d) { return a | (b << 8) | (c << 16) | (d << 24); }
  35.         const array<uint> FriendColors = {
  36.                 FriendColor(72, 72, 32, 80), FriendColor(29,29,29,29), FriendColor(1,1,1,1), FriendColor(34,24,31,16), FriendColor(72,32,72,72), FriendColor(1,1,1,1), FriendColor(1,1,1,1), FriendColor(80,80,80,80), FriendColor(1,1,1,1), FriendColor(1,88,40,16), FriendColor(32, 72, 24, 88), FriendColor(40,80,72,80), FriendColor(152,152,152,152), FriendColor(72, 24,24,24),
  37.                 FriendColor(32,16,64,40), FriendColor(24,16,40,32), FriendColor(32,40,96,104), FriendColor(32,1,16,1), FriendColor(24,32,24,72), FriendColor(74,96,40,104), FriendColor(73,24,24,39), FriendColor(32,40,32,32), FriendColor(24,24,24,24), FriendColor(72,72,72,72), FriendColor(40,88,32,80), FriendColor(24,1,24,24), FriendColor(32,88,32,1), FriendColor(72,16,40,32)
  38.         };
  39.         //jazz: fur, headband, gun, armbands
  40.         //spaz: gun, ears, shoes, chest
  41.         const array<uint8> KeyColors = {
  42.                 48, 80, 24, 72, 32, 88, 40, 16
  43.         };
  44.        
  45.        
  46.         const SOUND::Sample HPYeahoo =          SOUND::P2_FROG1;
  47.         const SOUND::Sample HPCrystal =         SOUND::P2_FROG2;
  48.         const SOUND::Sample HPSwitch =          SOUND::P2_FROG3;
  49.         const SOUND::Sample HPTreasure =        SOUND::COMMON_PICKUP1;
  50.         const SOUND::Sample HPTrigger =         SOUND::P2_FROG5;
  51.         const SOUND::Sample HPWarp =            SOUND::P2_FART;
  52.         const SOUND::Sample HPLaughter =        SOUND::P2_CRUNCH;
  53.        
  54.         const SOUND::Sample ESJM =                      SOUND::P2_PLOPSEQ1;
  55.         const SOUND::Sample ESJF =                      SOUND::P2_PLOPSEQ4;
  56.        
  57.        
  58.         const bool CountEnemies = false; //only useful during development, for determining the total numbers of enemies in the whole pack, to display in the Guardian level clear screen
  59.        
  60.        
  61.         const uint heartFrame = jjAnimations[jjAnimSets[ANIM::PICKUPS] + 41];
  62.         int ExtraHealthPadding = 0;
  63.         uint skullFrame;
  64.         const array<uint8> StaticColors = {28, 29, 30, 76, 77, 78};
  65.         jjPLAYER@ Player = jjLocalPlayers[0];
  66.         float PlayerLastSpeed = 0;
  67.         const auto oldScore = Player.score;
  68.         const uint LocalPlayerID = Player.localPlayerID;
  69.        
  70.         array<uint> GemTargets(4, 0), GemCounts(4, 0);
  71.         uint TimeSpentNotViewingPopups = 0;
  72.        
  73.         bool IsMurderer = false;
  74.         jjVOIDFUNC@ myLevelReload = null;
  75.         jjVOIDFUNC@ myCoinWarpFunction = null;
  76.         funcdef void CanvasFunction(jjPLAYER@, jjCANVAS@);
  77.         CanvasFunction@ myHUD = null;
  78.        
  79.         int GetAngle(float xSpeed, float ySpeed) {
  80.                 return int(atan2(
  81.                     ySpeed,
  82.                     xSpeed
  83.                 ) * -512.0 * 0.318309886142228);
  84.         }
  85.        
  86.         class Point {
  87.                 float x; float y;
  88.                 Point(){}
  89.                 Point(float xx, float yy) {
  90.                         x = xx;
  91.                         y = yy;
  92.                 }
  93.                 const Point& opAssign(const Point &in other) {
  94.                         x = other.x;
  95.                         y = other.y;
  96.                         return this;
  97.                 }
  98.                 bool opEquals(const Point &in other) const {
  99.                         return x == other.x && y == other.y;
  100.                 }
  101.         }
  102.         enum CollectibleTypes {
  103.                 Coin, Pink,Turquoise,Red,Silver,Blue,Purple,Gold,Green
  104.         };
  105.         class CollectibleLocation {
  106.                 Point Location;
  107.                 bool Found = false;
  108.                 CollectibleTypes Type;
  109.                 CollectibleLocation(Point l, CollectibleTypes t) { Location = l; Type = t; }
  110.                 CollectibleLocation(){}
  111.         }
  112.         array<CollectibleLocation> CollectibleLocations;
  113.         Point CoinWarpLocation;
  114.         bool CoinWarpFound = false;
  115.         const uint CoinAnim = jjObjectPresets[OBJECT::SILVERCOIN].curAnim;
  116.         const uint CoinFirstFrame = jjAnimations[CoinAnim];
  117.         uint KeyFrame, PogoFrame;
  118.        
  119.         string musicFilename = jjMusicFileName;
  120.        
  121.         void Setup() {
  122.                 jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]].load(AnimSets::Misc, "BL18.j2a");
  123.                 const uint firstMiscMiscFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 1];
  124.                 KeyFrame = firstMiscMiscFrame + 3;
  125.                
  126.                 for (uint i = 4; i <= 5; ++i)
  127.                         Resize::Resize(jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + i], 2, Resize::Method::Scale2xSAI | Resize::Flags::OnlySpriteColors);
  128.                        
  129.                 for (uint i = 5; i <= 20; ++i)
  130.                         if (i <= 6 || i >= 19) {
  131.                                 const uint frameID = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 1] + i;
  132.                                 Resize::Resize(Resize::Resize(jjPIXELMAP(jjAnimFrames[frameID]), 0.5), 2, Resize::Method::Scale2x).save(jjAnimFrames[frameID]);
  133.                         }
  134.                
  135.                 TrueColor::ProcessPalette();
  136.                 const auto skullAnimSet = TrueColor::FindCustomAnim();
  137.                 TrueColor::AllocateSpriteSheet(skullAnimSet, TrueColor::Bitmap("BL18.bmp"), array<array<TrueColor::Coordinates>>={{TrueColor::Coordinates(0,0,40,45,-20,-22), TrueColor::Coordinates(40,0,64,32,-32,0), TrueColor::Coordinates(40,32,64,32,-32,0)}});
  138.                 skullFrame = jjAnimations[jjAnimSets[skullAnimSet]];
  139.                
  140.                 if ((FriendColors.length != FriendNames.length) || (FriendColors.length != FriendSpaz.length))
  141.                         jjDebug("Inconsistent friend array lengths!");
  142.                
  143.                 for (uint i = 0; i < FriendColors.length; ++i)
  144.                         jjPlayers[FirstFriendPlayerID + i].fur = FriendColors[i];
  145.                 ConversationAppearance.xAmp = ConversationAppearance.yAmp = 0;
  146.                
  147.                 jjAnimFrames[heartFrame].transparent = true;
  148.                
  149.                 Player.morphTo(Player.charOrig = CHAR::JAZZ, false);
  150.                
  151.                 jjSampleLoad(HPYeahoo, "HPYeahoo.wav");
  152.                 jjSampleLoad(HPCrystal, "HPCrystal.wav");
  153.                 jjSampleLoad(HPSwitch, "HPSwitch.wav");
  154.                 jjSampleLoad(HPTreasure, "HPTreasure.wav");
  155.                 jjSampleLoad(HPTrigger, "HPTrigger.wav");
  156.                 jjSampleLoad(HPWarp, "HPWarp.wav");
  157.                 for (int i = 0; i < 3; ++i) {
  158.                         jjSampleLoad(SOUND::Sample(ESJM + i), "ESJM" + (i+1) + ".wav");
  159.                         jjSampleLoad(SOUND::Sample(ESJF + i), "ESJF" + (i+1) + ".wav");
  160.                 }
  161.                
  162.                 uint EnemyCount = 0;
  163.                 array<uint> EventsAreEnemies(256, 0);
  164.                 const uint levelID = jjLevelFileName.substr(jjLevelFileName.length - 5, 1)[0] - '0'[0];
  165.                 if (CountEnemies) {
  166.                         EventsAreEnemies[OBJECT::FATCHICK] = 1;
  167.                         EventsAreEnemies[OBJECT::FENCER] = 1;
  168.                         EventsAreEnemies[OBJECT::FLOATLIZARD] = 1;
  169.                         EventsAreEnemies[OBJECT::TUBETURTLE] = 1;
  170.                         EventsAreEnemies[OBJECT::FISH] = 1;
  171.                         EventsAreEnemies[OBJECT::BAT] = 1;
  172.                         EventsAreEnemies[OBJECT::MONKEY] = 1;
  173.                         EventsAreEnemies[OBJECT::STANDMONKEY] = 1;
  174.                         EventsAreEnemies[OBJECT::DOGGYDOGG] = 1;
  175.                         EventsAreEnemies[OBJECT::WATERMELON] = 1; //Trogdor
  176.                         EventsAreEnemies[OBJECT::BILSY] = 1;
  177.                         EventsAreEnemies[OBJECT::PEACH] = 4; //Frog
  178.                         EventsAreEnemies[OBJECT::FLOATSUCKER] = 1;
  179.                         EventsAreEnemies[OBJECT::SUCKER] = 1;
  180.                         EventsAreEnemies[OBJECT::LABRAT] = 1;
  181.                         EventsAreEnemies[OBJECT::RAVEN] = 1;
  182.                         EventsAreEnemies[OBJECT::DEMON] = 1;
  183.                         EventsAreEnemies[OBJECT::SPARK] = 2;
  184.                         EventsAreEnemies[OBJECT::HATTER] = 1;
  185.                         if (levelID == 3) {
  186.                                 EventsAreEnemies[OBJECT::UTERUS] = 1 + 4;// + 2 * jjDifficulty; //spikeballs //doing this addition in Guardian instead
  187.                         } else if (levelID == 4) {
  188.                                 EventsAreEnemies[OBJECT::MORPH] = 8; //BN
  189.                                 EventsAreEnemies[OBJECT::MILK] = 1; //Cow
  190.                                 EventsAreEnemies[OBJECT::DEVILDEVAN] = 1;
  191.                         } else if (levelID == 6) {
  192.                                 EventsAreEnemies[OBJECT::UTERUS] = 1;
  193.                                 EventsAreEnemies[OBJECT::MORPH] = 1; //PJ
  194.                                 EnemyCount = 1; //Jill/Fooruman
  195.                         } else if (levelID == 7) {
  196.                                 EventsAreEnemies[OBJECT::MORPH] = 1; //Monk, including Greenkiller
  197.                         }
  198.                 }
  199.                 for (uint i = 0; i < 256; ++i)
  200.                         jjObjectPresets[i].points = 0;
  201.                 array<bool> EventsFound(256, false);
  202.                 for (int x = 0; x < jjLayerWidth[4]; ++x)
  203.                         for (int y = jjLayerHeight[4] - 1; y >= 0; --y) {
  204.                                 uint8 eventID = jjEventGet(x, y);
  205.                                 if (CountEnemies) {
  206.                                         if (eventID == OBJECT::GRASSPLATFORM && levelID == 6) { //ferris wheel
  207.                                                 if (jjParameterGet(x,y, 12,1) == 1) //with mortar enemies
  208.                                                         EnemyCount += jjParameterGet(x,y, 2,5); //number of platforms
  209.                                         } else {
  210.                                                 uint8 enemyEventID = eventID;
  211.                                                 //detect events hidden inside walls
  212.                                                 if (eventID == OBJECT::BANANA) //HocusWall
  213.                                                         enemyEventID = jjParameterGet(x, y, 5, 8);
  214.                                                 else if (eventID == OBJECT::CHERRY) // HocusDestructScenery
  215.                                                         enemyEventID = jjParameterGet(x, y, 0, 8);
  216.                                                 else if (enemyEventID == OBJECT::FLOATLIZARD && jjParameterGet(x, y, 0, 3) == 3) //fake egg
  217.                                                         EnemyCount -= 1;
  218.                                                 EnemyCount += EventsAreEnemies[enemyEventID];
  219.                                         }
  220.                                 }
  221.                                 if (eventID > 32 && !EventsFound[eventID]) {
  222.                                         EventsFound[eventID] = true;
  223.                                         jjOBJ@ preset = jjObjectPresets[eventID];
  224.                                         switch (eventID) {
  225.                                                 case OBJECT::FATCHICK:
  226.                                                         FatChick(preset);
  227.                                                         break;
  228.                                                 case OBJECT::FENCER:
  229.                                                         Fencer(preset);
  230.                                                         break;
  231.                                                 case OBJECT::FLOATLIZARD:
  232.                                                         FloatLizard(preset);
  233.                                                         break;
  234.                                                 case OBJECT::TUBETURTLE:
  235.                                                         TubeTurle(preset);
  236.                                                         break;
  237.                                                 case OBJECT::FISH:
  238.                                                         Fish(preset);
  239.                                                         break;
  240.                                                 case OBJECT::BAT:
  241.                                                         RedBat(preset);
  242.                                                         break;
  243.                                                 case OBJECT::MONKEY:
  244.                                                 case OBJECT::STANDMONKEY:
  245.                                                         Monkey(preset);
  246.                                                         break;
  247.                                                 case OBJECT::DOGGYDOGG:
  248.                                                         Muttshroom(preset);
  249.                                                         break;
  250.                                                 case OBJECT::WATERMELON:
  251.                                                         Trogdor(preset);
  252.                                                         break;
  253.                                                 case OBJECT::BILSY:
  254.                                                         MiniBilsy(preset);
  255.                                                         break;
  256.                                                 case OBJECT::PEACH:
  257.                                                         FrogFly(jjObjectPresets[OBJECT::DRAGONFLY]);
  258.                                                         Frog(preset);
  259.                                                         break;
  260.                                                 case OBJECT::FLOATSUCKER:
  261.                                                         FloatSucker(preset);
  262.                                                         break;
  263.                                                 case OBJECT::SUCKER:
  264.                                                         Sucker(preset);
  265.                                                         break;
  266.                                                 case OBJECT::LABRAT:
  267.                                                         Labrat(preset);
  268.                                                         break;
  269.                                                 case OBJECT::RAVEN:
  270.                                                         Phoenix(preset);
  271.                                                         break;
  272.                                                 case OBJECT::DEMON:
  273.                                                         Demon(preset);
  274.                                                         break;
  275.                                                 case OBJECT::SPARK:
  276.                                                         Duet(preset);
  277.                                                         break;
  278.                                                 case OBJECT::HATTER:
  279.                                                         Hatter(preset);
  280.                                                         break;
  281.                                                 case OBJECT::SILVERCOIN:
  282.                                                         EventsFound[eventID] = false;
  283.                                                         CollectibleLocations.insertLast(CollectibleLocation(Point(x * 32 + 15, y * 32 + 15), CollectibleTypes(jjParameterGet(x, y, 0, 4))));
  284.                                                         break;
  285.                                                 case OBJECT::WARP:
  286.                                                         if (jjParameterGet(x, y, 17, 1) == 0) { //ShowAnim=0: just an area, not a warp object
  287.                                                                 EventsFound[eventID] = false;
  288.                                                                 break;
  289.                                                         }
  290.                                                         CoinWarpLocation = Point(x * 32, y * 32);
  291.                                                         preset.behavior = CoinWarp;
  292.                                                         break;
  293.                                                 case OBJECT::CARROT:
  294.                                                         preset.behavior = RestrictedCarrot;
  295.                                                         break;
  296.                                                 case OBJECT::BOUNCERAMMO3:
  297.                                                 case OBJECT::ICEAMMO3:
  298.                                                 case OBJECT::SEEKERAMMO3:
  299.                                                 case OBJECT::RFAMMO3:
  300.                                                 case OBJECT::TOASTERAMMO3:
  301.                                                 case OBJECT::TNTAMMO3:
  302.                                                 case OBJECT::GUN8AMMO3:
  303.                                                 case OBJECT::GUN9AMMO3:
  304.                                                         preset.behavior = NotOnHardPickup;
  305.                                                         break;
  306.                                                 case OBJECT::APPLE:
  307.                                                         preset.scriptedCollisions = true;
  308.                                                         preset.curFrame = KeyFrame + 1;
  309.                                                         preset.behavior = Lock();
  310.                                                         break;
  311.                                                 case OBJECT::BANANA:
  312.                                                         preset.behavior = HocusWall;
  313.                                                         preset.playerHandling = HANDLING::PARTICLE;
  314.                                                         break;
  315.                                                 case OBJECT::CHERRY:
  316.                                                         preset.behavior = HocusDestructScenery();
  317.                                                         preset.playerHandling = HANDLING::SPECIAL;
  318.                                                         preset.bulletHandling = HANDLING::DETECTBULLET;
  319.                                                         preset.scriptedCollisions = true;
  320.                                                         preset.isFreezable = false;
  321.                                                         preset.triggersTNT = true;
  322.                                                         preset.determineCurAnim(ANIM::DESTSCEN, 2);
  323.                                                         preset.determineCurFrame();
  324.                                                         break;
  325.                                                 case OBJECT::REDGEM:
  326.                                                         HocusGem(preset, 0, 0, 10, 100);
  327.                                                         break;
  328.                                                 case OBJECT::PURPLEGEM:
  329.                                                         HocusGem(preset, 0, 3, 4, 250);
  330.                                                         break;
  331.                                                 case OBJECT::GREENGEM:
  332.                                                         HocusGem(preset, 1, 1, 5, 500);
  333.                                                         break;
  334.                                                 case OBJECT::BLUEGEM:
  335.                                                         HocusGem(preset, 2, 2, 5, 1000);
  336.                                                         break;
  337.                                                 case OBJECT::ORANGE: {
  338.                                                         preset.behavior = WarpPotion();
  339.                                                         preset.scriptedCollisions = true;
  340.                                                         Recolor(jjAnimations[preset.determineCurAnim(ANIM::PICKUPS, 78)], array<uint8>={88}, RecolorReplace(32));
  341.                                                         break; }
  342.                                                 case OBJECT::PEAR:
  343.                                                         preset.behavior = Switch();
  344.                                                         preset.playerHandling = HANDLING::PARTICLE;
  345.                                                         preset.curFrame = firstMiscMiscFrame + 5;
  346.                                                         break;
  347.                                                 case OBJECT::PRETZEL:
  348.                                                         preset.behavior = Friend();
  349.                                                         preset.playerHandling = HANDLING::PARTICLE;
  350.                                                         preset.determineCurAnim(ANIM::MENU,0,false);
  351.                                                         preset.curFrame = firstMiscMiscFrame + 5;
  352.                                                         break;
  353.                                                 case OBJECT::STRAWBERRY:
  354.                                                         preset.behavior = Furryball;
  355.                                                         preset.playerHandling = HANDLING::ENEMYBULLET;
  356.                                                         preset.animSpeed = jjDifficulty > 0 ? 2 : 1;
  357.                                                         preset.curFrame = firstMiscMiscFrame + 9;
  358.                                                         preset.killAnim = jjAnimSets[ANIM::AMMO] + 5;
  359.                                                         break;
  360.                                                 case OBJECT::LEMON:
  361.                                                         preset.behavior = Elevator;
  362.                                                         preset.isBlastable = false;
  363.                                                         preset.playerHandling = HANDLING::SPECIAL;
  364.                                                         preset.bulletHandling = HANDLING::DESTROYBULLET;
  365.                                                         preset.curFrame = skullFrame + TrueColor::NumberOfFramesPerImage;
  366.                                                         preset.special = firstMiscMiscFrame + 10; //8-bit
  367.                                                         preset.deactivates = false;
  368.                                                         break;
  369.                                                 case OBJECT::LIME:
  370.                                                         preset.behavior = Spikes;
  371.                                                         preset.playerHandling = HANDLING::ENEMYBULLET;
  372.                                                         preset.animSpeed = 1;
  373.                                                         preset.curFrame = firstMiscMiscFrame + 12;
  374.                                                         break;
  375.                                                 case 122: //lava
  376.                                                         LevelContainsLava = true;
  377.                                                         preset.behavior = function(obj) { obj.behavior = Lava(); };
  378.                                                         preset.playerHandling = HANDLING::PARTICLE;
  379.                                                         preset.determineCurAnim(ANIM::CUSTOM[AnimSets::Misc], 6);
  380.                                                         preset.special = firstMiscMiscFrame + 14;
  381.                                                         break;
  382.                                                 case OBJECT::BRIDGE:
  383.                                                         jjAnimSets[ANIM::BRIDGE].load(AnimSets::Bridge, "BL18.j2a");
  384.                                                         break;
  385.                                                 case OBJECT::SAVEPOST:
  386.                                                         preset.deactivates = false;
  387.                                                         preset.behavior = CheckpointWrapper;
  388.                                                         preset.curFrame = (jjAnimations[preset.curAnim] = jjAnimations[jjAnimSets[ANIM::CUSTOM[AnimSets::Misc]] + 8]).firstFrame;
  389.                                                         break;
  390.                                                 case OBJECT::BIGROCK:
  391.                                                         preset.behavior = CompanionRock;
  392.                                                         jjAnimFrames[preset.curFrame].hotSpotX -= 4; //to be better able to avoid landing on elevators
  393.                                                         break;
  394.                                                 case OBJECT::GRAPES:
  395.                                                         preset.behavior = Balloon();
  396.                                                         preset.scriptedCollisions = true;
  397.                                                         preset.curFrame = firstMiscMiscFrame + 18;
  398.                                                         break;
  399.                                                 case OBJECT::FLYCARROT:
  400.                                                         preset.behavior = TemporaryFlyCarrot(); //this is going to be limited to the PJ level, but it's similar enough code to some of this other stuff I find it more convenient to have it in here
  401.                                                         preset.scriptedCollisions = true;
  402.                                                         break;
  403.                                                 case OBJECT::LETTUCE:
  404.                                                         if (jjDifficulty > 0) { //no smart bombs on easy
  405.                                                                 preset.behavior = SmartBomb();
  406.                                                                 preset.playerHandling = HANDLING::SPECIAL;
  407.                                                                 preset.scriptedCollisions = true;
  408.                                                                 preset.curAnim = jjAnimSets[TrueColor::FindCustomAnim()].load(AnimSets::SmartBomb, "BL18.j2a");
  409.                                                                 preset.killAnim = jjAnimSets[ANIM::AMMO] + 77;
  410.                                                                 preset.lightType = LIGHT::BRIGHT;
  411.                                                                 preset.light = 9;
  412.                                                         } else {
  413.                                                                 preset.behavior = BEHAVIOR::INACTIVE;
  414.                                                         }
  415.                                                         break;
  416.                                                 case OBJECT::BURGER:
  417.                                                         preset.behavior = Gear;
  418.                                                         preset.playerHandling = HANDLING::PARTICLE;
  419.                                                         preset.curAnim = jjAnimSets[TrueColor::FindCustomAnim()].load(AnimSets::Gear, "BL18.j2a");
  420.                                                         preset.determineCurFrame();
  421.                                                         break;
  422.                                         }
  423.                                 }
  424.                                 //detect events hidden inside walls
  425.                                 if (eventID == OBJECT::BANANA) //HocusWall
  426.                                         eventID = jjParameterGet(x, y, 5, 8);
  427.                                 else if (eventID == OBJECT::CHERRY) // HocusDestructScenery
  428.                                         eventID = jjParameterGet(x, y, 0, 8);
  429.                                 if (eventID >= OBJECT::REDGEM && eventID <= OBJECT::PURPLEGEM) {
  430.                                         GemTargets[eventID - OBJECT::REDGEM] += 1;
  431.                                 }
  432.                         }
  433.                        
  434.                 if (CountEnemies)
  435.                         jjPrint("Total enemies:  " + EnemyCount);
  436.                        
  437.                 jjOBJ@ preset = jjObjectPresets[OBJECT::THING]; //do this outside the loop, because of JJPOGO
  438.                 preset.behavior = PogoStick();
  439.                 preset.playerHandling = HANDLING::PARTICLE;
  440.                 preset.curFrame = firstMiscMiscFrame + 5;
  441.                 jjAnimations[preset.curAnim].firstFrame = PogoFrame = firstMiscMiscFrame + 16;
  442.                 @preset = jjObjectPresets[OBJECT::SILVERCOIN]; //do this once, because coins are counted EVERY time they show up inside the loop
  443.                 preset.scriptedCollisions = true;
  444.                 preset.behavior = CollectableCoin();
  445.        
  446.                 if (jjResolutionMaxWidth != 800 || jjResolutionMaxHeight != 600)
  447.                         jjChat("/maxresolution 800x600");
  448.                
  449.                 int targetMaxHealth = 5 + Player.score % 50; //increased by 100% gem collection bonuses
  450.                 if (jjDifficulty != 0 ) targetMaxHealth -= jjDifficulty - 1; //5,5,4,3
  451.                 targetMaxHealth += ExtraHealthPadding;
  452.                 if (jjMaxHealth != targetMaxHealth) {
  453.                         jjChat("/smhealth " + targetMaxHealth);
  454.                 }
  455.                 for (int i = 0; i < 8; ++i)
  456.                         jjAlert(""); //move commands out of onscreen chatlog
  457.                
  458.                 //fireballs, not pepper spray
  459.                 jjWeapons[WEAPON::GUN8].gradualAim = false;
  460.                 jjWeapons[WEAPON::GUN8].spread = SPREAD::NORMAL;
  461.         }
  462.        
  463.         void RestrictedCarrot(jjOBJ@ obj) {
  464.                 if (IsMurderer) obj.delete();
  465.                 else obj.behave(BEHAVIOR::PICKUP);
  466.         }
  467.         void NotOnHardPickup(jjOBJ@ obj) {
  468.                 if (jjDifficulty >= 2 && obj.creator == CREATOR::LEVEL)
  469.                         obj.delete();
  470.                 else
  471.                         obj.behavior = BEHAVIOR::PICKUP;
  472.         }
  473.        
  474.         class CollectableCoin : jjBEHAVIORINTERFACE {
  475.                 void onBehave(jjOBJ@ obj) {
  476.                         if (obj.state == STATE::START) {
  477.                                 obj.var[0] = jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0, 4);
  478.                                 obj.points = 0;
  479.                                 //if (obj.var[0] != 0)
  480.                                         //obj.points *= 4;
  481.                         }
  482.                         obj.behave(BEHAVIOR::PICKUP, false);
  483.                         if (obj.var[0] != 0) obj.curFrame = KeyFrame;
  484.                         jjDrawSpriteFromCurFrame(obj.xPos, (obj.ySpeed != 0.f) ? obj.yPos : (obj.yPos + jjSin((obj.objectID*8+jjGameTicks+int(obj.xPos+obj.yPos*256))*16) * 4), obj.curFrame, obj.direction, SPRITE::SINGLEHUE, obj.var[0] == 0 ? 72 : KeyColors[obj.var[0] - 1]);
  485.                 }
  486.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  487.                         const Point myLocation(obj.xOrg, obj.yOrg);
  488.                         for (uint i = 0; i < CollectibleLocations.length; ++i)
  489.                                 if (myLocation == CollectibleLocations[i].Location) {
  490.                                         CollectibleLocations[i].Found = true;
  491.                                         break;
  492.                                 }
  493.                         jjSample(obj.xPos, obj.yPos, (obj.var[0] == 0) ? HPCrystal : HPYeahoo);
  494.                         jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos)].curAnim = obj.killAnim;
  495.                         obj.delete();
  496.                         jjEventSet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0);
  497.                         return true;
  498.                 }
  499.         }
  500.        
  501.         const array<SOUND::Sample> ButtstompSounds = {SOUND::COMMON_SPLAT1, SOUND::COMMON_SPLAT2, SOUND::COMMON_SPLAT3, SOUND::COMMON_SPLAT4};
  502.         enum Gender { Male, Female, Random };
  503.         class Enemy : jjBEHAVIORINTERFACE {
  504.                 ANIM::Set animSetID;
  505.                 Gender gender = Gender::Random;
  506.                 Enemy(jjOBJ@ preset, BL18::AnimSets asi = BL18::AnimSets::_LAST) {
  507.                         preset.behavior = this;
  508.                         if (asi != AnimSets::_LAST)
  509.                                 jjAnimSets[animSetID = TrueColor::FindCustomAnim()].load(asi, "BL18.j2a");
  510.                         preset.playerHandling = HANDLING::SPECIAL;
  511.                         preset.scriptedCollisions = true;
  512.                         preset.bulletHandling = HANDLING::DETECTBULLET;
  513.                         preset.isTarget = true;
  514.                         preset.triggersTNT = true;
  515.                         preset.isFreezable = true;
  516.                         preset.isBlastable = false; //not bothering with that nonsense
  517.                 }
  518.                 void die(jjOBJ@ obj, bool diedToBullet, bool diedWhileFrozen) const {
  519.                         Player.food += 1;
  520.                         obj.particlePixelExplosion(diedToBullet ? 0 : 2);
  521.                         for (uint i = 0; i < 30; ++i) {
  522.                                 if (i < 10)
  523.                                         jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, i, CREATOR::OBJECT, BloodSpatter);
  524.                                 jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos - (jjRandom() & 15), i, CREATOR::OBJECT, BoneWrapper)].freeze = diedWhileFrozen ? 255 : 0;
  525.                         }
  526.                         obj.energy = 0;
  527.                         obj.state = STATE::KILL;
  528.                         removeFromEventMap(obj);
  529.                         jjSamplePriority(jjRandom() & 3 < 3 ? SOUND::INTRO_BOEM1 : SOUND::INTRO_BOEM2);
  530.                         obj.delete();
  531.                 }
  532.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  533.                         if (obj.state == STATE::START || obj.state == STATE::DELAYEDSTART)
  534.                                 return true;
  535.                         const bool isProbablyBoss = jjObjectPresets[obj.eventID].energy >= 20;
  536.                         const bool objfreeze = obj.freeze != 0;
  537.                         if (bullet is null) {
  538.                                 if (force != 0) {
  539.                                         if (objfreeze) {
  540.                                                 obj.state = obj.oldState;
  541.                                                 obj.unfreeze(1);
  542.                                         }
  543.                                         if ((obj.energy -= 4) <= 0) {
  544.                                                 obj.energy = 0;
  545.                                                 if (!IsMurderer) {
  546.                                                         player.showText("#||||~@@MURDERER", STRING::LARGE);
  547.                                                 }
  548.                                                 die(obj, false, objfreeze);
  549.                                         } else {
  550.                                                 obj.justHit = 5;
  551.                                         }
  552.                                         if (!IsMurderer) {
  553.                                                 IsMurderer = true;
  554.                                                 jjPalette.fill(jjPALCOLOR(), 0.15);
  555.                                                 jjPalette.apply();
  556.                                                 if (!jjMusicLoad("DARKSIDE.MOD")) jjMusicStop();
  557.                                         }
  558.                                         jjSample(obj.xPos, obj.yPos, ButtstompSounds[jjRandom() % ButtstompSounds.length]);
  559.                                         if (force > 0) { //buttstomp or sugar rush
  560.                                                 player.buttstomp = 50; //landing
  561.                                                 player.ySpeed = player.ySpeed / -2 - 8;
  562.                                                 player.yAcc = 0;
  563.                                                 player.extendInvincibility(-70);
  564.                                         } else if (force == -101) { //running into frozen enemy
  565.                                                 player.xAcc = 0;
  566.                                                 player.xSpeed /= -2;
  567.                                                 player.ySpeed = -6;
  568.                                                 player.extendInvincibility(-10);
  569.                                         }
  570.                                         if (jjObjectPresets[obj.eventID].energy > 20)
  571.                                                 player.specialMove = 0;
  572.                                 } else if (!objfreeze)
  573.                                         player.hurt();
  574.                         } else {
  575.                                 if (objfreeze) {
  576.                                         obj.state = obj.oldState;
  577.                                         obj.unfreeze(0);
  578.                                         force += 1;
  579.                                 }
  580.                                 if ((bullet.var[6] & 16) == 0) { //fireball
  581.                                         bullet.state = STATE::EXPLODE;
  582.                                 } else if (isProbablyBoss) {
  583.                                         bullet.state = STATE::EXPLODE;
  584.                                         force *= 2;
  585.                                 }
  586.                                 if ((obj.energy -= force) <= 0) {
  587.                                         if (!IsMurderer) {
  588.                                                 obj.oldState = obj.state;
  589.                                                 obj.state = STATE::DONE;
  590.                                                 obj.playerHandling = HANDLING::SPECIALDONE;
  591.                                                 obj.deactivates = false;
  592.                                                 obj.triggersTNT = false;
  593.                                                 obj.isTarget = false;
  594.                                                 obj.lightType = LIGHT::NONE;
  595.                                                 if (!jjLowDetail)
  596.                                                         jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, 0, CREATOR::OBJECT, Orgasm);
  597.                                                 const uint rand = jjRandom();
  598.                                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(((gender == Gender::Female || (gender == Gender::Random && (rand & 128) == 128)) ? ESJF : ESJM) + (rand % 3)));
  599.                                                 Player.lives += 1;
  600.                                                 removeFromEventMap(obj);
  601.                                         } else
  602.                                                 die(obj, true, objfreeze);
  603.                                 } else {
  604.                                         obj.justHit = 5;
  605.                                 }
  606.                         }
  607.                         if (obj.energy <= 0 && !isProbablyBoss) {
  608.                                 const auto rand = jjRandom();
  609.                                 if (rand & 15 == 0) {
  610.                                         jjOBJ@ pickup = jjObjects[jjAddObject(rand & 16 == 16 ? OBJECT::FASTFIRE : OBJECT::CARROT, obj.xPos, obj.yPos, obj.objectID,CREATOR::OBJECT, BEHAVIOR::FLICKERGEM)];
  611.                                         pickup.playerHandling = HANDLING::DELAYEDPICKUP;
  612.                                         pickup.var[2] = 20;
  613.                                         pickup.counter = 400 - (jjDifficulty * 50);
  614.                                         pickup.light = 1;
  615.                                         pickup.lightType = LIGHT::LASER;
  616.                                 }
  617.                         }
  618.                         return true;
  619.                 }
  620.                 void onBehave(jjOBJ@ obj) {}
  621.                
  622.                 uint8 Layer = 4;
  623.                 void drawBodySprite(const jjOBJ@ obj, float xPos, float yPos, int curFrame, int angle = 0, float scale = 1) const {
  624.                         const SPRITE::Mode mode = obj.state == STATE::DONE ? SPRITE::TINTED : obj.state == STATE::FREEZE ? SPRITE::FROZEN : obj.justHit == 0 ? SPRITE::NORMAL : SPRITE::SINGLECOLOR;
  625.                         if (angle == 0)
  626.                                 jjDrawSpriteFromCurFrame(xPos, yPos, curFrame, obj.direction, mode, 48, Layer);
  627.                         else
  628.                                 jjDrawRotatedSpriteFromCurFrame(xPos, yPos, curFrame, angle, obj.direction * scale, scale, mode, 48, Layer);
  629.                 }
  630.                 void drawHearts(const jjOBJ@ obj, int curFrame = -1) const {
  631.                         if (obj.state == STATE::DONE) {
  632.                                 if (curFrame < 0) curFrame = obj.curFrame;
  633.                                 const float heartsY = obj.yPos + jjAnimFrames[obj.curFrame].hotSpotY - 8;
  634.                                 for (int i = 0; i < 1024; i += 205) {
  635.                                         const int heartAngle = (jjGameTicks << 3) + i;
  636.                                         jjDrawResizedSpriteFromCurFrame(obj.xPos + jjSin(heartAngle) * 13, heartsY + jjCos(heartAngle) * 6, heartFrame, 0.5, 0.5, SPRITE::TINTED, 48, Layer);
  637.                                 }
  638.                         }
  639.                 }
  640.                 void drawMainSprite(const jjOBJ@ obj, int curFrame = -1, int angle = 0) const {
  641.                         if (curFrame < 0) curFrame = obj.curFrame;
  642.                         drawHearts(obj, curFrame);
  643.                         drawBodySprite(obj, obj.xPos, obj.yPos, curFrame, angle);
  644.                 }
  645.                 void onDraw(jjOBJ@ obj) { drawMainSprite(obj); }
  646.                
  647.         }
  648.                 int getParameterAtOrigin(const jjOBJ@ obj, int offset, int length) /*const*/ {
  649.                         return jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, offset, length);
  650.                 }
  651.        
  652.         void removeFromEventMap(jjOBJ@ obj) {
  653.                 obj.eventID = 0; //prevent STATE::DEACTIVATE snafus
  654.                 jjEventSet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, 0);
  655.         }
  656.        
  657.         void Orgasm(jjOBJ@ obj) {
  658.                 if (obj.state == STATE::START) {
  659.                         obj.lightType = LIGHT::RING2;
  660.                         obj.light = 0;
  661.                         obj.state = STATE::EXPLODE;
  662.                 } else if (++obj.light > 31)
  663.                         obj.delete();
  664.                 else
  665.                         jjAddObject(obj.eventID, obj.xOrg + (jjRandom() & 63) - 32, obj.yOrg + (jjRandom() & 63) - 32, 0,CREATOR::OBJECT, LesserOrgasm);
  666.         }
  667.         void LesserOrgasm(jjOBJ@ obj) {
  668.                 if (obj.state == STATE::START) {
  669.                         obj.lightType = LIGHT::RING;
  670.                         obj.light = 0;
  671.                         obj.state = STATE::EXPLODE;
  672.                         if (jjGameTicks & 1 == 1)
  673.                                 jjObjects[jjAddObject(OBJECT::BOUNCEONCE, obj.xPos, obj.yPos)].curAnim = (jjRandom() & 1 == 1) ? (jjAnimSets[ANIM::COMMON].firstAnim) : (jjAnimSets[ANIM::PICKUPS].firstAnim + 41);
  674.                 } else if (++obj.light > 11)
  675.                         obj.delete();
  676.         }
  677.        
  678.         class FatChick : Enemy {
  679.                 FatChick(jjOBJ@ preset) {
  680.                         super(preset, AnimSets::FatChick);
  681.                         gender = Gender::Female;
  682.                 }              
  683.                 void onBehave(jjOBJ@ obj) override {
  684.                         if (obj.counter >= 70*2 - 1 && obj.state == STATE::WALK) {
  685.                                 if (obj.counter++ == 70*2 - 1) {
  686.                                         jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos + 5*obj.direction, obj.yPos - 14, obj.objectID, CREATOR::OBJECT, FatHead)].curFrame = jjAnimations[jjAnimSets[animSetID]];
  687.                                 } else if (obj.counter == 70*3)
  688.                                         obj.counter = 0;
  689.                         } else {
  690.                                 obj.behave(BEHAVIOR::FATCHICK, false);
  691.                         }
  692.                 }
  693.                 void onDraw(jjOBJ@ obj) override {
  694.                         drawMainSprite(obj, (obj.counter >= 70*2) ? jjAnimations[jjAnimSets[ANIM::FATCHK]] + 10 + ((obj.counter >> 2) & 1) : -1);
  695.                 }
  696.         }
  697.         void FatHead(jjOBJ@ obj) {
  698.                 if (obj.state == STATE::START) {
  699.                         obj.direction = jjObjects[obj.creatorID].direction;
  700.                        
  701.                         obj.special = GetAngle(Player.yPos - obj.yPos, obj.xPos - Player.xPos);
  702.                         obj.doesHurt = (jjRandom() & 3) + 6;
  703.                         obj.state = STATE::FLY;
  704.                         obj.playerHandling = HANDLING::ENEMYBULLET;
  705.                         obj.animSpeed = 1;
  706.                 } else if (obj.state == STATE::EXPLODE || ++obj.counter >= (!IsMurderer ? 120 + jjDifficulty * 40 : 255)) {
  707.                         obj.delete();
  708.                 } else {
  709.                         int angle = obj.special;
  710.                         float xPos = obj.xPos, yPos = obj.yPos;
  711.                         const jjANIMFRAME@ myFrame = jjAnimFrames[obj.curFrame];
  712.                         for (int i = 0; i < obj.counter; ++i) {
  713.                                 jjDrawSpriteFromCurFrame(xPos, yPos, obj.curFrame, obj.direction, SPRITE::NORMAL,0, 3);
  714.                                 if (Player.blink == 0 && myFrame.doesCollide(int(xPos), int(yPos), obj.direction, jjAnimFrames[Player.curFrame], int(Player.xPos), int(Player.yPos), Player.direction)) {
  715.                                         Player.objectHit(obj, 0, HANDLING::ENEMYBULLET);
  716.                                         //break;
  717.                                 }
  718.                                 xPos += jjSin(angle) * 2;
  719.                                 yPos += jjCos(angle) * 2;
  720.                                 angle += ((((((i >> 5) + 1) >> 1) & 1) << 1) - 1) * obj.doesHurt * obj.direction;
  721.                         }
  722.                 }
  723.                
  724.         }
  725.        
  726.         void Recolor(jjANIMFRAME@ frame, const array<uint8> &in pattern, const array<uint8> &in replace) {
  727.                 jjPIXELMAP image(frame);
  728.                 for (int x = image.width - 1; x >= 0; --x)
  729.                         for (int y = image.height - 1; y >= 0; --y) {
  730.                                 const uint8 color = image[x,y];
  731.                                 if (pattern.find( color & ~7) >= 0)
  732.                                         image[x,y] = replace[color & 7];
  733.                         }
  734.                 image.save(frame);
  735.         }
  736.         void Recolor(const jjANIMATION@ anim, const array<uint8> &in pattern, const array<uint8> &in replace) {
  737.                 for (uint frameID = 0; frameID < anim.frameCount; ++frameID)
  738.                         Recolor(jjAnimFrames[anim + frameID], pattern, replace);
  739.         }
  740.         array<uint8>@ RecolorReplace(uint8 hue) {
  741.                 return array<uint8> = {hue,hue+1,hue+2,hue+3,hue+4,hue+5,hue+6,hue+7};
  742.         }
  743.        
  744.         class Fencer : Enemy {
  745.                 Fencer(jjOBJ@ preset) {
  746.                         super(preset);
  747.                         gender = Gender::Male;
  748.                         for (uint animID = 0; animID < 2; ++animID) {
  749.                                 Recolor(jjAnimations[jjAnimSets[ANIM::FENCER] + animID], array<uint8>={24,48}, array<uint8>={38,39,79,79,79,79,79,79}); //red or pink to black
  750.                         }
  751.                 }              
  752.                 void onBehave(jjOBJ@ obj) override {
  753.                         if (obj.state == STATE::START) {
  754.                                 obj.doesHurt = getParameterAtOrigin(obj, 0, 2);
  755.                                 switch (obj.doesHurt) {
  756.                                         case 0: //floor
  757.                                                 obj.behave(BEHAVIOR::FENCER, false);
  758.                                                 break;
  759.                                         case 1: {
  760.                                                 int newYPos = int(obj.yPos) - 22;
  761.                                                 while (newYPos > 0 && !jjMaskedPixel(int(obj.xPos), newYPos))
  762.                                                         newYPos -= 2;
  763.                                                 obj.yPos = newYPos + 22;
  764.                                                 obj.state = STATE::DELAYEDSTART;
  765.                                                 break;
  766.                                         }
  767.                                         case 2:
  768.                                         case 3: {
  769.                                                 int newXPos = int(obj.xPos);
  770.                                                 obj.direction = ((int(obj.doesHurt) - 2) << 1) - 1;
  771.                                                 while (!jjMaskedPixel(newXPos, int(obj.yPos)))
  772.                                                         newXPos += (obj.direction << 1);
  773.                                                 obj.xPos = newXPos - (obj.direction << 4);
  774.                                                 obj.state = STATE::STILL;
  775.                                         }
  776.                                 }
  777.                         } else if (obj.doesHurt == 1) { //hiding at ceiling
  778.                                 if (Player.yPos > obj.yPos && abs(Player.xPos - obj.xPos) < 64) {
  779.                                         obj.state = STATE::JUMP;
  780.                                         obj.doesHurt = 0;
  781.                                         obj.curAnim -= 1;
  782.                                         obj.counter = obj.frameID = 0;
  783.                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_UP, 52);
  784.                                         obj.direction = Player.direction;
  785.                                         obj.xSpeed = -1 * obj.direction;
  786.                                         obj.ySpeed = 1;
  787.                                 }
  788.                         } else {
  789.                                 if (obj.state == STATE::JUMP && obj.doesHurt != 0) {
  790.                                         obj.xSpeed = (obj.doesHurt == 2) ? 4 : -4;
  791.                                         obj.doesHurt = 0;
  792.                                 }
  793.                                 obj.behave(BEHAVIOR::FENCER, false);
  794.                                 if (IsMurderer)// || (jjGameTicks & 1) == 1)
  795.                                         obj.behave(BEHAVIOR::FENCER, false);
  796.                         }
  797.                 }
  798.                 void onDraw(jjOBJ@ obj) override {
  799.                         if (obj.state == STATE::DELAYEDSTART) //hiding in the ceiling
  800.                                 return;
  801.                         else if (obj.doesHurt != 0)
  802.                                 drawMainSprite(obj, -1, obj.doesHurt == 2 ? 0x300 : 0x100);
  803.                         else
  804.                                 Enemy::onDraw(obj);
  805.                 }
  806.         }
  807.        
  808.         class FloatLizard : Enemy {
  809.                 Egg egg;
  810.                 FloatLizard(jjOBJ@ preset) {
  811.                         super(preset, AnimSets::Egg);
  812.                         egg.firstAnim = jjAnimSets[animSetID];
  813.                         preset.behavior = jjVOIDFUNCOBJ(behaviorAssigner);
  814.                         preset.energy = 1;
  815.                         gender = Male;
  816.                         Layer = 2;
  817.                         for (uint animID = 0; animID < 5; ++animID) {
  818.                                 Recolor(jjAnimations[jjAnimSets[ANIM::LIZARD] + animID], array<uint8>={40,56}, array<uint8>={38,39,39,39,39,79,79,79}); //orange or yellow to black
  819.                         }
  820.                 }
  821.                 void behaviorAssigner(jjOBJ@ obj) {
  822.                         if (obj.creator != CREATOR::LEVEL || getParameterAtOrigin(obj, 1, 1) == 0) obj.behavior = this; else obj.behavior = egg;
  823.                 }
  824.                
  825.                 void onBehave(jjOBJ@ obj) override {
  826.                         if (obj.state == STATE::START) {
  827.                                 obj.state = getParameterAtOrigin(obj, 0, 1) == 0 ? STATE::SLEEP : STATE::FALL;
  828.                         }
  829.                        
  830.                         const int xDiff = 96 * obj.direction;
  831.                         Player.yPos -= 96; //target ABOVE me
  832.                         Player.xPos += xDiff;
  833.                         obj.behave(BEHAVIOR::FLOATLIZARD, false);
  834.                         Player.yPos += 96;
  835.                         Player.xPos -= xDiff;
  836.                        
  837.                         if (obj.state == STATE::FLY) {
  838.                                 obj.var[1] = 0; //counter to entering STATE::ATTACK
  839.                                 if (obj.yPos < Player.yPos && obj.yPos > Player.yPos - 192 && abs(obj.xPos - Player.xPos) < 24 && !jjMaskedVLine(int(obj.xPos), int(obj.yPos) - 5, 30)) {
  840.                                         obj.state = STATE::FALL;
  841.                                         obj.counter = 0;
  842.                                         obj.frameID = 0;
  843.                                         obj.direction = (obj.xPos > Player.xPos) ? -6 : 6;
  844.                                 }
  845.                         } else if ((obj.state == STATE::WALK || obj.state == STATE::WAIT) && ++obj.counter > 70*4) {
  846.                                 obj.state = STATE::SLEEP; //return to the air
  847.                                 obj.ySpeed = -8;
  848.                                 obj.xSpeed *= 2;
  849.                         } else if (obj.state == STATE::SLEEP) { //just got off the ground
  850.                                 obj.xPos += (obj.xSpeed *= 0.9) + jjSin(jjGameTicks << 3) / 2;
  851.                                 obj.yPos += (obj.ySpeed *= 0.9) + jjCos(jjGameTicks << 3);
  852.                         }
  853.                 }
  854.                 void onDraw(jjOBJ@ obj) override {
  855.                         if (obj.state == STATE::FLY || obj.state == STATE::SLEEP || (obj.state == STATE::DONE && (obj.oldState == STATE::FLY || obj.oldState == STATE::SLEEP)))
  856.                                 jjDrawSprite(obj.xPos, obj.yPos + 4, ANIM::LIZARD, 3, jjGameTicks/3, obj.direction, SPRITE::NORMAL,0, Layer);
  857.                         Enemy::onDraw(obj);
  858.                 }
  859.         }
  860.         class Egg : jjBEHAVIORINTERFACE {
  861.                 uint firstAnim;
  862.                 void onBehave(jjOBJ@ obj) {
  863.                         if (obj.state == STATE::START) {
  864.                                 obj.curFrame = jjAnimations[firstAnim];
  865.                                 obj.xPos += int(jjRandom() & 31) - 15;
  866.                                 obj.putOnGround();
  867.                                 obj.yPos += jjRandom() & 3;
  868.                                 obj.isFreezable = false; //let's just not bother
  869.                                 obj.isTarget = false;
  870.                                 if (jjParameterGet(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, 0, 1) == 0) { //real egg
  871.                                         obj.playerHandling = HANDLING::SPECIAL;
  872.                                         obj.scriptedCollisions = true;
  873.                                         obj.bulletHandling = HANDLING::DETECTBULLET;
  874.                                         obj.counterEnd = 4;
  875.                                         obj.energy = 30;
  876.                                 } else { //false egg
  877.                                         obj.playerHandling = HANDLING::PARTICLE;
  878.                                         obj.counterEnd = (jjRandom() & 2) + 3;
  879.                                 }
  880.                                 obj.state = STATE::WAIT;
  881.                         } else if (obj.state == STATE::DEACTIVATE) {
  882.                                 obj.deactivate();
  883.                         } else if (obj.counterEnd == 4) { //real egg, needing to hatch
  884.                                 if (obj.counter > 0) {
  885.                                         if (++obj.counter >= 210)
  886.                                                 hatch(obj);
  887.                                         else
  888.                                                 obj.curFrame = jjAnimations[firstAnim] + obj.counter / 70;
  889.                                 }
  890.                         }
  891.                 }
  892.                 void onDraw(jjOBJ@ obj) {
  893.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, obj.counterEnd != 4 ? SPRITE::TINTED : SPRITE::NORMAL, 79, obj.counterEnd);
  894.                 }
  895.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  896.                         if (bullet !is null) {
  897.                                 obj.counter += force*70;
  898.                                 bullet.state = STATE::EXPLODE;
  899.                         } else {
  900.                                 if (obj.counter == 0) obj.counter = 1;
  901.                         }
  902.                         return true;
  903.                 }
  904.                 void hatch(jjOBJ@ obj) const {
  905.                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_CANSPS);
  906.                         jjParameterSet(uint(obj.xOrg)>>5, uint(obj.yOrg)>>5, 1, 1, 0); //only raptors now
  907.                         jjOBJ@ blacky = jjObjects[jjAddObject(obj.eventID, obj.xOrg, obj.yOrg, 0, CREATOR::LEVEL)];
  908.                         blacky.xPos = obj.xPos;
  909.                         blacky.yPos = obj.yPos - 24;
  910.                         for (uint i = 0; i < 6; ++i) {
  911.                                 jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos - 24, obj.creatorID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
  912.                                 shard.curAnim = firstAnim + 1 + i;
  913.                                 shard.behavior = BEHAVIOR::SHARD;
  914.                         }
  915.                         obj.delete();
  916.                 }
  917.         }
  918.        
  919.         class TubeTurle : Enemy {
  920.                 TubeTurle(jjOBJ@ preset) {
  921.                         super(preset, AnimSets::TubeTurle);
  922.                         preset.energy = 1;
  923.                 }
  924.                 void onBehave(jjOBJ@ obj) override {
  925.                         ++obj.counter;
  926.                         if (obj.state == STATE::WAIT) {
  927.                                 obj.curFrame = jjAnimations[obj.curAnim] + ((jjGameTicks >> 3) & 3);
  928.                                 const float distanceFromPlayer = abs(Player.xPos - obj.xPos);
  929.                                 if (distanceFromPlayer < 290)
  930.                                         obj.direction = (obj.xPos > Player.xPos) ? -1 : 1;
  931.                                 if ((distanceFromPlayer < 180 || IsMurderer) && obj.counter > 70) {
  932.                                         obj.counter = 0;
  933.                                         obj.state = STATE::ATTACK;
  934.                                 }
  935.                         } else if (obj.state == STATE::ATTACK) {
  936.                                 int frameID = obj.counter / 6;
  937.                                 if (frameID > 12) {
  938.                                         obj.state = STATE::WAIT;
  939.                                         obj.counter = 0;
  940.                                 } else if (frameID > 8)
  941.                                         frameID = 12 - frameID;
  942.                                 else if (frameID > 3) {
  943.                                         frameID = 3;
  944.                                         if (obj.counter % 6 == 5) {
  945.                                                 jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos + 20*obj.direction, obj.yPos + 12, obj.objectID, CREATOR::OBJECT, TurleTube)].curAnim = jjAnimSets[animSetID];
  946.                                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::TURTLE_SPK1TURT + (jjRandom() & 3)));
  947.                                         }
  948.                                 }
  949.                                 obj.curFrame = jjAnimations[obj.curAnim + 1] + frameID;
  950.                         } else {
  951.                                 obj.behave(BEHAVIOR::TUBETURTLE, false);
  952.                                 return;
  953.                         }
  954.                         const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
  955.                         if (!jjMaskedPixel(int(obj.xPos), int(obj.yPos) + frame.hotSpotY - frame.coldSpotY))
  956.                                 obj.yPos += 1;
  957.                 }
  958.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  959.                         if (bullet !is null && obj.state == STATE::FLOAT) {
  960.                                 bullet.state = STATE::EXPLODE;
  961.                                 obj.state = STATE::WAIT;
  962.                                 obj.energy = 4;
  963.                                 obj.curAnim = jjAnimSets[animSetID] + 1;
  964.                                 obj.counter = 45;
  965.                                 obj.yPos -= 20;
  966.                                 jjSample(obj.xPos, obj.yPos, SOUND::TURTLE_TURN);
  967.                                 return true;
  968.                         }
  969.                         return Enemy::onObjectHit(obj, bullet, player, force);
  970.                 }
  971.         }
  972.         void TurleTube(jjOBJ@ obj) {
  973.                 if (obj.state == STATE::START) {
  974.                         obj.xSpeed = jjObjects[obj.creatorID].direction * 5;
  975.                         obj.ySpeed = int(jjRandom() & 7) * -0.5;
  976.                         obj.state = STATE::FLY;
  977.                         obj.playerHandling = HANDLING::ENEMYBULLET;
  978.                         obj.animSpeed = 1;
  979.                         obj.killAnim = jjAnimSets[ANIM::AMMO] + 7;
  980.                 } else if (obj.state == STATE::EXPLODE) {
  981.                         obj.curAnim = obj.killAnim;
  982.                         obj.frameID = 0;
  983.                         obj.behavior = BEHAVIOR::EXPLOSION;
  984.                         obj.playerHandling = HANDLING::EXPLOSION;
  985.                 } else {
  986.                         if (jjMaskedPixel(int(obj.xPos), int(obj.yPos)))
  987.                                 obj.state = STATE::EXPLODE;
  988.                         else {
  989.                                 obj.xPos += obj.xSpeed;
  990.                                 obj.yPos += obj.ySpeed += 0.1;
  991.                                 obj.frameID = jjGameTicks / 3;
  992.                                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.determineCurFrame());
  993.                         }
  994.                 }
  995.         }
  996.         class Fish : Enemy {
  997.                 Fish(jjOBJ@ preset) {
  998.                         super(preset);
  999.                         preset.energy = 1;
  1000.                         preset.isFreezable = false;
  1001.                         preset.isTarget = false;
  1002.                         for (uint animID = 0; animID < 2; ++animID) {
  1003.                                 Recolor(jjAnimations[jjAnimSets[ANIM::FISH] + animID], array<uint8>={32}, RecolorReplace(80));
  1004.                                 Recolor(jjAnimations[jjAnimSets[ANIM::FISH] + animID], array<uint8>={48}, RecolorReplace(64));
  1005.                         }
  1006.                 }
  1007.                 void onBehave(jjOBJ@ obj) override {
  1008.                         if (obj.state == STATE::START)
  1009.                                 obj.state = STATE::DELAYEDSTART;
  1010.                         else if (obj.state == STATE::DEACTIVATE)
  1011.                                 obj.deactivate();
  1012.                         else if (obj.state == STATE::FREEZE)
  1013.                                 obj.behave(BEHAVIOR::FISH, false); //as good as any other for unfreezing
  1014.                         else {
  1015.                                 if (obj.state == STATE::DELAYEDSTART) {
  1016.                                         if (obj.yPos > jjWaterLevel) {
  1017.                                                 obj.state = STATE::FLY;
  1018.                                                 obj.isFreezable = true;
  1019.                                                 obj.isTarget = true;
  1020.                                         }
  1021.                                 } else if (obj.state != STATE::DONE && obj.state != STATE::KILL) {
  1022.                                         if (int(obj.xSpeed) == 0 && int(obj.ySpeed) == 0) {
  1023.                                                 const int divide = IsMurderer ? 1 : 2;
  1024.                                                 const float targetX = obj.xOrg + ((jjRandom() & 255) - 127.5) / divide;
  1025.                                                 const float targetY = obj.yOrg + ((jjRandom() & 255) - 127.5) / divide;
  1026.                                                 obj.xSpeed = (targetX - obj.xPos) / 16 / divide;
  1027.                                                 obj.ySpeed = (targetY - obj.yPos) / 16 / divide;
  1028.                                                 obj.direction = obj.xSpeed >= 0 ? 1 : -1;
  1029.                                         } else {
  1030.                                                 if (jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos)))
  1031.                                                         obj.xSpeed = -obj.xSpeed;
  1032.                                                 else
  1033.                                                         obj.xPos += obj.xSpeed;
  1034.                                                 if (jjMaskedPixel(int(obj.xPos), int(obj.yPos + obj.ySpeed)))
  1035.                                                         obj.ySpeed = -obj.ySpeed;
  1036.                                                 else
  1037.                                                         obj.yPos += obj.ySpeed;
  1038.                                                 obj.xSpeed *= 0.94;
  1039.                                                 obj.ySpeed *= 0.94;
  1040.                                         }
  1041.                                         if (obj.yPos < jjWaterLevel)
  1042.                                                 obj.yPos = jjWaterLevel;
  1043.                                 }
  1044.                                 obj.frameID = jjGameTicks >> 3;
  1045.                                 obj.determineCurFrame();
  1046.                         }
  1047.                 }
  1048.                 void onDraw(jjOBJ@ obj) override {
  1049.                         if (obj.state == STATE::DELAYEDSTART) //yet to be activated by the water
  1050.                                 return;
  1051.                         else
  1052.                                 Enemy::onDraw(obj);
  1053.                 }
  1054.         }
  1055.        
  1056.         class RedBat : Enemy {
  1057.                 RedBat(jjOBJ@ preset) {
  1058.                         super(preset);
  1059.                         preset.energy = 2;
  1060.                         preset.curAnim -= 1;
  1061.                         gender = Gender::Female;
  1062.                         for (uint animID = 0; animID < 1; ++animID) { //lul
  1063.                                 Recolor(jjAnimations[jjAnimSets[ANIM::BAT] + animID], array<uint8>={88}, RecolorReplace(24));
  1064.                         }
  1065.                 }
  1066.                 void onBehave(jjOBJ@ obj) override {
  1067.                         if (obj.state == STATE::START)
  1068.                                 obj.state = STATE::FLY; //don't stick to the ceiling like regular bats
  1069.                         else if (obj.state == STATE::DEACTIVATE || obj.state == STATE::FREEZE)
  1070.                                 obj.behave(BEHAVIOR::BAT, false); //as good as any other for unfreezing
  1071.                         else if (obj.state == STATE::FLY) { //I feel bad about not making any call to jjOBJ::behave in here but there's not a lot of bat code to begin with
  1072.                                 obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::BAT_BATFLY1,obj.doesHurt,43,0);
  1073.  
  1074.                                 const float wantx = Player.xPos;
  1075.                                 const float wanty = Player.yPos - 96;
  1076.                                
  1077.                                 //cut out some code here about making the bat return to its place of origin if the player is too far away
  1078.  
  1079.                                 const float xDiff = int(jjRandom() & 32767) / 10923.6667f;
  1080.                                 const float xTarget = obj.xPos + (wantx<obj.xPos ? -xDiff : xDiff);
  1081.                                 if (!jjMaskedPixel(int(xTarget), int(obj.yPos)))
  1082.                                         obj.xPos = xTarget;
  1083.                                 const float yTarget = obj.yPos + obj.ySpeed;
  1084.                                 if (yTarget <= jjWaterLevel && !jjMaskedPixel(int(obj.xPos), int(yTarget)))
  1085.                                         obj.yPos = yTarget;
  1086.  
  1087.                                 if (++obj.counter > 7) {
  1088.                                         if (wantx<obj.xPos-1)
  1089.                                                 obj.direction=-1;
  1090.                                         else
  1091.                                         if (wantx>obj.xPos+1)
  1092.                                                 obj.direction=1;
  1093.  
  1094.                                         if (wanty < obj.yPos)
  1095.                                                 obj.ySpeed = int(jjRandom() & 32767) / -16384.f;
  1096.                                         else
  1097.                                                 obj.ySpeed = int(jjRandom() & 32767) / 16384.f;
  1098.                                                
  1099.                                         if (++obj.counterEnd > 14) {
  1100.                                                 obj.counterEnd = 0;
  1101.                                                 jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::ELECTROBULLET, obj.xPos, obj.yPos, 0, CREATOR::OBJECT)];
  1102.                                                 bullet.playerHandling = HANDLING::ENEMYBULLET;
  1103.                                                 bullet.xSpeed = (wantx - obj.xPos) / (IsMurderer ? 32.f : 128.f);
  1104.                                                 bullet.ySpeed = 3;
  1105.                                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::AMMO_LASER2 + (jjRandom()&1)));
  1106.                                         }
  1107.  
  1108.                                         obj.counter = 0;
  1109.                                         ++obj.frameID;
  1110.                                         obj.determineCurFrame();
  1111.                                 }
  1112.                         }
  1113.                 }
  1114.         }
  1115.        
  1116.         bool LevelContainsMonkeys, LevelContainsLava;
  1117.         class Monkey : Enemy {
  1118.                 Monkey(jjOBJ@ preset) {
  1119.                         super(preset);
  1120.                         gender = Gender::Male;
  1121.                         if (!LevelContainsMonkeys) {
  1122.                                 LevelContainsMonkeys = true;
  1123.                                 for (uint animID = 0; animID < 7; ++animID) {
  1124.                                         const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::MONKEY] + animID];
  1125.                                         if (animID >= 2) {
  1126.                                                 Recolor(anim, array<uint8>={40}, RecolorReplace(24)); //orange to red
  1127.                                                 Recolor(anim, array<uint8>={64}, RecolorReplace(36)); //brown to orange
  1128.                                         } else {
  1129.                                                 Resize::Resize(anim, 1.75, Resize::Method::Scale2x);
  1130.                                         }
  1131.                                 }
  1132.                         }
  1133.                 }
  1134.                 void onBehave(jjOBJ@ obj) override {
  1135.                         for (uint i = (IsMurderer && jjGameTicks&1==1) ? 2 : 1; i > 0; --i)
  1136.                                 obj.behave(BEHAVIOR::MONKEY, false); //both MONKEY and STANDMONKEY use the same behavior... not very efficient, arjan
  1137.                 }
  1138.         }
  1139.         void DoObjectLoop() {
  1140.                 if (LevelContainsMonkeys && (jjDifficulty > 0 || IsMurderer)) {
  1141.                         for (uint i = jjObjectCount; --i > 0;) {
  1142.                                 jjOBJ@ obj = jjObjects[i];
  1143.                                 if (obj.behavior == BEHAVIOR::MONKEYBULLET) {
  1144.                                         if (obj.state == STATE::EXPLODE) {
  1145.                                                 obj.behavior = BEHAVIOR::RFBULLET;
  1146.                                                 obj.playerHandling = HANDLING::ENEMYBULLET;
  1147.                                                 obj.eventID = OBJECT::RFBULLETPU;
  1148.                                                 obj.curAnim = obj.killAnim;
  1149.                                                 obj.frameID = 0;
  1150.                                                 obj.blast(96*96, false);
  1151.                                         } else {
  1152.                                                 obj.lightType = LIGHT::BRIGHT;
  1153.                                                 obj.light = 6;
  1154.                                         }
  1155.                                 }
  1156.                         }
  1157.                 }
  1158.         }
  1159.        
  1160.         class Muttshroom : Enemy {
  1161.                 uint firstMushroomFrame;
  1162.                 Muttshroom(jjOBJ@ preset) {
  1163.                         super(preset, AnimSets::Muttshroom);
  1164.                         firstMushroomFrame = jjAnimations[jjAnimSets[animSetID]];
  1165.                         for (uint animID = 0; animID < 2; ++animID) {
  1166.                                 const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::DOG] + animID];
  1167.                                 Recolor(anim, array<uint8>={64}, RecolorReplace(24)); //brown to red
  1168.                                 Recolor(anim, array<uint8>={40}, RecolorReplace(62)); //orange to brown
  1169.                         }
  1170.                         if (true) {
  1171.                                 for (uint i = 0; i < 5; ++i) { //copter
  1172.                                         jjANIMFRAME@ frame = jjAnimFrames[firstMushroomFrame + 5 + i];
  1173.                                         jjPIXELMAP image(frame);
  1174.                                         for (int x = image.width - 1; x >= 0; --x)
  1175.                                                 for (int y = image.height - 1; y >= 0; --y)
  1176.                                                         if (image[x,y] != 0)
  1177.                                                                 image[x,y] += 16;
  1178.                                         image.save(frame);
  1179.                                 }
  1180.                         }
  1181.                 }
  1182.                 void onBehave(jjOBJ@ obj) override {
  1183.                         switch (obj.state) {
  1184.                                 case STATE::ROTATE:
  1185.                                         obj.counter += 4;
  1186.                                         obj.xPos = obj.xOrg + jjSin(obj.counter) * 48;
  1187.                                         obj.yPos = obj.yOrg + jjCos(obj.counter) * 48;
  1188.                                         obj.direction = (obj.counter - 256) & 1023 < 512 ? 1 : -1;
  1189.                                         if (Player.blink == 0 && jjAnimFrames[Player.curFrame].doesCollide(int(Player.xPos), int(Player.yPos), Player.direction, jjAnimFrames[firstMushroomFrame+5], int(obj.xPos), int(obj.yPos), 1)) //player collides with copter
  1190.                                                 Player.hurt(2);
  1191.                                         if (obj.counterEnd == 0)
  1192.                                                 obj.counterEnd = jjRandom() & 63;
  1193.                                         else if ((++obj.counterEnd > 200 || IsMurderer) && jjGameTicks & 1 == 1)
  1194.                                                 jjAddObject(obj.eventID, obj.xPos, obj.yPos+20, obj.creatorID); //spores
  1195.                                         break;
  1196.                                 case STATE::FLY: //copter part
  1197.                                         if ((obj.counterEnd -= 3) < 30)
  1198.                                                 obj.delete();
  1199.                                         else {
  1200.                                                 obj.xPos += obj.xSpeed *= 0.97;
  1201.                                                 obj.yPos += obj.ySpeed *= 0.97;
  1202.                                         }
  1203.                                         break;
  1204.                                 case STATE::START:
  1205.                                         if (obj.creatorType == CREATOR::LEVEL) {
  1206.                                                 if (getParameterAtOrigin(obj, 0, 1) == 1) {
  1207.                                                         obj.state = STATE::ROTATE;
  1208.                                                         obj.curFrame = firstMushroomFrame + 3;
  1209.                                                         obj.energy = 3;
  1210.                                                         break;
  1211.                                                 }
  1212.                                                 obj.energy = 2;
  1213.                                         } else {
  1214.                                                 obj.state = STATE::FLY;
  1215.                                                 obj.curFrame = firstMushroomFrame + 4;
  1216.                                                 obj.counterEnd = 255;
  1217.                                                 const auto angle = jjRandom();
  1218.                                                 obj.xSpeed = jjSin(angle) * 4;
  1219.                                                 obj.ySpeed = jjCos(angle) * 4;
  1220.                                                 obj.isTarget = false;
  1221.                                                 obj.isFreezable = false;
  1222.                                                 break;
  1223.                                         }
  1224.                                 default:
  1225.                                         obj.behave(BEHAVIOR::DOGGYDOGG, false);
  1226.                                         if (obj.state == STATE::WALK && (jjRandom() & 127) == 0) {
  1227.                                                 jjSample(obj.xPos, obj.yPos, SOUND::DOG_AGRESSIV);
  1228.                                                 obj.var[0] = -1;
  1229.                                                 obj.var[1] = 255;
  1230.                                                 obj.counter=jjGameTicks&7;      //aligning anim
  1231.                                                 obj.state = STATE::ACTION;
  1232.                                         }
  1233.                         }
  1234.                 }
  1235.                 void onDraw(jjOBJ@ obj) override {
  1236.                         if (obj.state == STATE::FLY) {
  1237.                                 const float scale = obj.counterEnd / 255.f;
  1238.                                 jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, scale,scale, SPRITE::ALPHAMAP, 42);
  1239.                         } else {
  1240.                                 if (obj.curFrame > firstMushroomFrame) {
  1241.                                         const auto realDirection = obj.direction;
  1242.                                         obj.direction = 1;
  1243.                                         drawMainSprite(obj, firstMushroomFrame + 5 + ((obj.counter >> 3) % 5));
  1244.                                         obj.direction = realDirection;
  1245.                                 }
  1246.                                 Enemy::onDraw(obj);
  1247.                                 if (obj.curFrame < firstMushroomFrame) {
  1248.                                         const uint frameID = (jjGameTicks >> 2) & 3;
  1249.                                         const uint mushroomFrame = firstMushroomFrame + (obj.state == STATE::DONE ? 0 : (frameID < 3 ? frameID : 1));
  1250.                                         drawBodySprite(obj, obj.xPos + obj.direction * 13, obj.yPos + (obj.curAnim == jjObjectPresets[obj.eventID].curAnim ? 2 : -5), mushroomFrame);
  1251.                                         drawBodySprite(obj, obj.xPos - obj.direction * 18, obj.yPos - 6, mushroomFrame, 128 * obj.direction, 0.5f);
  1252.                                 }
  1253.                         }
  1254.                 }
  1255.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1256.                         if (obj.state != STATE::FLY || (bullet is null && obj.counterEnd > 50)) {
  1257.                                 if (jjColorDepth >= 16 && player.shieldTime <= 0) {
  1258.                                         if (Player.stoned <= 0)
  1259.                                                 jjSample(Player.xPos, Player.yPos, SOUND::CATERPIL_RIDOE);
  1260.                                         if (Player.stoned < 15)
  1261.                                                 Player.stoned = IsMurderer ? 140 : 55;
  1262.                                 }
  1263.                         }
  1264.                         if (obj.state != STATE::FLY) {
  1265.                                 const bool isFlier = obj.state == STATE::ROTATE;
  1266.                                 Enemy::onObjectHit(obj,bullet,player,force);
  1267.                                 if (!obj.isActive && isFlier) { //murdered
  1268.                                         jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos)].curAnim = jjAnimSets[animSetID] + 1; //lose copter as particle
  1269.                                 }
  1270.                         } else if (bullet is null)
  1271.                                 obj.delete();
  1272.                         return true;
  1273.                 }
  1274.         }
  1275.        
  1276.         class Trogdor : Enemy {
  1277.                 Trogdor(jjOBJ@ preset) {
  1278.                         super(preset, AnimSets::Trogdor);
  1279.                         preset.energy = 3;
  1280.                         preset.curAnim = jjAnimSets[animSetID];
  1281.                         preset.determineCurFrame();
  1282.                         preset.xSpeed = 0.7;
  1283.                         gender = Gender::Male;
  1284.                 }
  1285.                 void onBehave(jjOBJ@ obj) override {
  1286.                         obj.behave(BEHAVIOR::WALKINGENEMY, false);
  1287.                         if (obj.state != STATE::FREEZE && obj.state != STATE::DONE && (jjGameTicks & 3) == 3 && (IsMurderer || (obj.frameID % 7) == 3)) {
  1288.                                 jjSample(obj.xPos, obj.yPos, SOUND::AMMO_FIREGUN2A);
  1289.                                 jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::TOASTERBULLET)];
  1290.                                 bullet.xPos += 40 * obj.direction;
  1291.                                 bullet.yPos += 20;
  1292.                                 bullet.killAnim = bullet.determineCurAnim(ANIM::AMMO, 0, false);
  1293.                                 bullet.playerHandling = HANDLING::ENEMYBULLET;
  1294.                                 bullet.state = STATE::FLY;
  1295.                                 bullet.animSpeed = 1;
  1296.                                 bullet.xSpeed /= 4;
  1297.                                 bullet.xAcc /= 3;
  1298.                                 bullet.yAcc = (int(jjRandom() & 3) - 1) / 64.f; //replicates normal spreading behavior for player-fired toaster
  1299.                         }
  1300.                 }
  1301.         }
  1302.        
  1303.         const uint FrogDistanceFromGround = 18;
  1304.         class Frog : Enemy {
  1305.                 Frog(jjOBJ@ preset) {
  1306.                         super(preset);
  1307.                         preset.energy = 2;
  1308.                         preset.determineCurAnim(ANIM::FROG, 2);
  1309.                         preset.determineCurFrame();
  1310.                 }
  1311.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1312.                         Enemy::onObjectHit(obj,bullet,player,force);
  1313.                         if (obj.energy <= 0) {
  1314.                                 for (uint i = jjObjectCount; --i > 0;) {
  1315.                                         jjOBJ@ obj2 = jjObjects[i];
  1316.                                         if (obj2.eventID == OBJECT::DRAGONFLY && obj2.isActive && obj2.state != STATE::DONE && obj2.creatorID == uint(obj.objectID))
  1317.                                                 obj2.state = STATE::ATTACK;
  1318.                                 }
  1319.                         }
  1320.                         return true;
  1321.                 }
  1322.                 void onBehave(jjOBJ@ obj) override {
  1323.                         if (obj.state == STATE::START) {
  1324.                                 obj.state = STATE::IDLE;
  1325.                                 obj.yPos += jjMaskedTopVLine(int(obj.xPos), int(obj.yPos + FrogDistanceFromGround), 200);
  1326.                                 for (uint i = 0; i < 3; ++i)
  1327.                                         jjAddObject(OBJECT::DRAGONFLY, obj.xPos, obj.yPos, obj.objectID);
  1328.                         } else if (obj.state == STATE::DEACTIVATE) {
  1329.                                 for (uint i = jjObjectCount; --i > 0;) {
  1330.                                         jjOBJ@ obj2 = jjObjects[i];
  1331.                                         if (obj2.eventID == OBJECT::DRAGONFLY && obj2.isActive && obj2.state != STATE::DONE && obj2.creatorID == uint(obj.objectID)) {
  1332.                                                 obj2.delete();
  1333.                                         }
  1334.                                 }
  1335.                                 if (obj.var[0] == 0)
  1336.                                         obj.deactivate();
  1337.                                 else {
  1338.                                         obj.delete();
  1339.                                         removeFromEventMap(obj);
  1340.                                 }
  1341.                         } else if (obj.state == STATE::FREEZE)
  1342.                                 obj.behave(BEHAVIOR::WALKINGENEMY, false);
  1343.                         else if (obj.state != STATE::DONE) {
  1344.                                 const float delta = abs(obj.xPos - Player.xPos);
  1345.                                 if (delta > 16)
  1346.                                         obj.direction = (obj.xPos > Player.xPos) ? -1 : 1;
  1347.                                 if (obj.state != STATE::JUMP && obj.state != STATE::FALL && !jjMaskedHLine(int(obj.xPos) - 12, 24, int(obj.yPos) + FrogDistanceFromGround)) {
  1348.                                         obj.determineCurAnim(ANIM::FROG, 0);
  1349.                                         obj.state = STATE::FALL;
  1350.                                         obj.ySpeed = 0;
  1351.                                 } else if (obj.state == STATE::FIRE) {
  1352.                                         if (++obj.counter >= 14*5) {
  1353.                                                 obj.determineCurAnim(ANIM::FROG, 2);
  1354.                                                 obj.state = STATE::IDLE;
  1355.                                                 obj.counter = 0;
  1356.                                         }
  1357.                                 } else if (obj.state == STATE::IDLE) {
  1358.                                         if (++obj.counter > 80) {
  1359.                                                 if (delta > 32 && delta < 256 && abs(obj.yPos - Player.yPos) < 192) {
  1360.                                                         if (Player.direction == obj.direction) { //looking away from me
  1361.                                                                 obj.determineCurAnim(ANIM::FROG, jjIsTSF ? 12 : 11);
  1362.                                                                 obj.state = STATE::WALK;
  1363.                                                                 obj.xSpeed = 1.5;
  1364.                                                         } else { //looking at me
  1365.                                                                 obj.determineCurAnim(ANIM::FROG, 5);
  1366.                                                                 obj.state = STATE::JUMP;
  1367.                                                                 obj.ySpeed = -3.75;
  1368.                                                                 obj.xSpeed = -2.3;
  1369.                                                                 jjSample(obj.xPos, obj.yPos, SOUND::COMMON_JUMP);
  1370.                                                         }
  1371.                                                 } else if (obj.counter >= 100) {
  1372.                                                         obj.counter = 0;
  1373.                                                         obj.state = STATE::FIRE;
  1374.                                                         obj.determineCurAnim(ANIM::FROG, (jjIsTSF ? 8 : 7) + (jjRandom() % 3));
  1375.                                                         jjSample(obj.xPos, obj.yPos, SOUND::FROG_TONG);
  1376.                                                 }
  1377.                                         }
  1378.                                 } else {
  1379.                                         if (!jjMaskedVLine(int(obj.xPos) + (obj.xSpeed >= 0 ? 19 : -19) * obj.direction, int(obj.yPos) - 10, 4 + FrogDistanceFromGround))
  1380.                                                 obj.xPos += obj.xSpeed * obj.direction;
  1381.                                         else if (obj.state == STATE::WALK) {
  1382.                                                 obj.determineCurAnim(ANIM::FROG, 2);
  1383.                                                 obj.state = STATE::IDLE;
  1384.                                                 obj.counter = 0;
  1385.                                         }
  1386.                                         if (obj.state != STATE::WALK && obj.state != STATE::IDLE) {
  1387.                                                 if ((obj.ySpeed += 0.25) >= 0 && obj.state == STATE::JUMP) {
  1388.                                                         obj.determineCurAnim(ANIM::FROG, 0);
  1389.                                                         obj.state = STATE::FALL;
  1390.                                                 } else if (jjMaskedHLine(int(obj.xPos) - 11, 22, int(obj.yPos += obj.ySpeed) + FrogDistanceFromGround)) {
  1391.                                                         if (obj.xSpeed != 1.5) {
  1392.                                                                 if (obj.state == STATE::FALL) {
  1393.                                                                         obj.determineCurAnim(ANIM::FROG, 2);
  1394.                                                                         obj.counter = 30;
  1395.                                                                         obj.yPos -= FrogDistanceFromGround * 2;
  1396.                                                                         obj.yPos += jjMaskedTopVLine(int(obj.xPos), int(obj.yPos + FrogDistanceFromGround), 200);
  1397.                                                                         obj.state = STATE::IDLE;
  1398.                                                                 }
  1399.                                                         } else {
  1400.                                                                 obj.determineCurAnim(ANIM::FROG, jjIsTSF ? 12 : 11);
  1401.                                                                 obj.state = STATE::WALK;
  1402.                                                         }
  1403.                                                 }
  1404.                                         } else if (delta <= 16) {
  1405.                                                 obj.determineCurAnim(ANIM::FROG, 2);
  1406.                                                 obj.state = STATE::IDLE;
  1407.                                                 obj.counter = 0;
  1408.                                                 obj.xSpeed = 0;
  1409.                                         }
  1410.                                 }
  1411.                                 obj.frameID = (obj.state != STATE::FIRE ? jjGameTicks : obj.counter) / 5 ;
  1412.                                 obj.determineCurFrame();
  1413.                         }
  1414.                 }
  1415.         }
  1416.         class FrogFly : Enemy {
  1417.                 FrogFly(jjOBJ@ preset) {
  1418.                         super(preset);
  1419.                         preset.energy = 1;
  1420.                         preset.deactivates = false;
  1421.                         preset.determineCurAnim(ANIM::DRAGFLY, 0);
  1422.                 }
  1423.                 void onBehave(jjOBJ@ obj) override {
  1424.                         switch (obj.state) {
  1425.                                 case STATE::START:
  1426.                                         if (obj.creatorType == CREATOR::OBJECT) {
  1427.                                                 obj.state = STATE::CIRCLE;
  1428.                                                 obj.counter = jjRandom();
  1429.                                         } else {
  1430.                                                 jjDebug("something went wrong with a frogfly :(");
  1431.                                                 obj.delete();
  1432.                                         }
  1433.                                         break;
  1434.                                 case STATE::DEACTIVATE:
  1435.                                 case STATE::FREEZE:
  1436.                                         obj.behave(BEHAVIOR::DRAGONFLY, false);
  1437.                                         return;
  1438.                                 case STATE::CIRCLE: {
  1439.                                         obj.counter += 5 + (jjRandom() & 7);
  1440.                                         const float targetX = jjObjects[obj.creatorID].xPos + jjSin(obj.counter) * 40, targetY = jjObjects[obj.creatorID].yPos - 45 + jjCos(obj.counter) * 15;
  1441.                                         if (obj.xPos > targetX + 3) {
  1442.                                                 obj.xPos -= 1;
  1443.                                                 obj.direction = -1;
  1444.                                         } else if (obj.xPos < targetX - 3) {
  1445.                                                 obj.xPos += 1;
  1446.                                                 obj.direction = 1;
  1447.                                         }
  1448.                                         if (obj.yPos > targetY + 3) {
  1449.                                                 obj.yPos -= 1;
  1450.                                         } else if (obj.yPos < targetY - 3) {
  1451.                                                 obj.yPos += 1;
  1452.                                         }
  1453.                                         break; }
  1454.                                 case STATE::ATTACK:
  1455.                                         obj.counter += 14;
  1456.                                         if ((obj.counterEnd += 2) == 20) {
  1457.                                                 obj.special = GetAngle(Player.yPos - obj.yPos, obj.xPos - Player.xPos);
  1458.                                                 obj.deactivates = true;
  1459.                                                 obj.direction = jjSin(obj.special) >= 0 ? 1 : -1;
  1460.                                         } else if (obj.counterEnd > 70) {
  1461.                                                 obj.xPos += jjSin(obj.special) * 6;
  1462.                                                 obj.yPos += jjCos(obj.special) * 6;
  1463.                                         }
  1464.                                         break;
  1465.                                 default:
  1466.                                         return;
  1467.                         }
  1468.                         obj.frameID = obj.counter >> 4;
  1469.                         obj.determineCurFrame();
  1470.                         obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::DRAGFLY_BEELOOP,obj.doesHurt, obj.state == STATE::ATTACK ? 63 : 20);
  1471.                 }
  1472.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1473.                         Enemy::onObjectHit(obj,bullet,player,force);
  1474.                         if (obj.energy <= 0) {
  1475.                                 jjObjects[obj.creatorID].var[0] = 1; //has lost a fly; should be deleted instead of deactivated, to avoid letting the player kill more enemies than exist.
  1476.                         }
  1477.                         return true;
  1478.                 }
  1479.         }
  1480.        
  1481.         class MiniBilsy : Enemy {
  1482.                 MiniBilsy(jjOBJ@ preset) {
  1483.                         super(preset);
  1484.                         preset.energy = 4;
  1485.                         preset.isFreezable = false;
  1486.                         preset.isTarget = false;
  1487.                         gender = Gender::Male;
  1488.                 }
  1489.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
  1490.                         if (obj.state == STATE::DELAYEDSTART) {
  1491.                                 if (bullet is null) {
  1492.                                         obj.isFreezable = true;
  1493.                                         obj.isTarget = true;
  1494.                                         obj.state = STATE::FADEIN;
  1495.                                         obj.determineCurAnim(ANIM::BILSBOSS, 1);
  1496.                                         obj.determineCurFrame();
  1497.                                         obj.putOnGround(true);
  1498.                                         obj.direction = player.direction;
  1499.                                         obj.xPos -= obj.direction * 96;
  1500.                                         jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_THUNDER);
  1501.                                 }
  1502.                         } else
  1503.                                 Enemy::onObjectHit(obj,bullet,player,force);
  1504.                         return true;
  1505.                 }
  1506.                 void onBehave(jjOBJ@ obj) override {
  1507.                         switch (obj.state) {
  1508.                         case STATE::START:
  1509.                                 obj.state = STATE::DELAYEDSTART;
  1510.                                 break;
  1511.                         case STATE::FREEZE:
  1512.                         case STATE::DEACTIVATE:
  1513.                                 obj.behave(BEHAVIOR::TUBETURTLE);
  1514.                                 break;
  1515.                         case STATE::IDLE:
  1516.                                 obj.frameID = (jjGameTicks >> 2) & 7;
  1517.                                 obj.determineCurFrame();
  1518.                                 switch (jjRandom() & 127) {
  1519.                                         case 0:
  1520.                                                 obj.state = STATE::ATTACK;
  1521.                                                 obj.determineCurAnim(ANIM::BILSBOSS, 0);
  1522.                                                 obj.counter = 0;
  1523.                                                 break;
  1524.                                         case 1: //teleport
  1525.                                         case 2:
  1526.                                         case 3:
  1527.                                                 obj.state = STATE::FADEOUT;
  1528.                                                 obj.determineCurAnim(ANIM::BILSBOSS, 2);
  1529.                                                 obj.counter = 0;
  1530.                                                 if (IsMurderer) { //invincible while transportalizing
  1531.                                                         obj.playerHandling = HANDLING::PARTICLE;
  1532.                                                 } {
  1533.                                                         obj.xAcc = Player.xPos;
  1534.                                                         obj.yAcc = Player.yPos;
  1535.                                                 }
  1536.                                                 break;
  1537.                                 }
  1538.                                 break;
  1539.                         case STATE::FADEOUT:
  1540.                                 if (++obj.counter < 68) {
  1541.                                         obj.frameID = obj.counter >> 2;
  1542.                                         obj.determineCurFrame();
  1543.                                 } else {
  1544.                                         obj.frameID = obj.counter = 0;
  1545.                                         obj.state = STATE::FADEIN;
  1546.                                         jjSample(obj.xPos, obj.yPos, SOUND::BILSBOSS_BILLAPPEAR);
  1547.                                         obj.determineCurAnim(ANIM::BILSBOSS, 1);
  1548.                                         obj.xPos = obj.xAcc;
  1549.                                         obj.yPos = obj.yAcc - 32;
  1550.                                         obj.direction = (Player.xPos < obj.xPos) ? -1 : 1;
  1551.                                         obj.determineCurFrame();
  1552.                                         obj.putOnGround(true);
  1553.                                         return;
  1554.                                 }
  1555.                                 break;
  1556.                         case STATE::FADEIN:
  1557.                                 if (++obj.counter < 40) {
  1558.                                         obj.frameID = obj.counter >> 1;
  1559.                                         obj.determineCurFrame();
  1560.                                 } else {
  1561.                                         obj.frameID = obj.counter = 0;
  1562.                                         obj.determineCurAnim(ANIM::BILSBOSS, 4);
  1563.                                         obj.state = STATE::IDLE;
  1564.                                         obj.playerHandling = HANDLING::SPECIAL;
  1565.                                 }
  1566.                                 break;
  1567.                         case STATE::ATTACK:
  1568.                                 if (++obj.counter < 36) {
  1569.                                         obj.frameID = obj.counter >> 1;
  1570.                                         obj.determineCurFrame();
  1571.                                         if (obj.counter == 32) {
  1572.                                                 jjOBJ@ bullet = jjObjects[obj.fireBullet(OBJECT::FIREBALLBULLET)];
  1573.                                                 bullet.direction = obj.direction;
  1574.                                                 bullet.xAcc = abs(bullet.xAcc) * bullet.direction;
  1575.                                                 bullet.xSpeed = abs(bullet.xSpeed) * bullet.direction / 3;
  1576.                                                 bullet.state = STATE::FLY;
  1577.                                                 bullet.playerHandling = HANDLING::ENEMYBULLET;
  1578.                                                 bullet.animSpeed = IsMurderer ? 2 : 1;
  1579.                                                 bullet.determineCurAnim(ANIM::BILSBOSS, 3);
  1580.                                         }
  1581.                                 } else {
  1582.                                         obj.frameID = obj.counter = 0;
  1583.                                         obj.determineCurAnim(ANIM::BILSBOSS, 4);
  1584.                                         obj.state = STATE::IDLE;
  1585.                                 }
  1586.                                 break;
  1587.                         }
  1588.                 }
  1589.                 void onDraw(jjOBJ@ obj) override {
  1590.                         if (obj.state != STATE::DELAYEDSTART)
  1591.                                 Enemy::onDraw(obj);
  1592.                         //else //just for testing
  1593.                         //      jjDrawSpriteFromCurFrame(obj.xPos,obj.yPos,obj.curFrame,obj.direction,SPRITE::TRANSLUCENT);
  1594.                 }
  1595.         }
  1596.        
  1597.         abstract class SharedSucker : Enemy {
  1598.                 SharedSucker(jjOBJ@ preset) { super(preset); }
  1599.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  1600.                         if (obj.age < 0) {
  1601.                                 if (bullet is null)
  1602.                                         player.hurt();
  1603.                                 else
  1604.                                         if (!bullet.ricochet()) bullet.state = STATE::EXPLODE;
  1605.                                 return true;
  1606.                         }
  1607.                         return Enemy::onObjectHit(obj,bullet,player,force);
  1608.                 }
  1609.                 void onDraw(jjOBJ@ obj) override {
  1610.                         if (obj.state == STATE::DONE || obj.age >= 0 || jjGameTicks & 1 == 1)
  1611.                                 Enemy::onDraw(obj);
  1612.                         else
  1613.                                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLEHUE, 80);
  1614.                 }
  1615.                 void makeSparks(jjOBJ@ obj, bool reducedY) {
  1616.                         obj.lightType = LIGHT::BRIGHT;
  1617.                         obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::COMMON_SHIELD_ELEC,obj.doesHurt);
  1618.                         if (jjGameTicks & 3 == 0) {
  1619.                                 jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, obj.xPos, obj.yPos, obj.objectID)];
  1620.                                 spark.determineCurAnim(ANIM::PICKUPS, 85);
  1621.                                 spark.animSpeed = 1;
  1622.                                 spark.playerHandling = HANDLING::ENEMYBULLET;
  1623.                                 spark.xSpeed /= 7;
  1624.                                 if (reducedY)
  1625.                                         spark.ySpeed /= 2;
  1626.                                 else
  1627.                                         spark.ySpeed = -2.5;
  1628.                                 spark.lightType = LIGHT::POINT2;
  1629.                         }
  1630.                 }
  1631.         }
  1632.         class FloatSucker : SharedSucker {
  1633.                 FloatSucker(jjOBJ@ preset) {
  1634.                         super(preset);
  1635.                         preset.energy = 1;
  1636.                         preset.age = 40;
  1637.                         preset.light = 13;
  1638.                         Recolor(jjAnimations[jjAnimSets[ANIM::SUCKER] + 4], array<uint8>={24}, RecolorReplace(40)); //red to orange
  1639.                         Recolor(jjAnimations[jjAnimSets[ANIM::SUCKER] + 4], array<uint8>={80}, RecolorReplace(24)); //green to red
  1640.                 }
  1641.                 void onBehave(jjOBJ@ obj) override {
  1642.                         if (obj.state != STATE::DONE) {
  1643.                                 obj.behave(BEHAVIOR::FLOATSUCKER, false);
  1644.                                 if (obj.freeze == 0) {
  1645.                                         if (--obj.age == -175) {
  1646.                                                 obj.age = (IsMurderer ? 75 : 200) + (jjRandom() & 31);
  1647.                                                 obj.lightType = LIGHT::NONE;
  1648.                                         } else if (obj.age < 0) {
  1649.                                                 obj.behave(BEHAVIOR::FLOATSUCKER, false);
  1650.                                                 makeSparks(obj, true);
  1651.                                         }
  1652.                                 }
  1653.                         }
  1654.                 }
  1655.         }
  1656.        
  1657.         class Sucker : SharedSucker {
  1658.                 Sucker(jjOBJ@ preset) {
  1659.                         super(preset);
  1660.                         preset.energy = 2;
  1661.                         preset.light = 13;
  1662.                         const auto@ anim = jjAnimations[jjAnimSets[ANIM::SUCKER] + 6];
  1663.                         Recolor(anim, array<uint8>={24}, RecolorReplace(40)); //red to orange
  1664.                         Recolor(anim, array<uint8>={80}, RecolorReplace(24)); //green to red
  1665.                 }
  1666.                 void onBehave(jjOBJ@ obj) override {
  1667.                         if (obj.state != STATE::DONE) {
  1668.                                 obj.behave(BEHAVIOR::SUCKER, false);
  1669.                                 if (obj.freeze == 0) {
  1670.                                         obj.age = ((obj.direction == 1) == (obj.xPos < Player.xPos)) ? 1 : -1;
  1671.                                         if (obj.age < 0)
  1672.                                                 makeSparks(obj, false);
  1673.                                         else
  1674.                                                 obj.lightType = LIGHT::NONE;
  1675.                                 }
  1676.                         }
  1677.                 }
  1678.         }
  1679.        
  1680.         class Labrat : Enemy {
  1681.                 Labrat(jjOBJ@ preset) {
  1682.                         super(preset);
  1683.                         Layer = 3;
  1684.                         preset.energy = 1;
  1685.                         for (uint i = 1; i < 3; ++i) {
  1686.                                 Recolor(jjAnimations[jjAnimSets[ANIM::LABRAT] + i], array<uint8>={48}, RecolorReplace(32));
  1687.                                 Recolor(jjAnimations[jjAnimSets[ANIM::LABRAT] + i], array<uint8>={72}, RecolorReplace(80));
  1688.                         }
  1689.                 }
  1690.                 void onBehave(jjOBJ@ obj) override {
  1691.                         if (obj.state == STATE::DONE)
  1692.                                 return;
  1693.                         else if (obj.state == STATE::FREEZE || obj.state == STATE::DEACTIVATE)
  1694.                                 obj.behave(BEHAVIOR::LABRAT, false);
  1695.                         else if (obj.state == STATE::START)
  1696.                                 obj.state = STATE::WALK;
  1697.                         else {
  1698.                                 const auto xDelta = obj.xPos - Player.xPos;
  1699.                                 const auto yDelta = obj.yPos - Player.yPos - 8;
  1700.                                 const jjOBJ@ preset = jjObjectPresets[OBJECT::LABRAT];
  1701.                                 if (abs(xDelta) > 224 || abs(yDelta) > 160) {
  1702.                                         obj.curAnim = preset.curAnim - 1;
  1703.                                 } else {
  1704.                                         obj.curAnim = preset.curAnim;
  1705.                                         bool moved = false;
  1706.                                         if (abs(xDelta) > 64)
  1707.                                                 obj.direction = (xDelta < 0) ? 1 : -1;
  1708.                                         if (yDelta > 0) {
  1709.                                                 if (jjMaskedHLine(int(obj.xPos) - 30, 60, int(obj.yPos) + 7)) {
  1710.                                                         obj.yPos -= 0.75;
  1711.                                                         moved = true;
  1712.                                                 }
  1713.                                         } else {
  1714.                                                 if (jjMaskedHLine(int(obj.xPos) - 30, 60, int(obj.yPos) + 18)) {
  1715.                                                         obj.yPos += 0.75;
  1716.                                                         moved = true;
  1717.                                                 }
  1718.                                         }
  1719.                                         if (obj.direction == -1) {
  1720.                                                 if (jjMaskedVLine(int(obj.xPos) - 30, int(obj.yPos) + 7, 20)) {
  1721.                                                         obj.xPos -= 1.25;
  1722.                                                         moved = true;
  1723.                                                 }
  1724.                                         } else {
  1725.                                                 if (jjMaskedVLine(int(obj.xPos) + 30, int(obj.yPos) + 7, 20)) {
  1726.                                                         obj.xPos += 1.25;
  1727.                                                         moved = true;
  1728.                                                 }
  1729.                                         }
  1730.                                         if (!moved)
  1731.                                                 obj.curAnim -= 1;
  1732.                                 }
  1733.                                
  1734.                                 obj.frameID = jjGameTicks >> 3;
  1735.                                 obj.determineCurFrame();
  1736.                                
  1737.                                 uint rand = jjRandom();
  1738.                                 if ((rand & 127) == 0) {
  1739.                                         switch ((rand >>= 7) &= 3) {
  1740.                                                 case 0:
  1741.                                                 case 1:
  1742.                                                 case 2:
  1743.                                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::LABRAT_MOUSE1 + rand));
  1744.                                                         break;
  1745.                                         }
  1746.                                 }
  1747.                         }
  1748.                 }
  1749.         }
  1750.        
  1751.         class Phoenix : Enemy {
  1752.                 Phoenix(jjOBJ@ preset) {
  1753.                         super(preset);
  1754.                         preset.energy = 3;
  1755.                         gender = Gender::Male;
  1756.                         for (uint i = 0; i < 3; ++i) {
  1757.                                 Recolor(jjAnimations[jjAnimSets[ANIM::RAVEN] + i], array<uint8>={24}, RecolorReplace(88));
  1758.                                 Recolor(jjAnimations[jjAnimSets[ANIM::RAVEN] + i], array<uint8>={72}, array<uint8>={0,0,0,0,46,44,42,41});
  1759.                         }
  1760.                 }
  1761.                 void onBehave(jjOBJ@ obj) override {
  1762.                         if (obj.state != STATE::DONE) {
  1763.                                 const auto oldDirection = obj.direction;
  1764.                                 for (uint i = (obj.state == STATE::ATTACK) ? 3 : IsMurderer ? 2 : 1; i > 0; --i)
  1765.                                         obj.behave(BEHAVIOR::RAVEN, false);
  1766.                                 if (oldDirection != obj.direction) {
  1767.                                         jjSample(obj.xPos, obj.yPos, SOUND::AMMO_FIREGUN2A);
  1768.                                         obj.direction *= -1;
  1769.                                         for (int i = 0; i < 3; ++i) {
  1770.                                                 jjOBJ@ bullet = jjObjects[jjAddObject(OBJECT::TOASTERBULLET, obj.xPos + i * 15 * oldDirection, obj.yPos + (i-1) * 10)];
  1771.                                                 bullet.playerHandling = HANDLING::ENEMYBULLET;
  1772.                                                 bullet.state = STATE::FLY;
  1773.                                                 bullet.animSpeed = 1;
  1774.                                                 bullet.xSpeed /= 3;
  1775.                                                 bullet.xAcc /= 2;
  1776.                                                 bullet.yAcc = (int(jjRandom() & 3) - 1) / 64.f; //replicates normal spreading behavior for player-fired toaster
  1777.                                         }
  1778.                                         obj.direction *= -1;
  1779.                                 }
  1780.                                 if (obj.state == STATE::STOP)
  1781.                                         obj.state = STATE::IDLE; //never return to origin place
  1782.                         }
  1783.                 }
  1784.                 void onDraw(jjOBJ@ obj) override {
  1785.                         if (obj.state != STATE::ATTACK)
  1786.                                 Enemy::onDraw(obj);
  1787.                         else
  1788.                                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLEHUE, 40);
  1789.                 }
  1790.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  1791.                         if (obj.state == STATE::ATTACK) {
  1792.                                 if (bullet is null)
  1793.                                         player.hurt(IsMurderer ? 2 : 1);
  1794.                                 return true;
  1795.                         }
  1796.                         return Enemy::onObjectHit(obj,bullet,player,force);
  1797.                 }
  1798.         }
  1799.        
  1800.         class Demon : Enemy {
  1801.                 Demon(jjOBJ@ preset) {
  1802.                         super(preset);
  1803.                         for (uint i = 0; i < 4; ++i) {
  1804.                                 Recolor(jjAnimations[jjAnimSets[ANIM::DEMON] + i], array<uint8>={24}, RecolorReplace(48));
  1805.                                 Recolor(jjAnimations[jjAnimSets[ANIM::DEMON] + i], array<uint8>={64,80}, RecolorReplace(72));
  1806.                         }
  1807.                 }
  1808.                 void onBehave(jjOBJ@ obj) override {
  1809.                         obj.special = (obj.state == STATE::IDLE) ? obj.counter : 0;
  1810.                        
  1811.                         if (obj.state != STATE::DONE) {
  1812.                                 if (obj.state == STATE::START) {
  1813.                                         obj.isBlastable = getParameterAtOrigin(obj, -2, 1) == 1;
  1814.                                         obj.lightType = LIGHT::NONE;
  1815.                                 }
  1816.                                 if (obj.special > 140) obj.special = 140;
  1817.                                 else if (obj.state == STATE::WALK && abs(obj.direction) == 1)
  1818.                                         obj.direction *= 2;
  1819.                                 if (!obj.isBlastable) { //rightside up
  1820.                                         obj.behave(BEHAVIOR::DEMON, false);
  1821.                                 } else { //upside down
  1822.                                         obj.yPos -= 64;
  1823.                                         Player.yPos -= 64;
  1824.                                         obj.behave(BEHAVIOR::DEMON, false);
  1825.                                         obj.yPos += 64;
  1826.                                         Player.yPos += 64;
  1827.                                 }
  1828.                                 if (obj.special != 0 && obj.state == STATE::ATTACK) {
  1829.                                         FireLoop(obj, true);
  1830.                                 }
  1831.                         }
  1832.                 }
  1833.                 void onDraw(jjOBJ@ obj) override {
  1834.                         if (obj.isBlastable) obj.direction ^= SPRITE::FLIPV;
  1835.                         Enemy::onDraw(obj);
  1836.                         if (obj.isBlastable) obj.direction ^= SPRITE::FLIPV;
  1837.                         FireLoop(obj, false);
  1838.                 }
  1839.                 void FireLoop(const jjOBJ@ obj, bool attack) const {
  1840.                         const int fireFrame = jjAnimations[jjAnimSets[ANIM::AMMO] + 13] + ((jjGameTicks >> 2) % 7);
  1841.                         for (int i = 0; i + 16 < obj.special; i += (IsMurderer ? 16 : 32)) {
  1842.                                 const int angle = (i + jjGameTicks) << 3;
  1843.                                 const int distance = (200 - obj.special) / 2;
  1844.                                 const float xPos = obj.xPos + jjSin(angle) * distance;
  1845.                                 const float yPos = obj.yPos + jjCos(angle) * distance;
  1846.                                 if (attack) {
  1847.                                         jjOBJ@ spark = jjObjects[jjAddObject(OBJECT::SHARD, xPos, yPos, obj.objectID)];
  1848.                                         spark.determineCurAnim(ANIM::AMMO, 13);
  1849.                                         spark.animSpeed = 1;
  1850.                                         spark.playerHandling = HANDLING::ENEMYBULLET;
  1851.                                         spark.xSpeed /= 2;
  1852.                                         spark.lightType = LIGHT::RING2;
  1853.                                         spark.light = 2;
  1854.                                 } else {
  1855.                                         jjDrawSpriteFromCurFrame(xPos, yPos, fireFrame);
  1856.                                         if (jjAnimFrames[Player.curFrame].doesCollide(int(Player.xPos), int(Player.yPos), Player.direction, jjAnimFrames[fireFrame], int(xPos), int(yPos), 1))
  1857.                                                 Player.hurt(IsMurderer ? 2 : 1);
  1858.                                         for (uint j = jjObjectCount; --j != 0;) {
  1859.                                                 jjOBJ@ bullet = jjObjects[j];
  1860.                                                 if (bullet.playerHandling == HANDLING::PLAYERBULLET && bullet.freeze == 0 && bullet.isActive && jjAnimFrames[bullet.curFrame].doesCollide(int(bullet.xPos), int(bullet.yPos), bullet.direction, jjAnimFrames[fireFrame], int(xPos), int(yPos), 1)) {
  1861.                                                         bullet.particlePixelExplosion(1);
  1862.                                                         bullet.delete();
  1863.                                                 }
  1864.                                         }
  1865.                                 }
  1866.                         }
  1867.                 }
  1868.         }
  1869.        
  1870.         class Duet : Enemy {
  1871.                 Duet(jjOBJ@ preset) {
  1872.                         super(preset, AnimSets::Duet);
  1873.                         Layer = 3;
  1874.                         preset.isFreezable = false; //splitness
  1875.                         preset.special = jjAnimations[jjAnimSets[animSetID]];
  1876.                         preset.lightType = LIGHT::BRIGHT;
  1877.                         preset.light = 16;
  1878.                         preset.direction = 1;
  1879.                 }
  1880.                 void onBehave(jjOBJ@ obj) override {
  1881.                         if (obj.state == STATE::DONE)
  1882.                                 return;
  1883.                         if (obj.state == STATE::START || obj.state == STATE::FREEZE || obj.state == STATE::DEACTIVATE) {
  1884.                                 if (obj.state == STATE::START)
  1885.                                         obj.xAcc = float(jjRandom() & 1023);
  1886.                                 obj.behave(BEHAVIOR::SPARK, false);
  1887.                                 return;
  1888.                         } //else...
  1889.                         obj.behave(BEHAVIOR::SPARK, false);
  1890.                         if (!obj.isFreezable) { //not yet split
  1891.                                 obj.doesHurt = jjSampleLooped(obj.xPos,obj.yPos,SOUND::COMMON_SHIELD_ELEC,obj.doesHurt);
  1892.                                 obj.xAcc += (obj.xSpeed) * (IsMurderer ? 7 : 5) + obj.direction; //angle
  1893.                                 const int angle = int(obj.xAcc);
  1894.                                 for (int i = 0; i < 1024; i += 512) {
  1895.                                         const float xPos = obj.xPos + jjSin(angle+i) * 50, yPos = obj.yPos + jjCos(angle+i) * 50;
  1896.                                         const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
  1897.                                         if (jjAnimFrames[Player.curFrame].doesCollide(int(Player.xPos), int(Player.yPos), Player.direction, frame, int(xPos), int(yPos), 1)) {
  1898.                                                 const auto force = Player.getObjectHitForce();
  1899.                                                 if (force == 0)
  1900.                                                         Player.hurt(1);
  1901.                                                 else
  1902.                                                         split(obj, xPos, yPos, null, force);
  1903.                                         }
  1904.                                         for (uint j = jjObjectCount; --j != 0;) {
  1905.                                                 jjOBJ@ bullet = jjObjects[j];
  1906.                                                 if (bullet.playerHandling == HANDLING::PLAYERBULLET && bullet.freeze == 0 && bullet.isActive && jjAnimFrames[bullet.curFrame].doesCollide(int(bullet.xPos), int(bullet.yPos), bullet.direction, frame, int(xPos), int(yPos), 1)) {
  1907.                                                         split(obj, xPos, yPos, bullet, 0);
  1908.                                                 }
  1909.                                         }
  1910.                                         if (obj.isFreezable) { //got split
  1911.                                                 i ^= 512;
  1912.                                                 obj.xPos += jjSin(angle+i) * 50;
  1913.                                                 obj.yPos += jjCos(angle+i) * 50;
  1914.                                                 obj.lightType = LIGHT::NONE;
  1915.                                                 break;
  1916.                                         }
  1917.                                 }
  1918.                         }
  1919.                 }
  1920.                 void split(jjOBJ@ obj, float xPos, float yPos, jjOBJ@ bullet, int force) {
  1921.                         obj.isFreezable = true;
  1922.                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_SHLDOF3);
  1923.                         jjOBJ@ spark2 = jjObjects[jjAddObject(obj.eventID, xPos, yPos, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
  1924.                         removeFromEventMap(obj); //in case it deactivates, so it can't spawn a second spark more than once
  1925.                         spark2.isFreezable = true;
  1926.                         spark2.direction = obj.direction;
  1927.                         spark2.curFrame = obj.curFrame; //why not
  1928.                         spark2.behavior = this;
  1929.                         if (bullet !is null)
  1930.                                 bullet.objectHit(spark2, spark2.playerHandling);
  1931.                         else
  1932.                                 Player.objectHit(spark2, force, spark2.playerHandling);
  1933.                 }
  1934.                 void onDraw(jjOBJ@ obj) override {
  1935.                         if (!obj.isFreezable) {
  1936.                                 int angle = int(obj.xAcc);
  1937.                                 jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.special, angle + 256, int(jjRandom() & 1) * 2 - 1, int(jjRandom() & 1) * 2 - 1, SPRITE::TRANSLUCENT,0, Layer);
  1938.                                 for (int i = 0; i < 1024; i += 512) {
  1939.                                         drawBodySprite(obj, obj.xPos + jjSin(angle+i) * 50, obj.yPos + jjCos(angle+i) * 50, obj.curFrame);
  1940.                                 }
  1941.                         } else
  1942.                                 Enemy::onDraw(obj);
  1943.                 }
  1944.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  1945.                         if (bullet !is null && !obj.isFreezable) {
  1946.                                 bullet.particlePixelExplosion(1);
  1947.                                 bullet.delete();
  1948.                                 return true;
  1949.                         }
  1950.                         return Enemy::onObjectHit(obj, bullet, player, force);
  1951.                 }
  1952.         }
  1953.        
  1954.         class Hatter : Enemy {
  1955.                 Hatter(jjOBJ@ preset) {
  1956.                         super(preset);
  1957.                         for (uint i = 0; i < 3; ++i) {
  1958.                                 Recolor(jjAnimations[jjAnimSets[ANIM::HATTER] + 2 + i], array<uint8>={80}, RecolorReplace(120));
  1959.                                 Recolor(jjAnimations[jjAnimSets[ANIM::HATTER] + 2 + i], array<uint8>={32}, RecolorReplace(48));
  1960.                         }
  1961.                 }
  1962.                 void onBehave(jjOBJ@ obj) override {
  1963.                         if (obj.state == STATE::TURN) {
  1964.                                 if (--obj.counterEnd == 0)
  1965.                                         obj.state = STATE::WALK;
  1966.                                 if (obj.counterEnd & 7 == 0) {
  1967.                                         jjSample(obj.xPos, obj.yPos, SOUND::HATTER_PTOEI, 128)