Downloads containing Jazz1Enemies v05.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Jazz of the JungleFeatured Download Violet CLM Single player 8.9 Download file
Jazz 1 Enemies Violet CLM Other N/A Download file

File preview

  1. /*************************
  2.  
  3.                                                                                 Jazz1Enemies v05.asc
  4.                                                                                 version 0.5
  5.  
  6. API:
  7.  
  8.  
  9.  
  10.         Jazz1::Enemy@ Jazz1::MakeEnemy(uint8 eventID, Jazz1::Enemies enemyID, bool useTrueColor = false, float scale = 1.f, uint resizeMethod = Resize::Method::NearestNeighbor)
  11.                 This is the main function and is intended to be called somewhere in the onLevelLoad hook. Its purpose is to convert one of the events in the level, as specified by the eventID argument, to be an enemy from Jazz 1 instead of whatever it originally was. For example:
  12.                
  13.                         Jazz1::MakeEnemy(OBJECT::HELMUT, Jazz1::Enemies::Medivo_Helmut);
  14.                        
  15.                 This function modifies various properties of jjObjectPresets[eventID], in particular jjOBJ::behavior, which is set to a new jjBEHAVIORINTERFACE instance. The value of the "eventID" argument is totally up to you (although using an OBJECT::Object constant is recommended); possible values for the "enemyID" argument are listed shortly below this comment in the "Enemies" enum, starting with "Diamondus_BumblingBee".
  16.                        
  17.                 The third argument, "useTrueColor", lets you choose which color model to use for drawing this particular enemy:
  18.                         false (default):
  19.                                 The enemy will be limited to using colors that appear in the standard sprite palette. It will look the same in both 8-bit and 16-bit color, and it will be recolored to fit the atmosphere in tilesets with noticeably altered sprite palettes, e.g. Swamps or Glowee. However, it may not look exactly like the original Jazz 1 sprites, depending on how closely the original colors were or were not similar to those available in Jazz 2's standard sprite palette.
  20.                         true:
  21.                                 In 8-bit color this option will have no effect, but in 16-bit color the enemy will be drawn in an approximation of palette-independent 24-bit color mode. It should look much closer, if not identical, to the original Jazz 1 sprites; however, it may therefore not match the atmosphere of the tileset, and if you plan to make any calls to jjPAL::apply() later, you should use the TrueColor::EnableCaching function (see TrueColor v13.asc's documentation for details).
  22.                                
  23.                 Finally there are two more arguments, "scale" and "resizeMethod", for in case you want to make the Jazz 1 enemy sprites somewhat larger to better match the other sprites around them. The "scale" argument should be self-explanatory and defaults to 1.f (no resizing at all); see Resize v11.asc's documentation for a list of options for the "resizeMethod" argument. The object and/or its bullets may also be instructed to move faster or slower to better match larger or smaller sprites, using the SetEnemySpeedsAreScalable and SetBulletSpeedsAreScalable methods described below.
  24.                
  25.                 That one function call, once per enemy you want to add to your level, is all you need to do to use this library, besides the "#include Jazz1Enemies v05.asc" line (see the "Packaging Instructions" section below for more details). There are also some options available to you, however. Jazz1::MakeEnemy returns a handle to an object of class Jazz1::Enemy, which has a number of methods allowing you to slightly edit how that enemy behaves. Each of those methods returns another handle to the same object, allowing you to chain multiple method calls on the same line as the original Jazz1::MakeEnemy call, e.g.:
  26.                
  27.                         Jazz1::MakeEnemy(OBJECT::NORMTURTLE, Jazz1::Enemies::Diamondus_TurtleGoon).SetDirection(Jazz1::Directions::Random).SetSpeed(6);
  28.                        
  29.                 Many of the object methods are available to certain enemies but not others; for example, SetFireDelay is not generally useful for enemies that do not fire any bullets. If you make such an inappropriate method call, you will see a warning to this effect in your chatlog (if the [General]AngelscriptDebug setting is enabled in your plus.ini) but no behavior change will occur.
  30.                
  31.                 Note that each Jazz1::Enemy instance is unique to a Jazz1::MakeEnemy call, even if you use the same value for more than one enemyID argument. To have one event place Tubelectric blasters on the floor and another event place them on the ceiling, for instance, you could write:
  32.                
  33.                         Jazz1::MakeEnemy(OBJECT::APPLE, Jazz1::Enemies::Tubelectric_Blaster).SetDirection(Jazz1::Directions::Right);
  34.                         Jazz1::MakeEnemy(OBJECT::BANANA, Jazz1::Enemies::Tubelectric_Blaster).SetDirection(Jazz1::Directions::Left);
  35.                
  36.                
  37.                
  38.         List of Jazz1::Enemy methods (the return type for the Set* methods is always Jazz1::Enemy@, so you can chain these methods indefinitely):
  39.                 SetSpeed(float speed)
  40.                         Replaces the enemy's default movement speed with a new one of your choice. Different enemies have different default speeds, so you'll have to play around with this number a bit in order to find what feels best for you.
  41.                        
  42.                 SetSpeedBasedOnParameter(uint offset, uint length, float modifier = 1.0f)
  43.                         Instead of its movement speed being a constant, the enemy will base its speed on an event parameter you specified in JCS. (This will require an appropriately edited JCS.ini entry for that eventID). The enemy will get a parameter value based on the "offset" and "length" arguments, familiar from the global "jjParameterGet" function; add 1 to that value (so that the enemy never moves at speed 0); and then multiply that value by the "modifier" argument. For example, if "length" is 2 and "modifier" is 0.5, then any instance of that enemy you place in the level will have its movement speed be either 0.5, 1.0, 1.5, or 2.0, depending on what parameter you set in the level.
  44.                        
  45.                 SetDirection(int offsetOrConstant)
  46.                         Four constant values are provided for use with this method, to be found in the Jazz1::Directions enum: Left, Right, FaceJazz, and Random. Mostly this method specifies which direction the enemy should face when it is first spawned in the level, but a handful of enemies will check it constantly, so e.g. a Nippius_SnowGoon enemy would be able to turn around to face the nearest local player at all times if passed Jazz1::Directions::FaceJazz.
  47.                         If you pass an unsigned integer value instead of a Jazz1::Directions constant, that will make the enemy base its direction on an event parameter instead, specifically a parameter whose length is 1 and whose offset is the value you passed, just like e.g. springs or CTF bases.
  48.                        
  49.                 SetFireDelay(uint delay)
  50.                         How many gameticks (70 per second) should pass after the enemy fires a bullet before it fires another one. Each enemy has its own default delay time but you can replace it.
  51.                         For hopping enemies (e.g. Marbelara_Firebomb), this sets the number of ticks the enemy waits at its starting position between hops.
  52.                        
  53.                 SetUsesJJ2StyleDeathAnimation(bool setTo)
  54.                         By default, all enemies use more or less their original death animations, e.g. a Tubelectric_Spark enemy explodes into yellow shards. If you pass true to this method, that enemy will instead die by bursting into particles appropriate to a JJ2 enemy dying from whatever means (regular bullet, toaster bullet, physical attack, etc.) was used to kill it.
  55.                         (This is a PURELY visual change. Either way the player will get points and potentially a pickup for killing the enemy.)
  56.                        
  57.                 SetWalkingEnemyCliffReaction(Jazz1::CliffReaction)
  58.                         All enemies that walk back and forth on the floor share their basic code, and this method lets you choose how they should react when they run out of floor to walk on. The available values of the Jazz1::CliffReaction enum are TurnAround (e.g. Diamondus_TurtleGoon), Fall (e.g. Medivo_Helmut), and Careen (not found in Jazz 1, but perhaps a slightly more attractive option than falling straight down).
  59.                        
  60.                 SetEnemySpeedsAreScalable(bool setTo)
  61.                 SetBulletSpeedsAreScalable(bool setTo)
  62.                         Each enemy's movement speed, as well as the movement speeds of its bullets if any, assumes the sprites will be drawn at their original size. If you pass a value other than 1.f for the "scale" argument of MakeEnemy, however, this may look wrong, so there are a couple methods to have the enemies and/or bullets' speeds scale as well. Both default false.
  63.                        
  64.                 SetDeathSound(SOUND::Sample sample)
  65.                 SetBulletFireSound(SOUND::Sample sample)
  66.                 SetBulletExplosionSound(SOUND::Sample sample)
  67.                         These methods let you choose which sounds are played when (respectively) the enemy dies, the enemy fires a bullet, or a bullet fired by the enemy explodes.
  68.                        
  69.                 SetPointsFormula(Jazz1::PointsFormula pointsFormula)
  70.                         By default (Jazz1::PointsFormula::Original), killing an enemy will give you the same number of points it did in JJ1. This is always a multiple of 10, often 20 or 30. However, normal JJ2 objects only ever give points in multiples of 50. So you may apply a different rule to this enemy by using the alternate options Jazz1::PointsFormula::MultiplyByFive (e.g. 20 would become 100) or Jazz1::PointsFormula::RoundUpToMultipleOfFifty (e.g. 20 would become 50).
  71.                
  72.                 uint GetFirstFrame(bool useTrueColor = false) const
  73.                         The only Get*, not Set*, method. If you want to use the sprites from one or more Jazz 1 Enemies, but not the behavior class this library provides for it, this method will give you the first frame of the enemy's animset (as an index to jjAnimFrames, suitable for jjOBJ::curFrame usage) for you to use however you wish. You can look at the corresponding .png image for a guide to what the enemy's various frames are, with the leftmost frame in the image the one whose ID GetFirstFrame specifically returns you. For example, "Jazz1::MakeEnemy(OBJECT::APPLE, Jazz1::Enemies::Technoir_TanketyTankTank).GetFirstFrame() + 5" will get you the Technoir_TanketyTankTank's rightward-facing pineapple missile sprite.
  74.                         If you pass true for the "useTrueColor" argument, you'll get the first frame again, but as formatted for use by the TrueColor library, e.g. "Jazz1::MakeEnemy(OBJECT::APPLE, Jazz1::Enemies::Technoir_TanketyTankTank, true).GetFirstFrame(true) + 5 * TrueColor::NumberOfFramesPerImage". The return value if you pass "true" to GetFirstFrame's "useTrueColor" argument but _not_ to MakeEnemy's "useTrueColor" argument is undefined.
  75.                        
  76.         Additionally, you are welcome to edit various of a jjObjectPreset entry's properties in accordance with their usual meanings, including jjOBJ::energy, jjOBJ::points, jjOBJ::isFreezable, jjOBJ::isTarget, jjOBJ::light, and jjOBJ::lightType; however, the MakeEnemy call will assign to many of them the default values for that enemy, so you should not edit those properties for a given preset until AFTER calling MakeEnemy on it.
  77.        
  78.         When dealing with active objects, rather than presets, the jjKillObject function is fully supported. (As are various others, but that seemed most worthy of note.)
  79.                        
  80.         These enemies should work just about as well online (in SP/Coop servers) as any other enemies do, provided the same code is run by all players in the server.
  81.        
  82.        
  83.        
  84.        
  85.        
  86.        
  87. Packaging Instructions:
  88.  
  89.  
  90.         In the most basic case, your script needs to include only one line:
  91.                 #include "Jazz1Enemies v05.asc"
  92.         This will automatically work in multiplayer servers; this library automatically inserts enough preprocessor instructions for clients to know to download the relevant files.
  93.        
  94.         If you are packaging a .zip archive for a level/mutator using this library, you will need to include all four of these files, even if you are not actively using the Resize and/or TrueColor libraries for any of the enemies in this level:
  95.                 Jazz1Enemies v05.asc
  96.                 Jazz1Enemies v05.j2a
  97.                 Resize v11.asc
  98.                 TrueColor v13.asc
  99.        
  100.        
  101.         If you do make one or more calls to Jazz1::MakeEnemy with the "useTrueColor" argument equalling true, however, you will need to handle the .png image for each such call manually. It must be included in any .zip archive, and in the event that you want the script to work online, you will need to write a "#pragma require" line specifically for that .png.
  102.  
  103.         For example, suppose you have two calls to Jazz1::MakeEnemy, one of them passing "useTrueColor" as false and one passing it as true:
  104.                 Jazz1::MakeEnemy(OBJECT::BUMBEE, Jazz1::Enemies::Diamondus_BumblingBee, false);
  105.                 Jazz1::MakeEnemy(OBJECT::NORMTURTLE, Jazz1::Enemies::Diamondus_TurtleGoon, true);
  106.         In this case, Diamondus_BumblingBee requires no special treatment (because it is not using True Color), but your script should include both these lines:
  107.                 #include "Jazz1Enemies v05.asc"
  108.                 #pragma require "Jazz1Enemies_Diamondus_TurtleGoon.png"
  109.         And your .zip archive should include these five files:
  110.                 Jazz1Enemies v05.asc
  111.                 Jazz1Enemies v05.j2a
  112.                 Jazz1Enemies_Diamondus_TurtleGoon.png
  113.                 Resize v11.asc
  114.                 TrueColor v13.asc
  115.                
  116.                
  117.         The pattern for True Color image filenames is perfectly regular: the name of the enemy, with "Jazz1Enemies_" as the prefix, followed by the file extension ".png". So:
  118.                 Jazz1::Enemies::Letni_BugCeiling -> Jazz1Enemies_Letni_BugCeiling.png
  119.                 Jazz1::Enemies::Marbelara_Schwarzenguard -> Jazz1Enemies_Marbelara_Schwarzenguard.png
  120.                 Jazz1::Enemies::Battleships_Generator -> Jazz1Enemies_Battleships_Generator.png
  121.         ...etc.
  122.                
  123. *************************/
  124.  
  125. namespace Jazz1 {
  126.         enum Enemies { _Misc_Anims,
  127.         //Full list of Enemy IDs for passing as the second argument to Jazz1::MakeEnemy (but remember to precede any name with "Jazz1::Enemies::"):
  128.                 Diamondus_BumblingBee, Diamondus_TurtleGoon,
  129.                 Tubelectric_BlasterHorizontal, Tubelectric_BlasterVertical, Tubelectric_Spark, Tubelectric_SparkBarrier, //for BlasterVertical, right=on floor, left=on ceiling
  130.                 Medivo_GhostRapierHorizontal, Medivo_GhostRapierVertical, Medivo_Helmut,
  131.                 Letni_Bug, Letni_BugCeiling, Letni_ElecBarrier,
  132.                 Technoir_MiniMine, Technoir_Misfire, Technoir_TanketyTankTank,
  133.                 Orbitus_BeholderPurple, Orbitus_BeholderSilver, Orbitus_SilverSnake,
  134.                 Fanolint_FlyFlower, Fanolint_PottedPlant, Fanolint_SuperTankety,
  135.                 Scraparap_GunnerDrone, Scraparap_LaunchCart, Scraparap_RoboTurtleDrone,
  136.                 Megairbase_Doofusguard, Megairbase_Missile, Megairbase_SuperSpark, //Megairbase also essentially reuses Tubelectric_Spark, but that does not a separate enum value warrant
  137.                 Turtemple_JeTurtle, Turtemple_ScorpWeenie,
  138.                 Nippius_SkatePen, Nippius_SkiTurtle, Nippius_SnowGoon,
  139.                 Jungrock_JetSnake, Jungrock_RedBuzzer, Jungrock_YellowBuzzer,
  140.                 Marbelara_Drageen, Marbelara_Firebomb, Marbelara_Schwarzenguard,
  141.                 Sluggion_Dragoon, Sluggion_RedBat, Sluggion_Sluggi,
  142.                 Dreempipes_Minite, Dreempipes_Overgrown, Dreempipes_TerrapinSwimmer,
  143.                 Pezrox_ClammyHorizontal, Pezrox_ClammyVertical, Pezrox_GreenSnake, //for ClammyVertical, right=down, left=up
  144.                 Crysilis_GoldenBounceSpike, Crysilis_LooGuard,
  145.                 Battleships_ArmorDoofi, Battleships_BounceSpike, Battleships_Generator, Battleships_SuperBee,
  146.                 /*Exoticus_???,
  147.                 Industrius_???,
  148.                 Muckamok_???,
  149.                 Raneforus_???,
  150.                 Stonar_???,
  151.                 Deckstar_???,
  152.                 Ceramicus_???,
  153.                 Deserto_???,
  154.                 Lagunicus_???,*/
  155.                 Holidaius_BlueDog, Holidaius_Devil, Holidaius_HandHorizontal, Holidaius_HandVertical, Holidaius_SkiTurtle, Holidaius_SnowMonkey, //for HandVertical, right=on floor, left=on ceiling
  156.                 //Candion_Rat, Bloxonius_GreenBaron, Bloxonius_RaggedyAnne, Bloxonius_RedBaron
  157.                
  158.                 LAST
  159.         };
  160.        
  161.         //Everything below is subject to change and need not therefore be read by users; only the above enum and API description should be taken as promises.
  162.        
  163.        
  164.        
  165.        
  166.        
  167.        
  168.        
  169.        
  170.        
  171.        
  172.        
  173.        
  174.        
  175.        
  176.        
  177.        
  178.        
  179.        
  180.        
  181.        
  182.        
  183.        
  184.        
  185.        
  186.        
  187.        
  188.        
  189.        
  190.        
  191.        
  192.        
  193.        
  194.        
  195.        
  196.        
  197.        
  198.        
  199.        
  200.        
  201.        
  202.        
  203.        
  204.        
  205.        
  206.         const array<string> _enemyNames = {
  207.                 "Diamondus_BumblingBee", "Diamondus_TurtleGoon",
  208.                 "Tubelectric_BlasterHorizontal", "Tubelectric_BlasterVertical", "Tubelectric_Spark", "Tubelectric_SparkBarrier",
  209.                 "Medivo_GhostRapierHorizontal", "Medivo_GhostRapierVertical", "Medivo_Helmut",
  210.                 "Letni_Bug", "Letni_BugCeiling", "Letni_ElecBarrier",
  211.                 "Technoir_MiniMine", "Technoir_Misfire", "Technoir_TanketyTankTank",
  212.                 "Orbitus_BeholderPurple", "Orbitus_BeholderSilver", "Orbitus_SilverSnake",
  213.                 "Fanolint_FlyFlower", "Fanolint_PottedPlant", "Fanolint_SuperTankety",
  214.                 "Scraparap_GunnerDrone", "Scraparap_LaunchCart", "Scraparap_RoboTurtleDrone",
  215.                 "Megairbase_Doofusguard", "Megairbase_Missile", "Megairbase_SuperSpark",
  216.                 "Turtemple_JeTurtle", "Turtemple_ScorpWeenie",
  217.                 "Nippius_SkatePen", "Nippius_SkiTurtle", "Nippius_SnowGoon",
  218.                 "Jungrock_JetSnake", "Jungrock_RedBuzzer", "Jungrock_YellowBuzzer",
  219.                 "Marbelara_Drageen", "Marbelara_Firebomb", "Marbelara_Schwarzenguard",
  220.                 "Sluggion_Dragoon", "Sluggion_RedBat", "Sluggion_Sluggi",
  221.                 "Dreempipes_Minite", "Dreempipes_Overgrown", "Dreempipes_TerrapinSwimmer",
  222.                 "Pezrox_ClammyHorizontal", "Pezrox_ClammyVertical", "Pezrox_GreenSnake",
  223.                 "Crysilis_GoldenBounceSpike", "Crysilis_LooGuard",
  224.                 "Battleships_ArmorDoofi", "Battleships_BounceSpike", "Battleships_Generator", "Battleships_SuperBee",
  225.                 //ABC
  226.                 "Holidaius_BlueDog", "Holidaius_Devil", "Holidaius_HandHorizontal", "Holidaius_HandVertical", "Holidaius_SkiTurtle", "Holidaius_SnowMonkey"
  227.                 //"Candion_Rat", "Bloxonius_GreenBaron", "Bloxonius_RaggedyAnne", "Bloxonius_RedBaron"
  228.         };
  229.        
  230.         enum Directions { Left = -4, Right = -3, FaceJazz = -2, Random = -1 };
  231.         enum CliffReaction { TurnAround, Fall, Careen };
  232.         enum PointsFormula { Original, MultiplyByFive, RoundUpToMultipleOfFifty };
  233.        
  234.         enum _objVar { AnimCounter, DirectionCurrent, FireDelayCounter };
  235.         enum _causeOfDeath { Bullet, OrangeShards, BlueShards, GrayShards, PhysicalAttack, FrozenBullet, FrozenPhysicalAttack, AlreadyPerformedAnimation};
  236.         const float _levelWidth = jjLayerWidth[4] * 32;
  237.         const float _levelHeight = jjLayerHeight[4] * 32;
  238.        
  239.         dictionary _animSets;
  240.        
  241.         bool _trueColorHasProcessedPalette = false;
  242.        
  243.         int _getParameter(jjOBJ@ obj, int offset, int length) {
  244.                 return jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, offset, length);
  245.         }
  246.         bool _maskedPixelFloat(float x, float y) {
  247.                 return jjMaskedPixel(int(x), int(y));
  248.         }
  249.         const AnimSetDetails@ _getAnimSet(Enemies enemyID, float scale = 1.f, string name = "", uint resizeMethod = Resize::Method::AdvMAME2x) {
  250.                 const bool truecolor = !name.isEmpty();
  251.                 const string key = enemyID + "-" + scale;// + "-" + truecolor;
  252.                 AnimSetDetails@ result;
  253.                 bool setHasBeenLoadedBefore;
  254.                 if (!(setHasBeenLoadedBefore = _animSets.get(key, @result))) {
  255.                         @_animSets[key] = @result = AnimSetDetails();
  256.                         result.animID = jjAnimSets[TrueColor::FindCustomAnim()].load(enemyID, "Jazz1Enemies v05.j2a").firstAnim;
  257.                         result.firstFrame = jjAnimations[result.animID].firstFrame;
  258.                        
  259.                         {
  260.                                 uint animID = result.animID;
  261.                                 while (jjAnimations[animID++].frameCount != 0)
  262.                                         result.animCount += 1; //this number is not directly stored anywhere, so I have to infer it from when I run out of used animations
  263.                         }
  264.                 }
  265.                        
  266.                 if (truecolor && result.firstFrameTrueColor == 0) { //images haven't been generated for this enemy/scale combo yet.
  267.                         if (!_trueColorHasProcessedPalette) {
  268.                                 TrueColor::ProcessPalette();
  269.                                 _trueColorHasProcessedPalette = true;
  270.                         }
  271.                        
  272.                         array<array<TrueColor::Coordinates>> _trueColorCoordinates = {array<TrueColor::Coordinates>(0)}; //construct the TrueColor animsets as single flat animations, no matter the structures of the animsets from Jazz1Enemies.j2a, because all I really care about is frame offset
  273.                        
  274.                         uint leftPositionOfAnimFrameInSpriteSheet = 0;
  275.                         for (uint i = 0; i < result.animCount; ++i) { //generate an array<TrueColor::Coordinates> based on the animset's animations' frames' properties
  276.                                 const jjANIMATION@ animation = jjAnimations[result.animID + i];
  277.                                 for (uint j = 0; j < animation.frameCount; ++j) {
  278.                                         const jjANIMFRAME@ animFrame = jjAnimFrames[animation + j];
  279.                                         _trueColorCoordinates[0].insertLast(TrueColor::Coordinates(
  280.                                                 leftPositionOfAnimFrameInSpriteSheet, 0,
  281.                                                 animFrame.width, animFrame.height,
  282.                                                 animFrame.hotSpotX, animFrame.hotSpotY/*, //the other properties aren't needed
  283.                                                 animFrame.gunSpotX, animFrame.gunSpotY,
  284.                                                 animFrame.coldSpotX, animFrame.coldSpotY*/
  285.                                         ));
  286.                                         leftPositionOfAnimFrameInSpriteSheet += animFrame.width; //the next sprite on the spritesheet, if any, will be placed immediately to the right of this one
  287.                                 }
  288.                         }
  289.                        
  290.                         const ANIM::Set trueColorAnimSet = TrueColor::FindCustomAnim();
  291.                         auto bitmap = TrueColor::Bitmap("Jazz1Enemies_" + name + ".png");
  292.                         if (scale != float(int(scale)) || (resizeMethod & (Resize::Flags::_firstFlag - 1)) >= Resize::Method::_firstInvolvingFindNearestColor)
  293.                                 bitmap.generateAlphaChannel();
  294.                         Resize::AllocateAndResizeTrueColorSpriteSheet(
  295.                                 trueColorAnimSet,
  296.                                 bitmap,
  297.                                 _trueColorCoordinates,
  298.                                 scale, resizeMethod
  299.                         );
  300.                         result.firstFrameTrueColor = jjAnimations[jjAnimSets[trueColorAnimSet]];
  301.                 }
  302.                        
  303.                 if (!setHasBeenLoadedBefore && scale != 1.f)
  304.                         for (uint i = 0; i < result.animCount; ++i)
  305.                                 Resize::Resize(jjAnimations[result.animID + i], scale, resizeMethod, scale);
  306.                 return @result;
  307.         }
  308.        
  309.         void _explosion(jjOBJ@ obj) {
  310.                 if (obj.ySpeed != 0) {
  311.                         obj.xPos += obj.xSpeed;
  312.                         obj.yPos += obj.ySpeed += 0.125f;
  313.                 }
  314.                 if (jjRandom() & 7 < uint(obj.ySpeed == 0 ? 3 : 5)) { //advance frame
  315.                         jjANIMATION@ anim = jjAnimations[obj.curAnim];
  316.                         if(++obj.frameID >= int(anim.frameCount)) {
  317.                                 obj.frameID = 0;
  318.                                 if (++obj.counterEnd >= obj.creatorID) { //_killAnimRepetitionCounts
  319.                                         obj.delete();
  320.                                         return;
  321.                                 }
  322.                         }
  323.                         obj.curFrame = anim.firstFrame + obj.frameID;
  324.                 }
  325.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
  326.         }
  327.        
  328.         enum _bulletDirection { Left = -1, Either = 0, Right = 1 };
  329.         class _bulletPreferences {
  330.                 private float xSpeed, ySpeed, yAcc;
  331.                 private uint animID, frameID;
  332.                 _bulletDirection direction;
  333.                 _bulletPreferences(){}//array purposes
  334.                 _bulletPreferences(float x, float y, uint a, uint f, _bulletDirection d, float ya = 0) { xSpeed = x; ySpeed = y; animID = a; frameID = f; direction = d; yAcc = ya / 4;}
  335.                 jjOBJ@ fire(const jjOBJ@ obj, int sound, float scaleSpeed, float scaleSprite) const {
  336.                         int x1, x2, y1, y2;
  337.                         { //jjOBJ::fireBullet doesn't work with vertically flipped frames, so we'll have to figure out on our own where the bullet object needs to be spawned
  338.                                 const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
  339.                                 if (obj.direction >= 0) {
  340.                                         x1 = frame.hotSpotX;
  341.                                         x2 = frame.gunSpotX;
  342.                                 } else { //horizontally flipped
  343.                                         x2 = frame.hotSpotX;
  344.                                         x1 = frame.gunSpotX;
  345.                                 }
  346.                                 if (obj.direction != SPRITE::FLIPV && obj.direction != SPRITE::FLIPHV) {
  347.                                         y1 = frame.hotSpotY;
  348.                                         y2 = frame.gunSpotY;
  349.                                 } else { //vertically flipped
  350.                                         y2 = frame.hotSpotY;
  351.                                         y1 = frame.gunSpotY;
  352.                                 }
  353.                         }
  354.                         jjOBJ@ bullet = jjObjects[jjAddObject(
  355.                                 OBJECT::BULLET,
  356.                                 obj.xPos + x1 - x2,
  357.                                 obj.yPos + y1 - y2,
  358.                                 obj.objectID, CREATOR::OBJECT,
  359.                                 BEHAVIOR::INACTIVE
  360.                         )];
  361.                         bullet.xSpeed = xSpeed * scaleSpeed;
  362.                         bullet.ySpeed = ySpeed * scaleSpeed;
  363.                         bullet.yAcc = yAcc * scaleSpeed;
  364.                         bullet.curFrame = jjAnimations[jjObjectPresets[obj.eventID].curAnim + animID] + frameID;
  365.                         bullet.behavior = _bullet;
  366.                         bullet.special = sound;
  367.                         bullet.killAnim = _getAnimSet(Enemies::_Misc_Anims, scaleSprite).animID + 1;
  368.                         return bullet;
  369.                 }
  370.         }
  371.         void _bullet(jjOBJ@ obj) {
  372.                 if (obj.state == STATE::START) {
  373.                         obj.state = STATE::FLY;
  374.                         obj.playerHandling = HANDLING::ENEMYBULLET;
  375.                         obj.animSpeed = 1;
  376.                         obj.lightType = LIGHT::POINT2;
  377.                 }
  378.                 obj.xPos += obj.xSpeed;
  379.                 obj.yPos += obj.ySpeed += obj.yAcc;
  380.                 if (obj.curAnim == 0 || jjColorDepth == 8)
  381.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
  382.                 else
  383.                         TrueColor::DrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curAnim);
  384.                 if (obj.state == STATE::EXPLODE || obj.xPos < 0 || obj.yPos < 0 || obj.xPos >= _levelWidth || obj.yPos >= _levelHeight || _maskedPixelFloat(obj.xPos, obj.yPos)) {
  385.                         obj.playerHandling = HANDLING::EXPLOSION;
  386.                         obj.curAnim = obj.killAnim;
  387.                         obj.behavior = _explosion;
  388.                         obj.creatorID = 1; //repetitions
  389.                         obj.ySpeed = 0; //don't move
  390.                         obj.counterEnd = 0;
  391.                         if (obj.special >= 0)
  392.                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(obj.special));
  393.                 }
  394.         }
  395.        
  396.         class EnemyArguments {
  397.                 jjOBJ@ preset; Enemies enemyID; bool tc; float scale; uint resizeMethod;
  398.                 EnemyArguments(jjOBJ@ p, Enemies e, bool t, float s, uint r) { @preset = p; enemyID = e; tc = t; scale = s; resizeMethod = r; }
  399.         }
  400.         class AnimSetDetails {
  401.                 uint animID, animCount, firstFrame, firstFrameTrueColor;
  402.         }
  403.         abstract class Enemy : jjBEHAVIORINTERFACE {
  404.                 private string _behaviorName;
  405.                 private bool _useTrueColor;
  406.                 protected float _scale;
  407.                 private uint _firstFramePaletted, _firstFrameTrueColor;
  408.                
  409.                 protected array<int8>@ _animFrames = array<int8>(0), _fireFrames = null;
  410.                 protected uint _fireAnimID = 0;
  411.                 protected jjOBJ@ preset;
  412.                
  413.                 Enemy(const EnemyArguments &in arg) {
  414.                         _behaviorName = _enemyNames[arg.enemyID - 1];
  415.                         @preset = arg.preset;
  416.                         auto animDetails = _getAnimSet(arg.enemyID, arg.scale, arg.tc ? _behaviorName : "", arg.resizeMethod);
  417.                         preset.curAnim = animDetails.animID;
  418.                         _firstFramePaletted = preset.curFrame = animDetails.firstFrame;
  419.                         _firstFrameTrueColor = animDetails.firstFrameTrueColor;
  420.                         _scale = arg.scale;
  421.                         _useTrueColor = arg.tc;
  422.                        
  423.                         for (uint i = 0; i < jjAnimations[preset.curAnim].frameCount; ++i) //create a default _animFrames array that repeats every frame in the animation exactly once and in linear order
  424.                                 _animFrames.insertLast(i);
  425.                        
  426.                         preset.behavior = this;
  427.                         preset.playerHandling = HANDLING::SPECIAL;
  428.                         preset.bulletHandling = HANDLING::DETECTBULLET;
  429.                         preset.scriptedCollisions = true;
  430.                         preset.isTarget = true;
  431.                         preset.isFreezable = true;
  432.                         preset.isBlastable = true;
  433.                         preset.triggersTNT = true;
  434.                         preset.causesRicochet = false;
  435.                         preset.deactivates = true;
  436.                         preset.direction = 1;
  437.                         preset.state = STATE::START;
  438.                         preset.freeze = 0;
  439.                         preset.energy = preset.points = preset.animSpeed = 0; //these should be overridden
  440.                         preset.killAnim = -1; //first misc anim
  441.                         preset.var[_objVar::AnimCounter] = 0;
  442.                         preset.var[_objVar::FireDelayCounter] = 0;
  443.                 }
  444.                 void onBehave(jjOBJ@ obj) {
  445.                         if (obj.state == STATE::DEACTIVATE)
  446.                                 obj.deactivate();
  447.                         else if (obj.state == STATE::KILL) {
  448.                                 if (_useJJ2DeathAnimation) {
  449.                                         switch (_died) {
  450.                                                 case _causeOfDeath::Bullet:
  451.                                                         obj.particlePixelExplosion(0);
  452.                                                         break;
  453.                                                 case _causeOfDeath::OrangeShards:
  454.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
  455.                                                         obj.particlePixelExplosion(1);
  456.                                                         break;
  457.                                                 case _causeOfDeath::BlueShards:
  458.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
  459.                                                         obj.particlePixelExplosion(32);
  460.                                                         break;
  461.                                                 case _causeOfDeath::GrayShards:
  462.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
  463.                                                         obj.particlePixelExplosion(72);
  464.                                                         break;
  465.                                                 case _causeOfDeath::PhysicalAttack:
  466.                                                         obj.particlePixelExplosion(2);
  467.                                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_SPLAT1 + (jjRandom() & 3)));
  468.                                                         break;
  469.                                                 case _causeOfDeath::FrozenBullet:
  470.                                                         obj.unfreeze(0);
  471.                                                         break;
  472.                                                 case _causeOfDeath::FrozenPhysicalAttack:
  473.                                                         obj.unfreeze(1);
  474.                                                         break;
  475.                                                 case _causeOfDeath::AlreadyPerformedAnimation:
  476.                                                         break;
  477.                                         }
  478.                                 } else { //Jazz1-style
  479.                                         const auto killAnim = _getPresetRelativeAnimID(int(obj.killAnim));
  480.                                         for (int i = 0; i < 7; ++i) {
  481.                                                 jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, _killAnimRepetitionCounts)];
  482.                                                 shard.curAnim = killAnim;
  483.                                                 shard.behavior = _explosion;
  484.                                                 shard.counterEnd = 0;
  485.                                                 if (_killAnimExplosion) {
  486.                                                         shard.behave(BEHAVIOR::SHARD); //get some random directions
  487.                                                         shard.xSpeed *= _scale;
  488.                                                         shard.ySpeed *= _scale;
  489.                                                         shard.ySpeed += 0.0001; //non-zero
  490.                                                 } else
  491.                                                         break; //only one
  492.                                         }
  493.                                 }
  494.                                 if (_deathSound >= 0)
  495.                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(_deathSound));
  496.                                 obj.delete();
  497.                         } else if (obj.state == STATE::FREEZE) {
  498.                                 if (obj.freeze-- <= 1) {
  499.                                         obj.freeze = 0;
  500.                                         obj.state = obj.oldState;
  501.                                 }
  502.                         } else {
  503.                                 if (obj.state == STATE::START) {
  504.                                         if (_supportsSpeed) {
  505.                                                 //we're using xAcc exclusively, instead of xSpeed, because xAcc can't get changed by TNT
  506.                                                 if (_speedParamOffset >= 0)
  507.                                                         obj.xAcc = (_getParameter(obj, _speedParamOffset, _speedParamLength) + 1) * _speed;
  508.                                                 else
  509.                                                         obj.xAcc = _speed;
  510.                                                 obj.xAcc *= _scaledObjectSpeed;
  511.                                         }
  512.                                         if (jjDifficulty >= 3) //turbo
  513.                                                 obj.energy += 1;
  514.                                 }
  515.                                
  516.                                 if (_supportsFire) {
  517.                                         if (obj.counterEnd > _fireDelay) { //showing fire animation
  518.                                                 obj.var[_objVar::AnimCounter] = obj.var[_objVar::AnimCounter] + 1;
  519.                                                 if (obj.var[_objVar::AnimCounter] > obj.animSpeed) {
  520.                                                         obj.var[_objVar::AnimCounter] = 0;
  521.                                                         if (uint(++obj.frameID) >= _fireFrames.length) {
  522.                                                                 _fireBullets(obj);
  523.                                                                 obj.counterEnd = 0;
  524.                                                                 return;
  525.                                                         }
  526.                                                 }
  527.                                                 obj.curFrame = jjAnimations[_getPresetRelativeAnimID(_fireAnimID)].firstFrame + _fireFrames[obj.frameID];
  528.                                                 return;
  529.                                         } else if (++obj.counterEnd > _fireDelay) { //start firing
  530.                                                 if (_fireFrames !is null && _fireFrames.length > 0) {
  531.                                                         obj.var[_objVar::AnimCounter] = 0;
  532.                                                         obj.frameID = 0;
  533.                                                         obj.curFrame = jjAnimations[_getPresetRelativeAnimID(_fireAnimID)].firstFrame + _fireFrames[obj.frameID];
  534.                                                         return;
  535.                                                 } else {
  536.                                                         _fireBullets(obj);
  537.                                                         obj.counterEnd = 0;
  538.                                                         //fall through...
  539.                                                 }
  540.                                         }
  541.                                 }
  542.                                
  543.                                 myBehave(obj);
  544.                                
  545.                                 jjANIMATION@ anim = jjAnimations[obj.curAnim];
  546.                                 obj.var[_objVar::AnimCounter] = obj.var[_objVar::AnimCounter] + 1;
  547.                                 obj.curFrame = anim.firstFrame + _animFrames[(((obj.var[_objVar::AnimCounter] >> 1) / (obj.animSpeed+1)) % _animFrames.length)];
  548.                                
  549.                                 if (_flipSpriteWhenMovingLeft) //otherwise always face right
  550.                                         obj.direction = obj.var[_objVar::DirectionCurrent];
  551.                         }
  552.                 }
  553.                 protected void myBehave(jjOBJ@ obj) const { obj.state = STATE::IDLE; } //here to be overridden
  554.                
  555.                 protected void _drawBodyFrame(const jjOBJ@ obj, float xPos, float yPos, uint frameID, int direction, int8 layer = 4) const {
  556.                         if (!_useTrueColor || jjColorDepth == 8 || obj.freeze != 0 || obj.justHit != 0)
  557.                                 jjDrawSpriteFromCurFrame(
  558.                                         xPos, yPos, frameID, direction,
  559.                                         (obj.freeze == 0) ? (obj.justHit == 0) ? SPRITE::NORMAL : SPRITE::SINGLECOLOR : SPRITE::FROZEN,
  560.                                         15,
  561.                                         layer
  562.                                 );
  563.                         else
  564.                                 TrueColor::DrawSpriteFromCurFrame(
  565.                                         xPos, yPos,
  566.                                         _firstFrameTrueColor + (frameID - _firstFramePaletted) * TrueColor::NumberOfFramesPerImage,
  567.                                         direction,
  568.                                         layer
  569.                                 );
  570.                 }
  571.                 void onDraw(jjOBJ@ obj) {
  572.                         if (obj.isActive)
  573.                                 _drawBodyFrame(obj, obj.xPos, obj.yPos, obj.curFrame, obj.direction);
  574.                 }
  575.                
  576.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) { //mostly copied from plus52Scripting.j2as
  577.                         if (bullet !is null) {
  578.                                 //recreation of HANDLING::HURTBYBULLET with HANDLING::ENEMY
  579.                                 if (obj.causesRicochet) {
  580.                                         if ((bullet.var[6] & 6) == 0) //not fire-based, not a laser beam
  581.                                                 bullet.ricochet();
  582.                                         else if ((bullet.var[6] & 4) == 0) //not a laser beam
  583.                                                 bullet.delete();
  584.                                 } else if ((bullet.var[6] & 16) == 0) //not a fireball
  585.                                         bullet.state = STATE::EXPLODE;
  586.                                 if (obj.freeze > 0 && force < 3)
  587.                                         force = 3;
  588.                                 obj.energy -= force;
  589.                                 obj.justHit = 5; //flash white for 5 ticks--jjOBJ::justHit is automatically deincremented by the JJ2 engine, so individual behavior functions don't need to worry about doing that.
  590.                                 if (obj.energy <= 0) { //killed
  591.                                         obj.energy = 0;
  592.                                         if (obj.freeze > 0)
  593.                                                 _died = _causeOfDeath::FrozenBullet;
  594.                                         else if ((bullet.var[6] & 2) == 0) //not fire-based
  595.                                                 _died = _causeOfDeath::Bullet;
  596.                                         else if ((bullet.var[6] & 4) != 0) //laser beam
  597.                                                 _died = _causeOfDeath::GrayShards;
  598.                                         else
  599.                                                 _died = ((bullet.var[6] & 8) != 0) ? _causeOfDeath::BlueShards : _causeOfDeath::OrangeShards; //powered-up (blue) or not (orange)
  600.                                         if (player !is null) {
  601.                                                 obj.grantPickup(player, (uint(bullet.curAnim) == jjAnimSets[ANIM::AMMO].firstAnim + 17) ? 5 : 10);
  602.                                                 givePlayerPointsForObject(player, obj);
  603.                                         }
  604.                                         jjKillObject(obj.objectID);
  605.                                 } else
  606.                                         obj.freeze = 0;
  607.                         } else { //recreation of HANDLING::ENEMY; player guaranteed to be non-null
  608.                                 if (force != 0) { //attacking via special attack, e.g. buttstomp
  609.                                         obj.energy -= 4; //constant amount of damage for special attacks
  610.                                         if (obj.energy <= 0) { //killed
  611.                                                 obj.energy = 0;
  612.                                                 if (obj.freeze > 0)
  613.                                                         _died = _causeOfDeath::FrozenPhysicalAttack;
  614.                                                 else
  615.                                                         _died = _causeOfDeath::PhysicalAttack;
  616.                                                 givePlayerPointsForObject(player, obj);
  617.                                                 jjKillObject(obj.objectID);
  618.                                         } else { //only wounded
  619.                                                 obj.justHit = 5;
  620.                                                 if (obj.freeze <= 0)
  621.                                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_SPLAT1 + (jjRandom() & 3)));
  622.                                         }
  623.                                        
  624.                                         if (force > 0) { //buttstomp or sugar rush
  625.                                                 player.buttstomp = 50; //landing
  626.                                                 player.ySpeed = player.ySpeed / -2 - 8;
  627.                                                 player.yAcc = 0;
  628.                                                 player.extendInvincibility(-70);
  629.                                         } else if (force == -101) { //running into frozen enemy
  630.                                                 player.xAcc = 0;
  631.                                                 player.xSpeed /= -2;
  632.                                                 player.ySpeed = -6;
  633.                                                 player.extendInvincibility(-10);
  634.                                         }
  635.                                 } else  { //not attacking
  636.                                         if (obj.freeze == 0)
  637.                                                 player.hurt();
  638.                                 }
  639.                         }
  640.                         return true;
  641.                 }
  642.                
  643.                 private bool givePlayerPointsForObject(jjPLAYER@ player, jjOBJ@ obj) const { //This will probably be made a jjOBJ method as part of the real JJ2+ API eventually, because it shows up all the time in the native code, but that hasn't happened yet, so here you go. Increases the player's jjPLAYER::score to match the object's jjOBJ::points, and creates a string particle with that number which flies up to the top left corner of the screen.
  644.                         if (player is null)
  645.                                 return false;
  646.                         if (obj.points != 0 && (jjGameMode == GAME::SP || jjGameMode == GAME::COOP)) {
  647.                                 if (_pointsFormula == PointsFormula::MultiplyByFive)
  648.                                         obj.points *= 5;
  649.                                 else if (_pointsFormula == PointsFormula::RoundUpToMultipleOfFifty)
  650.                                         obj.points = (obj.points + 49) / 50 * 50;
  651.                                 player.score += obj.points;
  652.                                 jjPARTICLE@ particle = jjAddParticle(PARTICLE::STRING);
  653.                                 if (particle !is null) {
  654.                                         particle.xPos = obj.xPos;
  655.                                         particle.yPos = obj.yPos;
  656.                                         particle.xSpeed = (-32768 - int(jjRandom() & 0x3FFF)) / 65536.f;
  657.                                         particle.ySpeed = (-65536 - int(jjRandom() & 0x7FFF)) / 65536.f;
  658.                                         particle.string.text = formatInt(obj.points);
  659.                                 }
  660.                                 obj.points = 0;
  661.                                 return true;
  662.                         }
  663.                         return false;
  664.                 }
  665.                 private void _fireBullets(const jjOBJ@ obj) const {
  666.                         for (uint i = 0; i < _bullets.length; ++i)
  667.                                 if (-obj.var[_objVar::DirectionCurrent] != _bullets[i].direction) { //compatible with the object's current direction
  668.                                         jjOBJ@ bullet = _bullets[i].fire(obj, _explosionSound, _scaleBulletSpeed ? _scale : 1.f, _scale);
  669.                                         bullet.curAnim = (!_useTrueColor ? 0 : (_firstFrameTrueColor + (bullet.curFrame - _firstFramePaletted) * TrueColor::NumberOfFramesPerImage));
  670.                                 }
  671.                         if (_fireSound >= 0)
  672.                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(_fireSound));
  673.                 }
  674.                
  675.                
  676.                 int _getPresetRelativeAnimID(int relativeAnimID) const {
  677.                         if (relativeAnimID < 0) //negative numbers mean generic Jazz1 animations
  678.                                 return _getAnimSet(Enemies::_Misc_Anims, _scale).animID - 1 - relativeAnimID;
  679.                         else if (relativeAnimID < 20) //small positive numbers mean animations specific to this Jazz1 enemy
  680.                                 return preset.curAnim + relativeAnimID;
  681.                         else //large positive numbers are common JJ2 animations
  682.                                 return relativeAnimID;
  683.                 }
  684.                
  685.                 protected bool
  686.                         _supportsSpeed = false,
  687.                         _supportsDirection = false,
  688.                         _supportsFire = false,
  689.                         _flipSpriteWhenMovingLeft = false,
  690.                         _scaleBulletSpeed = false;
  691.                 protected float _scaledObjectSpeed = 1.f;
  692.                
  693.                
  694.                 protected int _adjustObjectDirection(jjOBJ@ obj) {
  695.                         switch (_direction) {
  696.                                 case Directions::Right:
  697.                                         obj.var[_objVar::DirectionCurrent] = 1;
  698.                                         break;
  699.                                 case Directions::Left:
  700.                                         obj.var[_objVar::DirectionCurrent] = -1;
  701.                                         break;
  702.                                 case Directions::FaceJazz:
  703.                                         obj.var[_objVar::DirectionCurrent] = (obj.xPos > jjLocalPlayers[0].xPos) ? -1 : 1;
  704.                                         break;
  705.                                 case Directions::Random:
  706.                                         obj.var[_objVar::DirectionCurrent] = int(jjRandom() & 1) * 2 - 1;
  707.                                         break;
  708.                                 default:
  709.                                         obj.var[_objVar::DirectionCurrent] = _getParameter(obj, _direction, 1) * 2 - 1;
  710.                                         break;
  711.                         }
  712.                         return obj.var[_objVar::DirectionCurrent];
  713.                 }
  714.                        
  715.                 protected int _direction = Directions::Right;
  716.                 Enemy@ SetDirection(int dir) {
  717.                         if (_supportsDirection)
  718.                                 _direction = dir;
  719.                         else
  720.                                 jjDebug(_behaviorName + " does not support the SetDirection method.");
  721.                         return this;
  722.                 }
  723.                 protected void _reverseDirection(jjOBJ@ obj) {
  724.                         obj.var[_objVar::DirectionCurrent] = -obj.var[_objVar::DirectionCurrent];
  725.                 }
  726.                
  727.                 protected float _speed;
  728.                 private int _speedParamOffset = -1;
  729.                 private uint _speedParamLength;
  730.                 Enemy@ SetSpeed(float speed) {
  731.                         if (_supportsSpeed) {
  732.                                 _speed = abs(speed);
  733.                                 _speedParamOffset = -1;
  734.                         } else
  735.                                 jjDebug(_behaviorName + " does not support the SetSpeed* methods.");
  736.                         return this;
  737.                 }
  738.                 Enemy@ SetSpeedBasedOnParameter(uint o, uint l, float m = 1.0f) {
  739.                         if (_supportsSpeed) {
  740.                                 _speed = abs(m);
  741.                                 _speedParamOffset = o;
  742.                                 _speedParamLength = l;
  743.                         } else
  744.                                 jjDebug(_behaviorName + " does not support the SetSpeed* methods.");
  745.                         return this;
  746.                 }
  747.                
  748.                 protected uint _fireDelay = 0;
  749.                 protected array<_bulletPreferences> _bullets(0);
  750.                 Enemy@ SetFireDelay(uint delay) {
  751.                         if (_supportsFire)
  752.                                 _fireDelay = delay;
  753.                         else
  754.                                 jjDebug(_behaviorName + " does not support the SetFireDelay method.");
  755.                         return this;
  756.                 }
  757.                
  758.                 Enemy@ SetEnemySpeedsAreScalable(bool setTo) {
  759.                         if (_supportsSpeed)
  760.                                 _scaledObjectSpeed = setTo ? _scale : 1.f;
  761.                         else
  762.                                 jjDebug(_behaviorName + " does not support the SetEnemySpeedsAreScalable method.");
  763.                         return this;
  764.                 }
  765.                 Enemy@ SetBulletSpeedsAreScalable(bool setTo) {
  766.                         if (_supportsFire)
  767.                                 _scaleBulletSpeed = setTo;
  768.                         else
  769.                                 jjDebug(_behaviorName + " does not support the SetBulletSpeedsAreScalable method.");
  770.                         return this;
  771.                 }
  772.                
  773.                 protected int _deathSound = -1, _fireSound = -1, _explosionSound = -1;
  774.                 Enemy@ SetDeathSound(SOUND::Sample sample) {
  775.                         _deathSound = sample;
  776.                         return this;
  777.                 }
  778.                 Enemy@ SetBulletFireSound(SOUND::Sample sample) {
  779.                         if (_supportsFire)
  780.                                 _fireSound = sample;
  781.                         else
  782.                                 jjDebug(_behaviorName + " does not support the SetBulletFireSound method.");
  783.                         return this;
  784.                 }
  785.                 Enemy@ SetBulletExplosionSound(SOUND::Sample sample) {
  786.                         if (_supportsFire)
  787.                                 _explosionSound = sample;
  788.                         else
  789.                                 jjDebug(_behaviorName + " does not support the SetBulletExplosionSound method.");
  790.                         return this;
  791.                 }
  792.                
  793.                 private _causeOfDeath _died = _causeOfDeath::Bullet;
  794.                 protected bool _killAnimExplosion = false;
  795.                 protected uint _killAnimRepetitionCounts = 1;
  796.                 protected bool _useJJ2DeathAnimation = false;
  797.                 Enemy@ SetUsesJJ2StyleDeathAnimation(bool setTo) {
  798.                         _useJJ2DeathAnimation = setTo;
  799.                         return this;
  800.                 }
  801.                
  802.                 Enemy@ SetWalkingEnemyCliffReaction(CliffReaction) { //overridden by Walker
  803.                         jjDebug(_behaviorName + " is not a walking enemy.");
  804.                         return this;
  805.                 }
  806.                
  807.                 private PointsFormula _pointsFormula;
  808.                 Enemy@ SetPointsFormula(PointsFormula pointsFormula) {
  809.                         _pointsFormula = pointsFormula;
  810.                         return this;
  811.                 }
  812.                
  813.                 uint GetFirstFrame(bool useTrueColor = false) const {
  814.                         return !useTrueColor ? _firstFramePaletted : _firstFrameTrueColor;
  815.                 }
  816.         }
  817.        
  818.         Enemy@ MakeEnemy(uint8 eventID, Enemies enemyID, bool useTrueColor = false, float scale = 1.f, uint resizeMethod = Resize::Method::NearestNeighbor) {
  819.                 if (enemyID <= Enemies::_Misc_Anims || enemyID >= Enemies::LAST) {
  820.                         jjDebug("Invalid Jazz1::Enemies enum value " + enemyID + ".");
  821.                         return null;
  822.                 }
  823.                
  824.                 jjOBJ@ preset = jjObjectPresets[eventID];
  825.                 EnemyArguments arg(preset, enemyID, useTrueColor, scale, resizeMethod);
  826.                
  827.                 switch (enemyID) {
  828.                         case Enemies::Diamondus_TurtleGoon:                             Diamondus_TurtleGoon                    (arg);  break;
  829.                         case Enemies::Diamondus_BumblingBee:                    Diamondus_BumblingBee                   (arg);  break;
  830.                         case Enemies::Tubelectric_BlasterHorizontal:    Tubelectric_BlasterHorizontal   (arg);  break;
  831.                         case Enemies::Tubelectric_BlasterVertical:              Tubelectric_BlasterVertical             (arg);  break;
  832.                         case Enemies::Tubelectric_Spark:                                Tubelectric_Spark                               (arg);  break;
  833.                         case Enemies::Tubelectric_SparkBarrier:                 Tubelectric_SparkBarrier                (arg);  break;
  834.                         case Enemies::Medivo_GhostRapierHorizontal:             Medivo_GhostRapierHorizontal    (arg);  break;
  835.                         case Enemies::Medivo_GhostRapierVertical:               Medivo_GhostRapierVertical              (arg);  break;
  836.                         case Enemies::Medivo_Helmut:                                    Medivo_Helmut                                   (arg);  break;
  837.                         case Enemies::Letni_Bug:                                                Letni_Bug                                               (arg);  break;
  838.                         case Enemies::Letni_BugCeiling:                                 Letni_BugCeiling                                (arg);  break;
  839.                         case Enemies::Letni_ElecBarrier:                                Letni_ElecBarrier                               (arg);  break;
  840.                         case Enemies::Technoir_MiniMine:                                Technoir_MiniMine                               (arg);  break;
  841.                         case Enemies::Technoir_Misfire:                                 Technoir_Misfire                                (arg);  break;
  842.                         case Enemies::Technoir_TanketyTankTank:                 Technoir_TanketyTankTank                (arg);  break;
  843.                         case Enemies::Orbitus_BeholderPurple:                   Orbitus_BeholderPurple                  (arg);  break;
  844.                         case Enemies::Orbitus_BeholderSilver:                   Orbitus_BeholderSilver                  (arg);  break;
  845.                         case Enemies::Orbitus_SilverSnake:                              Orbitus_SilverSnake                             (arg);  break;
  846.                         case Enemies::Fanolint_FlyFlower:                               Fanolint_FlyFlower                              (arg);  break;
  847.                         case Enemies::Fanolint_PottedPlant:                             Fanolint_PottedPlant                    (arg);  break;
  848.                         case Enemies::Fanolint_SuperTankety:                    Fanolint_SuperTankety                   (arg);  break;
  849.                         case Enemies::Scraparap_GunnerDrone:                    Scraparap_GunnerDrone                   (arg);  break;
  850.                         case Enemies::Scraparap_LaunchCart:                             Scraparap_LaunchCart                    (arg);  break;
  851.                         case Enemies::Scraparap_RoboTurtleDrone:                Scraparap_RoboTurtleDrone               (arg);  break;
  852.                         case Enemies::Megairbase_Doofusguard:                   Megairbase_Doofusguard                  (arg);  break;
  853.                         case Enemies::Megairbase_Missile:                               Megairbase_Missile                              (arg);  break;
  854.                         case Enemies::Megairbase_SuperSpark:                    Megairbase_SuperSpark                   (arg);  break;
  855.                         case Enemies::Turtemple_JeTurtle:                               Turtemple_JeTurtle                              (arg);  break;
  856.                         case Enemies::Turtemple_ScorpWeenie:                    Turtemple_ScorpWeenie                   (arg);  break;
  857.                         case Enemies::Nippius_SkatePen:                                 Nippius_SkatePen                                (arg);  break;
  858.                         case Enemies::Nippius_SkiTurtle:                                Nippius_SkiTurtle                               (arg);  break;
  859.                         case Enemies::Nippius_SnowGoon:                                 Nippius_SnowGoon                                (arg);  break;
  860.                         case Enemies::Jungrock_JetSnake:                                Jungrock_JetSnake                               (arg);  break;
  861.                         case Enemies::Jungrock_RedBuzzer:                               Jungrock_RedBuzzer                              (arg);  break;
  862.                         case Enemies::Jungrock_YellowBuzzer:                    Jungrock_YellowBuzzer                   (arg);  break;
  863.                         case Enemies::Marbelara_Drageen:                                Marbelara_Drageen                               (arg);  break;
  864.                         case Enemies::Marbelara_Firebomb:                               Marbelara_Firebomb                              (arg);  break;
  865.                         case Enemies::Marbelara_Schwarzenguard:                 Marbelara_Schwarzenguard                (arg);  break;
  866.                         case Enemies::Sluggion_Dragoon:                                 Sluggion_Dragoon                                (arg);  break;
  867.                         case Enemies::Sluggion_RedBat:                                  Sluggion_RedBat                                 (arg);  break;
  868.                         case Enemies::Sluggion_Sluggi:                                  Sluggion_Sluggi                                 (arg);  break;
  869.                         case Enemies::Dreempipes_Minite:                                Dreempipes_Minite                               (arg);  break;
  870.                         case Enemies::Dreempipes_Overgrown:                             Dreempipes_Overgrown                    (arg);  break;
  871.                         case Enemies::Dreempipes_TerrapinSwimmer:               Dreempipes_TerrapinSwimmer              (arg);  break;
  872.                         case Enemies::Pezrox_ClammyHorizontal:                  Pezrox_ClammyHorizontal                 (arg);  break;
  873.                         case Enemies::Pezrox_ClammyVertical:                    Pezrox_ClammyVertical                   (arg);  break;
  874.                         case Enemies::Pezrox_GreenSnake:                                Pezrox_GreenSnake                               (arg);  break;
  875.                         case Enemies::Crysilis_GoldenBounceSpike:               Crysilis_GoldenBounceSpike              (arg);  break;
  876.                         case Enemies::Crysilis_LooGuard:                                Crysilis_LooGuard                               (arg);  break;
  877.                         case Enemies::Battleships_ArmorDoofi:                   Battleships_ArmorDoofi                  (arg);  break;
  878.                         case Enemies::Battleships_BounceSpike:                  Battleships_BounceSpike                 (arg);  break;
  879.                         case Enemies::Battleships_Generator:                    Battleships_Generator                   (arg);  break;
  880.                         case Enemies::Battleships_SuperBee:                             Battleships_SuperBee                    (arg);  break;
  881.                         case Enemies::Holidaius_BlueDog:                                Holidaius_BlueDog                               (arg);  break;
  882.                         case Enemies::Holidaius_Devil:                                  Holidaius_Devil                                 (arg);  break;
  883.                         case Enemies::Holidaius_HandHorizontal:                 Holidaius_HandHorizontal                (arg);  break;
  884.                         case Enemies::Holidaius_HandVertical:                   Holidaius_HandVertical                  (arg);  break;
  885.                         case Enemies::Holidaius_SkiTurtle:                              Holidaius_SkiTurtle                             (arg);  break;
  886.                         case Enemies::Holidaius_SnowMonkey:                             Holidaius_SnowMonkey                    (arg);  break;
  887.                         default: return null;
  888.                 }
  889.                
  890.                 return cast<Enemy>(cast<jjBEHAVIORINTERFACE>(preset.behavior));
  891.         }
  892.        
  893.        
  894.         /*
  895.         import glob, os
  896.         from PIL import Image
  897.         os.chdir("D:\\Games\Jazz2")
  898.         for file in glob.glob("Jazz1Enemies*.bmp"):
  899.                 Image.open(file).save(file[:-4] + ".png")
  900.         */
  901.        
  902.        
  903.         abstract class Walker : Enemy {
  904.                 protected CliffReaction _cliffReaction = CliffReaction::TurnAround;
  905.                 Walker(const EnemyArguments &in arg) {
  906.                         super(arg);
  907.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  908.                 }
  909.                 void myBehave(jjOBJ@ obj) const override {
  910.                         if (obj.state == STATE::START) {
  911.                                 _adjustObjectDirection(obj);
  912.                                 obj.state = STATE::WALK;
  913.                                 obj.putOnGround(true);
  914.                                 obj.special = jjMaskedTopVLine(int(obj.xPos), int(obj.yPos), 64); //properDistanceFromGround
  915.                         }
  916.                        
  917.                         const int direction = obj.var[_objVar::DirectionCurrent];
  918.                         const int spriteWidth = jjAnimFrames[obj.curFrame].width / 2;
  919.                         const float positionForwards = obj.xPos + direction * spriteWidth;
  920.                         const float positionBackwards = obj.xPos - direction * spriteWidth;
  921.                         int distanceFromGroundForwards = jjMaskedTopVLine(int(positionForwards), int(obj.yPos), 64);
  922.                         int distanceFromGroundBackwards = jjMaskedTopVLine(int(positionBackwards), int(obj.yPos), 64);
  923.                        
  924.                         if (jjEventGet(int(positionForwards) / 32, int(obj.yPos) / 32) == AREA::STOPENEMY) {
  925.                                 _reverseDirection(obj); //it takes a tick to turn around
  926.                         } else {
  927.                                 if (distanceFromGroundForwards > obj.special && distanceFromGroundBackwards > obj.special) { //in the air
  928.                                         obj.yPos += obj.ySpeed += 0.125f;
  929.                                         if (obj.ySpeed > 4.f) obj.ySpeed = 4;
  930.                                         distanceFromGroundForwards = jjMaskedTopVLine(int(positionForwards), int(obj.yPos), 64);
  931.                                         distanceFromGroundBackwards = jjMaskedTopVLine(int(positionBackwards), int(obj.yPos), 64);
  932.                                         int currentDistanceFromGround = (distanceFromGroundForwards < distanceFromGroundBackwards) ? distanceFromGroundForwards : distanceFromGroundBackwards;
  933.                                         if (currentDistanceFromGround <= obj.special) { //landed
  934.                                                 obj.yPos -= obj.special - currentDistanceFromGround;
  935.                                                 distanceFromGroundForwards = obj.special; //don't instantly turn around
  936.                                                 obj.ySpeed = 0;
  937.                                         }
  938.                                         if (_cliffReaction != CliffReaction::Careen)
  939.                                                 return; //no horizontal movement
  940.                                 }
  941.                                
  942.                                 if (distanceFromGroundForwards < obj.special) {
  943.                                         _reverseDirection(obj);
  944.                                 } else if (distanceFromGroundForwards > obj.special && _cliffReaction == CliffReaction::TurnAround) {
  945.                                         _reverseDirection(obj);
  946.                                 } else {
  947.                                         obj.xPos += direction * obj.xAcc;
  948.                                 }
  949.                         }
  950.                 }
  951.                
  952.                 Enemy@ SetWalkingEnemyCliffReaction(CliffReaction setTo) override {
  953.                         _cliffReaction = setTo;
  954.                         return this;
  955.                 }
  956.         }
  957.        
  958.         const array<array<int8>> _paths = {
  959.                 {28,23,28,22,27,20,26,18,25,16,25,15,24,15,24,14,23,14,23,12,22,9,22,7,22,6,22,5,22,4,22,3,23,3,23,2,24,2,24,1,24,0,25,0,25,-1,25,-2,26,-2,26,-3,26,-4,27,-4,27,-5,28,-5,28,-6,28,-7,28,-8,29,-8,29,-9,29,-10,30,-10,30,-11,30,-12,29,-13,29,-14,28,-14,28,-15,27,-16,26,-17,25,-17,25,-18,24,-18,23,-19,22,-19,22,-20,21,-20,21,-21,21,-22,21,-23,21,-24,22,-25,23,-25,24,-26,25,-26,26,-26,26,-27,27,-27,28,-27,29,-27,29,-26,30,-26,31,-26,31,-25,32,-25,33,-24,33,-23,34,-23,34,-22,34,-21,34,-20,34,-19,34,-18,33,-18,32,-17,31,-17,30,-16,29,-16,28,-15,27,-14,26,-14,26,-13,25,-13,24,-12,23,-11,22,-11,22,-10,21,-10,21,-8,20,-8,20,-7,20,-6,19,-5,19,-4,19,-3,19,-2,20,-1,21,-1,21,0,22,0,22,2,23,4,24,5,25,5,25,6,26,6,26,7,27,7,28,8,29,8,29,10,30,11,30,12,31,12,31,13,32,13,32,14,33,14,33,15,34,15,35,16,36,17,37,17,37,18,38,18,38,19,38,20,38,21,38,22,38,23,38,24,38,25,37,25,37,26,36,26,35,26,35,27,34,27,33,27,32,27,31,27,30,27,29,27,28,27,28,26,27,26,27,25,27,24,26,24,26,23,27,23}, //bee
  960.                 {20,37,20,36,20,35,19,35,19,34,19,33,18,33,18,32,18,31,18,30,19,29,19,28,19,27,19,26,19,25,19,23,19,22,19,21,19,20,19,19,19,18,19,17,19,16,19,15,19,14,19,13,19,12,19,11,19,10,20,9,20,8,20,7,20,6,19,5,19,4,19,3,19,2,19,1,18,0,18,-1,18,-2,18,-3,18,-4,17,-5,17,-6,17,-7,17,-8,17,-10,17,-12,17,-13,17,-15,17,-17,17,-18,17,-19,17,-20,17,-21,17,-22,17,-23,17,-24,18,-24,18,-25,19,-26,20,-26,21,-27,22,-27,23,-28,24,-28,25,-28,26,-27,27,-27,27,-26,28,-25,28,-24,28,-23,28,-22,28,-21,28,-20,29,-20,29,-19,29,-18,29,-17,29,-15,29,-14,29,-13,29,-12,29,-11,29,-10,29,-9,30,-9,30,-8,30,-7,30,-6,30,-4,30,-3,30,-1,30,0,30,2,30,3,30,4,30,5,29,5,29,6,29,7,28,7,28,8,28,9,28,10,28,11,28,12,28,13,28,14,27,14,27,15,27,16,27,17,27,18,27,19,27,20,27,21,27,22,27,23,28,23,28,24,28,25,29,25,29,26,29,27,28,28,28,29,28,30,27,30,27,31,26,32,25,33,24,33,24,34,24,35,24,36,23,36,23,37,23,38,22,38,21,38,20,38,20,37,19,37,19,36}, //rapier
  961.                 {22,4,22,3,22,2,22,1,22,0,22,-1,22,-2,22,-3,22,-4,22,-5,22,-6,22,-7,22,-8,22,-9,22,-10,22,-11,22,-12,22,-13,22,-14,22,-15,22,-16,22,-17,22,-18,22,-19,22,-20,22,-19,22,-18,22,-17,22,-16,22,-15,22,-14,22,-13,22,-12,22,-11,22,-10,22,-9,22,-8,22,-7,22,-6,22,-5,22,-4,22,-3,22,-2,22,-1,22,0,22,1,22,2,22,3,22,4,22,5,22,6,22,7,22,8,22,9,22,10,22,12,22,13,22,14,22,15,22,16,22,17,22,18,22,19,22,20,22,21,22,22,22,23,22,24,22,25,22,26,22,27,22,28,22,29,22,30,22,31,22,30,22,29,22,28,22,27,22,26,22,25,22,23,21,22,21,20,21,19,21,18,21,17,21,16,21,15,21,14,21,13,21,12,21,11,21,10,21,9,21,8,21,7,21,6,21,5,21,4}, //silversnake
  962.                 {9,10,9,9,9,8,9,7,9,6,9,5,9,4,9,3,9,2,9,1,9,0,9,-1,9,-2,9,-3,9,-4,9,-5,9,-6,9,-7,9,-8,9,-9,9,-10,10,-10,10,-11,11,-12,12,-12,12,-13,13,-14,14,-14,15,-15,16,-15,17,-16,18,-16,19,-16,20,-16,21,-16,22,-16,23,-16,24,-16,25,-16,26,-16,27,-16,28,-16,29,-15,30,-15,30,-14,30,-13,30,-12,30,-11,30,-10,30,-9,30,-8,30,-7,30,-6,30,-5,30,-4,30,-3,30,-2,29,-1,29,0,29,1,29,2,28,3,28,4,28,5,28,6,28,7,28,8,28,9,28,10,27,11,27,12,27,13,26,13,25,14,24,15,23,15,22,15,21,16,20,16,19,16,18,16,17,16,16,16,15,16,14,16,13,16,12,16,11,16,10,15,9,15,8,15,8,14,8,13,8,12,8,11}, //devil
  963.                 {24,-5,23,-5,23,-7,23,-8,24,-8,24,-9,24,-10,24,-11,24,-12,24,-14,24,-16,24,-19,24,-21,24,-23,23,-26,23,-28,23,-31,23,-33,23,-36,23,-39,23,-43,23,-47,23,-50,23,-53,23,-54,23,-56,23,-57,23,-58,24,-58,24,-59,24,-60,25,-62,25,-63,25,-65,25,-66,25,-67,26,-67,26,-68,27,-68,27,-69,28,-69,28,-70,29,-70,30,-70,30,-71,31,-71,32,-72,33,-72,34,-72,35,-72,36,-72,37,-72,37,-71,38,-71,38,-70,39,-70,39,-69,40,-67,40,-65,40,-62,40,-60,40,-57,40,-53,40,-49,40,-45,40,-40,40,-34,40,-28,40,-21,41,-14,41,-7,41,0,41,7,41,13,41,20,41,25,41,28,41,33,42,37,42,40,43,42,43,45,43,46,43,47,43,48,42,48,42,49,41,49,41,50,41,51,40,51,40,52,39,52,39,53,38,53,37,53,36,53,35,53,34,53,33,53,33,52,32,52,31,52,31,51,30,51,30,50,29,50,29,49,28,48,28,47,27,47,27,46,27,44,26,42,26,40,26,37,26,35,25,33,25,32,25,30,25,27,25,24,25,22,25,19,24,17,24,15,24,13,23,12,23,11,23,10,23,9,23,8,23,7,23,6,23,5,23,4,23,3,23,2,23,1,23,0,23,-1,23,-2,23,-3,23,-4}, //fanflower hard
  964.                 {58,-21,57,-20,56,-19,55,-18,54,-17,53,-16,52,-15,51,-15,50,-14,50,-13,49,-13,49,-12,49,-11,48,-11,48,-10,48,-9,48,-8,48,-7,48,-6,49,-6,50,-5,51,-5,52,-4,53,-3,54,-3,54,-2,55,-2,56,-1,57,-1,57,0,58,0,58,1,59,1,59,2,60,3,60,4,61,4,61,5,61,6,61,7,61,8,60,8,60,9,59,10,58,11,58,12,57,12,56,12,56,13,55,13,54,14,53,14,53,15,52,15,52,16,51,16,50,16,49,16,48,16,47,16,46,16,45,16,45,15,44,15,43,15,43,14,42,14,42,13,42,12,42,11,43,11,44,10,44,9,45,9,45,8,46,8,46,7,47,7,47,6,48,6,49,6,50,5,51,5,51,4,52,4,53,4,53,3,54,3,55,3,55,2,56,2,56,1,57,1,57,0,58,0,58,-1,59,-1,59,-2,60,-2,60,-3,61,-3,61,-4,62,-4,62,-5,63,-6,63,-7,64,-7,64,-8,64,-9,65,-9,65,-10,65,-11,65,-12,65,-13,66,-13,66,-14,66,-15,66,-16,66,-17,66,-18,65,-18,65,-19,64,-20,64,-21,63,-21,63,-22,62,-22,61,-22,60,-22,59,-22,59,-21,58,-21}, //fanflower easy
  965.                 {23,-2, 23,-3, 23,-4, 23,-6, 23,-8, 23,-9, 23,-10, 23,-11, 23,-12, 23,-13, 23,-14, 23,-15, 23,-16, 23,-17, 23,-18, 23,-19, 23,-20, 23,-21, 23,-22, 23,-23, 23,-24, 23,-25, 23,-24, 24,-24, 24,-23, 24,-22, 24,-21, 24,-20, 24,-19, 24,-18, 24,-17, 24,-16, 24,-14, 24,-12, 24,-11, 24,-10, 24,-9, 24,-7, 24,-6, 24,-4, 24,-3, 24,-1, 24,0, 24,1, 24,2, 24,3, 24,4, 24,5, 24,6, 24,7, 24,8, 24,9, 24,10, 24,12, 24,13, 24,14, 24,15, 24,16, 24,17, 24,18, 24,19, 24,20, 24,21, 24,22, 24,23, 24,24, 24,25, 24,26, 24,27, 24,28, 24,29, 24,30, 24,31, 24,32, 24,33, 24,34, 24,35, 24,36, 23,36, 22,36, 21,35, 20,35, 19,35, 19,34, 18,33, 17,32, 17,31, 16,30, 16,29, 16,28, 16,27, 16,26, 16,25, 16,24, 16,23, 17,22, 17,21, 18,20, 19,19, 20,17, 21,16, 22,14, 23,12, 24,12, 24,11, 24,10, 24,9, 24,8, 25,7, 25,6, 26,5, 26,4, 27,2, 28,0, 29,-2, 29,-4, 29,-6, 29,-7, 29,-8, 29,-9, 29,-10, 29,-11, 29,-12, 29,-13, 29,-15, 29,-16, 29,-17, 29,-18, 29,-19, 29,-20, 28,-22, 28,-23, 27,-26, 26,-28, 25,-29, 25,-30, 25,-31, 24,-31, 23,-31, 23,-30, 22,-30, 21,-29, 20,-29, 20,-28, 20,-27, 20,-26, 20,-25, 20,-24, 20,-23, 20,-22, 20,-21, 20,-20, 20,-19, 20,-18, 20,-17, 20,-16, 20,-15, 20,-14, 20,-13, 20,-12, 20,-11, 20,-10, 20,-9, 20,-8, 20,-7, 20,-6, 20,-5, 20,-4, 20,-3, 20,-2, 20,-1, 20,0, 20,-1, 20,-2}, //redbuz
  966.                 {18,5, 19,4, 19,2, 20,1, 20,-1, 21,-2, 21,-4, 22,-5, 22,-7, 23,-8, 23,-10, 24,-11, 24,-13, 25,-14, 25,-16, 26,-17, 26,-19, 27,-20, 27,-22, 28,-23, 28,-25, 28,-27, 30,-27, 31,-28, 32,-29, 33,-29, 35,-30, 36,-31, 37,-31, 38,-32, 40,-33, 41,-33, 42,-34, 42,-34, 44,-34, 45,-33, 47,-33, 48,-32, 48,-31, 48,-30, 49,-28, 49,-27, 50,-25, 50,-24, 49,-23, 49,-21, 49,-19, 49,-17, 50,-16, 50,-14, 50,-12, 50,-10, 50,-8, 49,-7, 48,-6, 48,-4, 48,-3, 47,-1, 47,1, 47,2, 46,4, 46,5, 46,7, 45,8, 45,10, 43,10, 43,12, 42,13, 41,14, 41,15, 40,17, 39,18, 39,19, 38,21, 38,22, 37,23, 36,24, 36,26, 35,27, 32,26, 31,27, 29,27, 27,27, 26,27, 24,28, 23,28, 21,28, 18,27, 17,26, 15,26, 13,23, 13,22, 12,20, 12,19, 12,17, 12,16, 13,14, 14,13, 15,12, 15,11, 16,10, 17,8, 17,7, 18,6}, //marbel
  967.                 {23,16, 23,15, 22,15, 22,14, 21,14, 21,13, 21,12, 20,12, 20,11, 20,10, 19,10, 19,9, 19,8, 19,7, 19,6, 19,5, 19,4, 20,4, 20,3, 21,3, 21,2, 22,2, 23,2, 23,1, 24,1, 25,0, 25,-1, 26,-1, 26,-2, 26,-3, 26,-4, 26,-5, 26,-6, 26,-7, 26,-8, 25,-8, 25,-9, 24,-9, 24,-10, 23,-10, 22,-11, 21,-12, 20,-12, 20,-13, 19,-14, 20,-14, 20,-15, 21,-15, 22,-15, 23,-15, 24,-15, 24,-16, 25,-16, 26,-16, 26,-15, 27,-15, 28,-15, 29,-15, 29,-14, 30,-14, 30,-13, 30,-12, 31,-12, 31,-11, 31,-10, 31,-9, 30,-8, 29,-8, 29,-7, 28,-7, 27,-7, 27,-6, 26,-6, 25,-6, 24,-6, 23,-5, 22,-5, 21,-5, 21,-4, 20,-4, 19,-4, 19,-3, 19,-2, 19,-1, 20,-1, 20,0, 21,0, 21,1, 22,1, 22,2, 23,2, 24,2, 25,2, 26,2, 26,3, 27,3, 28,3, 28,4, 29,4, 30,5, 31,5, 31,6, 31,7, 31,8, 30,8, 30,9, 29,9, 29,10, 28,10, 27,10, 26,11, 25,11, 24,11, 24,12, 23,12, 22,13, 21,13, 21,14, 20,14, 20,15, 20,16, 19,16, 19,17, 19,18, 19,19, 19,20, 19,21, 20,22, 21,22, 21,23, 22,23, 23,23, 24,23, 25,23, 26,23, 26,22, 27,22, 27,21, 27,20, 26,20, 26,19, 25,19, 25,18, 24,17, 24,16}, //sluggy
  968.                 {53,17, 53,16, 53,15, 53,14, 53,13, 53,12, 53,11, 53,10, 53,9, 53,8, 53,6, 54,6, 54,5, 54,4, 54,3, 54,2, 54,1, 55,0, 55,-1, 55,-2, 55,-3, 55,-4, 55,-5, 55,-6, 55,-8, 55,-10, 55,-11, 55,-12, 55,-13, 55,-14, 56,-14, 56,-15, 56,-16, 56,-17, 56,-18, 56,-19, 57,-19, 57,-20, 57,-21, 58,-21, 59,-21, 60,-22, 61,-22, 62,-22, 63,-23, 64,-23, 65,-23, 66,-23, 67,-23, 68,-23, 69,-23, 70,-23, 70,-22, 71,-22, 72,-22, 73,-22, 74,-22, 74,-21, 74,-20, 74,-19, 74,-18, 74,-17, 74,-15, 74,-14, 73,-12, 73,-10, 73,-8, 73,-6, 73,-4, 73,-2, 72,0, 72,3, 72,5, 71,8, 71,12, 71,15, 71,19, 71,23, 70,26, 70,30, 70,32, 70,33, 70,34, 69,35, 69,36, 69,37, 68,37, 67,37, 66,37, 65,37, 64,37, 63,37, 62,37, 62,36, 61,36, 60,36, 59,36, 58,36, 57,36, 56,36, 55,36, 54,35, 53,35, 52,35, 52,34, 52,33, 52,31, 52,30, 53,29, 53,28, 53,27, 53,26, 53,25, 53,24, 53,23, 53,22, 53,21, 53,20, 53,19, 53,18, 53,17}, //batred
  969.                 {12,21, 12,20, 13,19, 13,18, 13,17, 13,16, 13,15, 13,14, 13,13, 13,12, 14,11, 14,10, 15,10, 15,9, 16,9, 16,8, 17,8, 17,7, 17,6, 18,5, 18,4, 19,4, 19,3, 20,2, 20,1, 20,0, 21,-1, 21,-2, 22,-3, 22,-4, 22,-5, 22,-6, 23,-8, 23,-9, 23,-10, 23,-11, 23,-12, 23,-13, 23,-14, 23,-15, 23,-16, 23,-17, 23,-18, 23,-19, 23,-20, 22,-20, 21,-20, 20,-20, 19,-19, 18,-18, 17,-17, 16,-17, 15,-16, 14,-15, 14,-14, 13,-13, 13,-12, 13,-11, 13,-10, 13,-9, 13,-8, 13,-7, 13,-6, 13,-5, 13,-4, 13,-3, 13,-2, 13,-1, 13,0, 14,1, 14,2, 15,3, 16,4, 17,5, 18,5, 19,6, 20,7, 21,8, 22,9, 23,11, 24,12, 25,13, 26,14, 26,15, 26,16, 26,17, 26,19, 26,20, 26,21, 26,23, 26,24, 26,25, 26,27, 26,28, 26,31, 26,32, 26,34, 26,35, 26,37, 25,38, 25,39, 25,40, 25,41, 24,42, 24,43, 23,44, 22,44, 21,45, 21,46, 20,46, 20,47, 19,47, 18,48, 17,48, 16,48, 15,49, 14,49, 13,49, 12,49, 11,49, 11,48, 10,48, 10,47, 10,46, 10,45, 10,44, 10,43, 10,42, 11,41, 11,40, 12,39, 13,39, 13,38, 13,37, 13,36, 14,36, 14,35, 14,34, 14,33, 14,32, 14,31, 14,30, 14,29, 14,28, 14,27, 14,26, 14,25, 14,24, 14,23, 14,22}, //loogies
  970.                 {20,18, 19,17, 18,16, 18,15, 17,15, 17,14, 16,14, 16,13, 15,12, 15,11, 15,10, 15,9, 15,7, 15,5, 15,2, 15,0, 16,-2, 16,-4, 17,-4, 18,-5, 19,-5, 19,-7, 20,-7, 20,-8, 21,-8, 21,-9, 22,-9, 22,-10, 23,-10, 23,-11, 23,-12, 24,-12, 24,-13, 24,-14, 24,-15, 24,-16, 24,-17, 24,-19, 24,-21, 24,-23, 24,-24, 24,-25, 24,-26, 23,-26, 22,-27, 21,-27, 21,-28, 20,-28, 19,-28, 19,-29, 18,-29, 17,-29, 16,-29, 15,-29, 14,-29, 13,-29, 12,-28, 11,-28, 10,-28, 10,-27, 9,-27, 9,-26, 8,-26, 8,-25, 7,-25, 7,-24, 7,-23, 7,-22, 7,-21, 7,-20, 8,-20, 8,-19, 8,-18, 9,-18, 10,-17, 10,-16, 11,-16, 11,-15, 12,-14, 13,-14, 13,-13, 14,-13, 15,-12, 16,-12, 16,-11, 17,-11, 18,-11, 18,-10, 19,-10, 20,-9, 21,-8, 22,-8, 22,-7, 23,-7, 23,-5, 24,-4, 24,-3, 25,-3, 25,-2, 26,-2, 27,-1, 27,1, 28,2, 28,5, 29,7, 29,9, 30,11, 30,12, 31,12, 31,13, 31,14, 30,14, 30,15, 30,16, 29,16, 28,17, 27,17, 26,17, 26,18, 25,18, 24,18, 23,18, 22,18, 21,18} //battlebee
  971.         };
  972.         abstract class PathFollower : Enemy {
  973.                 protected const array<int8>@ _path;
  974.                 PathFollower(const EnemyArguments &in arg, int pathID) {
  975.                         super(arg);
  976.                         @_path = _paths[pathID];
  977.                         preset.yAcc = 0; //distance moved
  978.                         _flipSpriteWhenMovingLeft = _supportsSpeed = true;
  979.                 }
  980.                 void myBehave(jjOBJ@ obj) const override {
  981.                         if (obj.state == STATE::START) {
  982.                                 _adjustObjectDirection(obj);
  983.                                 obj.state = STATE::FLY;
  984.                                 obj.xAcc /= 4 * _scaledObjectSpeed; //adjust for not moving in blocks of 4 pixels at a time anymore
  985.                         }
  986.                         obj.yAcc += obj.xAcc; //move forwards
  987.                         const int numberOfPointsOnPath = _path.length / 2;
  988.                         const int point1 = (int(obj.yAcc) % numberOfPointsOnPath) * 2;
  989.                         const float nearnessToNextPoint = obj.yAcc % 1.f; //instead of jumping from point to point, move smoothly during the transition gameticks
  990.                        
  991.                         const float directionMovingAhead = _path[(point1+3) % _path.length] - _path[point1+1];
  992.                         if (directionMovingAhead > 0)
  993.                                 obj.var[_objVar::DirectionCurrent] = 1;
  994.                         else if (directionMovingAhead < 0)
  995.                                 obj.var[_objVar::DirectionCurrent] = -1;
  996.                        
  997.                         obj.xPos = obj.xOrg + (_path[point1+1] + nearnessToNextPoint * directionMovingAhead) * 4 * _scaledObjectSpeed;
  998.                         obj.yPos = obj.yOrg + (_path[point1+0] + nearnessToNextPoint * (_path[(point1+2) % _path.length] - _path[point1+0])) * _scaledObjectSpeed;
  999.                 }
  1000.         }
  1001.         abstract class Snake : PathFollower {
  1002.                 protected uint
  1003.                         _segmentStrength = 1, //"strength" in J1E--jjOBJ::energy should be a multiple of this and "moving platform start"
  1004.                         _segmentSeparation = 1, //"var 2" in J1E
  1005.                         _segmentDropAnimationLength = 19;
  1006.                 Snake(const EnemyArguments &in arg, int pathID) {
  1007.                         super(arg, pathID);
  1008.                         if (jjDifficulty >= 3)
  1009.                                 _segmentStrength += 1;
  1010.                 }
  1011.                 void myBehave(jjOBJ@ obj) const override {
  1012.                         if (jjDifficulty >= 3 && obj.state == STATE::START) //turbo
  1013.                                 obj.energy = (obj.energy - 1) / (_segmentStrength - 1) * _segmentStrength; //undo normal turbo strength boost
  1014.                         PathFollower::myBehave(obj);
  1015.                 }
  1016.                 void onDraw(jjOBJ@ obj) override {
  1017.                         const uint numberOfSegments = (obj.energy + (_segmentStrength-1)) / _segmentStrength;
  1018.                         const uint numberOfPointsOnPath = _path.length >> 1;
  1019.                        
  1020.                         float oldPosition = obj.yAcc - obj.xAcc * _segmentSeparation * numberOfSegments;
  1021.                         uint i = 0;
  1022.                         while (true) {
  1023.                                 const float nearnessToNextPoint = (oldPosition) % 1.f; //instead of jumping from point to point, move smoothly during the transition gameticks
  1024.                                 int pointIndex = int(oldPosition);
  1025.                                 while (pointIndex < 0) pointIndex += numberOfPointsOnPath; //% doesn't really work with negative numbers
  1026.                                 pointIndex %= numberOfPointsOnPath;
  1027.                                 const uint pointIndex2 = ((pointIndex + 1) % numberOfPointsOnPath) << 1;
  1028.                                 pointIndex <<= 1;
  1029.                                 _drawBodyFrame(
  1030.                                         obj,
  1031.                                         obj.xOrg + (_path[pointIndex + 1] + nearnessToNextPoint * (_path[pointIndex2 + 1] - _path[pointIndex + 1])) * 4 * _scaledObjectSpeed,
  1032.                                         obj.yOrg + (_path[pointIndex + 0] + nearnessToNextPoint * (_path[pointIndex2 + 0] - _path[pointIndex + 0])) * _scaledObjectSpeed,
  1033.                                         jjAnimations[obj.curAnim + 1] + (i == 0 ? 1 : 0), //hardcoded: snake body, tail are the two frames immediately following the head's animation
  1034.                                         1
  1035.                                 );
  1036.                                 if (++i >= numberOfSegments)
  1037.                                         break;
  1038.                                 oldPosition += obj.xAcc * _segmentSeparation;
  1039.                         }
  1040.                        
  1041.                         Enemy::onDraw(obj);
  1042.                 }
  1043.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  1044.                         const auto numberOfSegmentsOld = (obj.energy + (_segmentStrength-1)) / _segmentStrength;
  1045.                         Enemy::onObjectHit(obj, bullet, player, force);
  1046.                         if (obj.isActive && ((obj.energy + (_segmentStrength-1)) / _segmentStrength) < numberOfSegmentsOld) { //hurt; drop a segment
  1047.                                 jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, _segmentDropAnimationLength)];
  1048.                                 shard.curAnim = obj.curAnim + 1;
  1049.                                 shard.behavior = _explosion;
  1050.                                 shard.counterEnd = 0;
  1051.                                 shard.xSpeed = ((bullet !is null) ? bullet.xSpeed : player.xSpeed) / 2;
  1052.                                 shard.ySpeed = ((bullet !is null) ? bullet.ySpeed : player.ySpeed) / 2 + 0.0001; //non-zero
  1053.                         }
  1054.                         return true;
  1055.                 }
  1056.         }
  1057.        
  1058.         abstract class StalkerGhost : Enemy {
  1059.                 StalkerGhost(const EnemyArguments &in arg) {
  1060.                         super(arg);
  1061.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  1062.                 }
  1063.                 void myBehave(jjOBJ@ obj) const override {
  1064.                         if (obj.state == STATE::START) {
  1065.                                 _adjustObjectDirection(obj); //get an initial direction in case the player is facing us
  1066.                                 obj.state = STATE::FLY;
  1067.                         }
  1068.                         const int nearestPlayerID = obj.findNearestPlayer(320*320);
  1069.                         if (nearestPlayerID >= 0) {
  1070.                                 const jjPLAYER@ play = jjPlayers[nearestPlayerID];
  1071.                                 const bool objectToRightOfPlayer = obj.xPos > play.xPos;
  1072.                                 if ((play.direction >= 0) != objectToRightOfPlayer && abs(obj.xPos - play.xPos) > 20) { //player looking away from a sufficiently distant enemy
  1073.                                         obj.var[_objVar::DirectionCurrent] = objectToRightOfPlayer ? -1 : 1;
  1074.                                         obj.xPos += obj.xAcc * obj.var[_objVar::DirectionCurrent];
  1075.                                         const float yDist = obj.yPos - play.yPos;
  1076.                                         if (abs(yDist) > 20) { //move vertically... but only do so if moving horizontally
  1077.                                                 if (yDist > 0)
  1078.                                                         obj.yPos -= obj.xAcc / 4;
  1079.                                                 else
  1080.                                                         obj.yPos += obj.xAcc / 4;
  1081.                                         }
  1082.                                 }
  1083.                         }
  1084.                 }
  1085.         }
  1086.        
  1087.         abstract class Missile : Enemy {
  1088.                 Missile(const EnemyArguments &in arg) {
  1089.                         super(arg);
  1090.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  1091.                 }
  1092.                 void myBehave(jjOBJ@ obj) const override {
  1093.                         if (obj.state == STATE::START) {
  1094.                                 _adjustObjectDirection(obj); //get an initial direction in case the player is facing us
  1095.                                 obj.state = STATE::FLY;
  1096.                         }
  1097.                         obj.xPos += obj.xAcc * obj.var[_objVar::DirectionCurrent];
  1098.                 }
  1099.         }
  1100.        
  1101.         abstract class Floater : Enemy {
  1102.                 Floater(const EnemyArguments &in arg) {
  1103.                         super(arg);
  1104.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  1105.                 }
  1106.                 void myBehave(jjOBJ@ obj) const override {
  1107.                         if (obj.state == STATE::START) {
  1108.                                 _adjustObjectDirection(obj);
  1109.                                 obj.state = STATE::FLY;
  1110.                         }
  1111.                        
  1112.                         const int direction = obj.var[_objVar::DirectionCurrent];
  1113.                         const jjANIMFRAME@ animFrame = jjAnimFrames[obj.curFrame];
  1114.                         const float positionForwards = obj.xPos + direction * animFrame.width / 2;
  1115.                        
  1116.                         if (
  1117.                                 (jjEventGet(int(positionForwards) / 32, int(obj.yPos) / 32) == AREA::STOPENEMY) ||
  1118.                                 (jjMaskedVLine(int(positionForwards), int(obj.yPos + animFrame.hotSpotY), animFrame.height)) //wall
  1119.                         )
  1120.                                 _reverseDirection(obj);
  1121.                         else
  1122.                                 obj.xPos += direction * obj.xAcc;
  1123.                 }
  1124.         }
  1125.        
  1126.         abstract class WallStickerHorizontal : Enemy {
  1127.                 WallStickerHorizontal(const EnemyArguments &in arg) {
  1128.                         super(arg);
  1129.                         _flipSpriteWhenMovingLeft = _supportsDirection = true;
  1130.                 }
  1131.                 void myBehave(jjOBJ@ obj) const override {
  1132.                         if (obj.state == STATE::START) {
  1133.                                 if (_adjustObjectDirection(obj) == 1) {
  1134.                                         while (obj.xPos > 0 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  1135.                                                 obj.xPos -= 1;
  1136.                                 } else {
  1137.                                         obj.direction = SPRITE::FLIPH;
  1138.                                         while (obj.xPos < _levelWidth-2 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  1139.                                                 obj.xPos += 1;
  1140.                                 }
  1141.                                 obj.state = STATE::WAIT;
  1142.                         }
  1143.                 }
  1144.         }
  1145.         abstract class WallStickerVertical : Enemy {
  1146.                 WallStickerVertical(const EnemyArguments &in arg) {
  1147.                         super(arg);
  1148.                         _supportsDirection = true;
  1149.                 }
  1150.                 void myBehave(jjOBJ@ obj) const override {
  1151.                         if (obj.state == STATE::START) {
  1152.                                 if (_adjustObjectDirection(obj) == 1) { //right = floor
  1153.                                         while (obj.yPos < _levelHeight-2 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  1154.                                                 obj.yPos += 1;
  1155.                                 } else { //left = ceiling
  1156.                                         obj.direction = SPRITE::FLIPV;
  1157.                                         while (obj.yPos > 0 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  1158.                                                 obj.yPos -= 1;
  1159.                                 }
  1160.                                 obj.state = STATE::WAIT;
  1161.                         }
  1162.                 }
  1163.         }
  1164.        
  1165.         abstract class StandOnFloor : Enemy {
  1166.                 StandOnFloor(const EnemyArguments &in arg) {
  1167.                         super(arg);
  1168.                 }
  1169.                 void myBehave(jjOBJ@ obj) const override {
  1170.                         if (obj.state == STATE::START) {
  1171.                                 obj.putOnGround(true);
  1172.                                 obj.state = STATE::WAIT;
  1173.                         }
  1174.                         if (_supportsDirection)
  1175.                                 _adjustObjectDirection(obj);
  1176.                 }
  1177.         }
  1178.        
  1179.         abstract class Hopper : Enemy {
  1180.                 private uint8 _timeBetweenHops;
  1181.                 private float _heightOfHops, _lengthOfHops;
  1182.                 Hopper(const EnemyArguments &in arg, uint8 var1, uint8 var2) {
  1183.                         super(arg);
  1184.                         _heightOfHops = 1;
  1185.                         _lengthOfHops = 2 + (var1 << 3); //(2), 10, 18, 26, 34, 42...
  1186.                         uint yTopIncrease = 3;
  1187.                         while (var1-- != 0) {
  1188.                                 _heightOfHops += yTopIncrease; //(1), 4, 11, 22, 37, 56, 79, 106, 137, 172, 211...
  1189.                                 yTopIncrease += 4;
  1190.                         }
  1191.                         _timeBetweenHops = var2;
  1192.                         if (_animFrames.length & 1 == 1) //odd number
  1193.                                 _animFrames.insertLast(_animFrames[_animFrames.length-1]);
  1194.                 }
  1195.                 void myBehave(jjOBJ@ obj) const override {
  1196.                         bool facingDown = true;
  1197.                         if (obj.state == STATE::START) {
  1198.                                 obj.yPos = obj.yOrg -= 1;
  1199.                                 obj.state = STATE::WAIT;
  1200.                         } else if (obj.state != STATE::BOUNCE) {
  1201.                                 if (obj.counterEnd++ >= _timeBetweenHops) {
  1202.                                         obj.counterEnd = 0;
  1203.                                         obj.state = STATE::BOUNCE;
  1204.                                 }
  1205.                         } else {
  1206.                                 if (obj.counterEnd++ >= uint(_lengthOfHops)) {
  1207.                                         obj.counterEnd = 0;
  1208.                                         obj.yPos = obj.yOrg;
  1209.                                         if (_timeBetweenHops != 0) obj.state = STATE::WAIT;
  1210.                                 } else {
  1211.                                         obj.yPos = obj.yOrg - jjSin(int((uint(obj.counterEnd) << 9) / _lengthOfHops)) * _heightOfHops * _scaledObjectSpeed;
  1212.                                         facingDown = obj.counterEnd > uint(_lengthOfHops) / 2;
  1213.                                 }
  1214.                         }
  1215.                        
  1216.                         obj.var[_objVar::AnimCounter] = ((facingDown ? _animFrames.length : 0) + ((jjGameTicks / (obj.animSpeed + 1) % _animFrames.length))) - 1;
  1217.                 }
  1218.                 Enemy@ SetFireDelay(uint delay) override {
  1219.                         _timeBetweenHops = delay;
  1220.                         return this;
  1221.                 }
  1222.                 Enemy@ SetEnemySpeedsAreScalable(bool setTo) override {
  1223.                         _scaledObjectSpeed = setTo ? _scale : 1.f;
  1224.                         return this;
  1225.                 }
  1226.         }
  1227.        
  1228.        
  1229.        
  1230.         final class Diamondus_TurtleGoon : Walker {
  1231.                 Diamondus_TurtleGoon(const EnemyArguments &in arg) {
  1232.                         super(arg);
  1233.                         _speed = 1.333333;
  1234.                         _direction = Directions::Right;
  1235.                         _killAnimExplosion = true;
  1236.                         preset.killAnim = -1;
  1237.                         preset.points = 100;
  1238.                         preset.energy = 1;
  1239.                         preset.animSpeed = 4;
  1240.                 }
  1241.         }
  1242.        
  1243.         final class Diamondus_BumblingBee : PathFollower {
  1244.                 Diamondus_BumblingBee(const EnemyArguments &in arg) {
  1245.                         super(arg, 0);
  1246.                         _speed = 2;
  1247.                         _killAnimExplosion = true;
  1248.                         preset.killAnim = -1;
  1249.                         preset.points = 50;
  1250.                         preset.energy = 1;
  1251.                         preset.animSpeed = 3;
  1252.                 }
  1253.         }
  1254.        
  1255.        
  1256.         final class Tubelectric_BlasterHorizontal : WallStickerHorizontal {
  1257.                 Tubelectric_BlasterHorizontal(const EnemyArguments &in arg) {
  1258.                         super(arg);
  1259.                         _supportsFire = true;
  1260.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1261.                         @_fireFrames = array<int8> = {2,2,2,2,2};
  1262.                         _bullets.insertLast(_bulletPreferences(-3,0, 1,0, _bulletDirection::Left));
  1263.                         _bullets.insertLast(_bulletPreferences( 3,0, 1,0, _bulletDirection::Right));
  1264.                         _fireDelay = 75;
  1265.                         preset.playerHandling = HANDLING::SELFCOLLISION;
  1266.                         preset.animSpeed = 4;
  1267.                 }
  1268.         }
  1269.         final class Tubelectric_BlasterVertical : WallStickerVertical {
  1270.                 Tubelectric_BlasterVertical(const EnemyArguments &in arg) {
  1271.                         super(arg);
  1272.                         _supportsFire = true;
  1273.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1274.                         @_fireFrames = array<int8> = {2,2,2,2,2};
  1275.                         _bullets.insertLast(_bulletPreferences(0, 3, 1,0, _bulletDirection::Left));
  1276.                         _bullets.insertLast(_bulletPreferences(0,-3, 1,0, _bulletDirection::Right));
  1277.                         _fireDelay = 75;
  1278.                         preset.playerHandling = HANDLING::SELFCOLLISION;
  1279.                         preset.animSpeed = 4;
  1280.                 }
  1281.         }
  1282.        
  1283.         final class Tubelectric_Spark : StalkerGhost {
  1284.                 Tubelectric_Spark(const EnemyArguments &in arg) {
  1285.                         super(arg);
  1286.                         _speed = 1.33333333;
  1287.                         _direction = Directions::Left;
  1288.                         @_animFrames = array<int8> = {0, 0, 0, 1}; //spend thrice as much time not-flashing as flashing
  1289.                         _killAnimExplosion = true;
  1290.                         _killAnimRepetitionCounts = 6;
  1291.                         preset.killAnim = 1;
  1292.                         preset.points = 20;
  1293.                         preset.energy = 1;
  1294.                         preset.animSpeed = 0;
  1295.                 }
  1296.         }
  1297.        
  1298.         final class Tubelectric_SparkBarrier : Enemy {
  1299.                 Tubelectric_SparkBarrier(const EnemyArguments &in arg) {
  1300.                         super(arg);
  1301.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1302.                         _killAnimExplosion = true;
  1303.                         _killAnimRepetitionCounts = 7;
  1304.                         preset.killAnim = 1;
  1305.                         preset.points = 20;
  1306.                         preset.energy = 3;
  1307.                         preset.animSpeed = 2;
  1308.                 }
  1309.         }
  1310.        
  1311.        
  1312.         final class Medivo_GhostRapierHorizontal : Missile {
  1313.                 Medivo_GhostRapierHorizontal(const EnemyArguments &in arg) {
  1314.                         super(arg);
  1315.                         _speed = 2;
  1316.                         _direction = Directions::FaceJazz;
  1317.                         preset.points = 10;
  1318.                         preset.energy = 1;
  1319.                         preset.animSpeed = 1;
  1320.                 }
  1321.         }
  1322.        
  1323.         final class Medivo_GhostRapierVertical : PathFollower {
  1324.                 Medivo_GhostRapierVertical(const EnemyArguments &in arg) {
  1325.                         super(arg, 1);
  1326.                         _speed = 1.333333;
  1327.                         _flipSpriteWhenMovingLeft = false;
  1328.                         preset.points = 10;
  1329.                         preset.energy = 1;
  1330.                         preset.animSpeed = 1;
  1331.                 }
  1332.         }
  1333.        
  1334.         final class Medivo_Helmut : Walker {
  1335.                 Medivo_Helmut(const EnemyArguments &in arg) {
  1336.                         super(arg);
  1337.                         _speed = 1.333333;
  1338.                         _cliffReaction = CliffReaction::Fall;
  1339.                         preset.points = 10;
  1340.                         preset.energy = 1;
  1341.                         preset.animSpeed = 3;
  1342.                         _direction = Directions::Left;
  1343.                 }
  1344.         }
  1345.        
  1346.        
  1347.         final class Letni_Bug : Walker {
  1348.                 Letni_Bug(const EnemyArguments &in arg) {
  1349.                         super(arg);
  1350.                         _speed = 1.333333;
  1351.                         preset.points = 50;
  1352.                         preset.energy = 1;
  1353.                         preset.animSpeed = 3;
  1354.                         _direction = Directions::Left;
  1355.                 }
  1356.         }
  1357.         final class Letni_BugCeiling : Enemy {
  1358.                 Letni_BugCeiling(const EnemyArguments &in arg) {
  1359.                         super(arg);
  1360.                         _speed = 1.333333;
  1361.                         preset.points = 50;
  1362.                         preset.energy = 1;
  1363.                         preset.animSpeed = 3;
  1364.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  1365.                         _direction = Directions::Left;
  1366.                         _killAnimExplosion = true; //different from floor variation for some reason
  1367.                 }
  1368.                 void myBehave(jjOBJ@ obj) const override { //like Walker, but on ceiling and without any falling options
  1369.                         if (obj.state == STATE::START) {
  1370.                                 {
  1371.                                         _adjustObjectDirection(obj);
  1372.                                         while (obj.yPos >= 5 && !_maskedPixelFloat(obj.xPos, obj.yPos - 5))
  1373.                                                 obj.yPos -= 1;
  1374.                                 }
  1375.                                 obj.state = STATE::WALK;
  1376.                         }
  1377.                        
  1378.                         const int direction = obj.var[_objVar::DirectionCurrent];
  1379.                         const float positionForwards = obj.xPos + direction * jjAnimFrames[obj.curFrame].width / 2;
  1380.                        
  1381.                         if (
  1382.                                 (jjEventGet(int(positionForwards) / 32, int(obj.yPos) / 32) == AREA::STOPENEMY) ||
  1383.                                 (!_maskedPixelFloat(positionForwards, obj.yPos - 5)) || //cliff
  1384.                                 (_maskedPixelFloat(positionForwards, obj.yPos - 4)) //wall
  1385.                         )
  1386.                                 _reverseDirection(obj);
  1387.                         else
  1388.                                 obj.xPos += direction * obj.xAcc;
  1389.                 }
  1390.         }
  1391.        
  1392.         final class Letni_ElecBarrier : Enemy {
  1393.                 Letni_ElecBarrier(const EnemyArguments &in arg) {
  1394.                         super(arg);
  1395.                         _killAnimExplosion = true;
  1396.                         preset.killAnim = -2;
  1397.                         preset.points = 50;
  1398.                         preset.energy = 3;
  1399.                         preset.animSpeed = 3;
  1400.                 }
  1401.         }
  1402.        
  1403.        
  1404.         final class Technoir_MiniMine : Floater {
  1405.                 Technoir_MiniMine(const EnemyArguments &in arg) {
  1406.                         super(arg);
  1407.                         _speed = 0.8f;
  1408.                         _flipSpriteWhenMovingLeft = false;
  1409.                         _killAnimExplosion = true;
  1410.                         _killAnimRepetitionCounts = 10;
  1411.                         preset.killAnim = 1;
  1412.                         preset.points = 0;
  1413.                         preset.energy = 3;
  1414.                         preset.animSpeed = 1;
  1415.                 }
  1416.         }
  1417.        
  1418.         final class Technoir_Misfire : Walker { //actually an amalgamation of the Misfires from the two Technoir levels; level 1's movement speed and points, level 2's falling off cliffs
  1419.                 Technoir_Misfire(const EnemyArguments &in arg) {
  1420.                         super(arg);
  1421.                         _speed = 1.333333;
  1422.                         _cliffReaction = CliffReaction::Fall;
  1423.                         preset.points = 50;
  1424.                         preset.energy = 1;
  1425.                         preset.animSpeed = 3;
  1426.                         _direction = Directions::Left;
  1427.                         _killAnimExplosion = true;
  1428.                 }
  1429.         }
  1430.        
  1431.         final class Technoir_TanketyTankTank : Walker {
  1432.                 Technoir_TanketyTankTank(const EnemyArguments &in arg) {
  1433.                         super(arg);
  1434.                         _speed = 1;
  1435.                         preset.points = 50;
  1436.                         preset.energy = 2;
  1437.                         preset.animSpeed = 3;
  1438.                         _direction = Directions::Left;
  1439.                         _killAnimExplosion = true;
  1440.                         _supportsFire = true;
  1441.                         @_animFrames = array<int8> = {0, 1, 2, 3};
  1442.                         @_fireFrames = array<int8> = {4,4,4,4,4,4};
  1443.                         _bullets.insertLast(_bulletPreferences(-4,0, 1,1, _bulletDirection::Left));
  1444.                         _bullets.insertLast(_bulletPreferences( 4,0, 1,0, _bulletDirection::Right));
  1445.                         _fireDelay = 100;
  1446.                 }
  1447.         }
  1448.        
  1449.        
  1450.         final class Orbitus_BeholderPurple : Enemy {
  1451.                 Orbitus_BeholderPurple(const EnemyArguments &in arg) {
  1452.                         super(arg);
  1453.                         preset.points = 30;
  1454.                         preset.energy = 4;
  1455.                         preset.animSpeed = 4;
  1456.                         @_animFrames = array<int8> = {0, 1, 2, 3, 2, 1};
  1457.                         _killAnimExplosion = true;
  1458.                         _killAnimRepetitionCounts = 9;
  1459.                         preset.killAnim = 1;
  1460.                 }
  1461.         }
  1462.        
  1463.         final class Orbitus_BeholderSilver : Walker {
  1464.                 Orbitus_BeholderSilver(const EnemyArguments &in arg) {
  1465.                         super(arg);
  1466.                         _speed = 1;
  1467.                         preset.points = 30;
  1468.                         preset.energy = 1;
  1469.                         preset.animSpeed = 5;
  1470.                         @_animFrames = array<int8> = {0, 1, 2, 3, 2, 1};
  1471.                         _direction = Directions::Left;
  1472.                         _flipSpriteWhenMovingLeft = false;
  1473.                         _killAnimExplosion = true;
  1474.                         _killAnimRepetitionCounts = 9;
  1475.                         preset.killAnim = 1;
  1476.                 }
  1477.         }
  1478.        
  1479.         final class Orbitus_SilverSnake : Snake {
  1480.                 Orbitus_SilverSnake(const EnemyArguments &in arg) {
  1481.                         super(arg, 2);
  1482.                         _speed = 4;
  1483.                         _killAnimExplosion = true;
  1484.                         _killAnimRepetitionCounts = 19;
  1485.                         preset.killAnim = 1;
  1486.                         preset.points = 20;
  1487.                         preset.energy = 10;
  1488.                         preset.animSpeed = 6;
  1489.                 }
  1490.         }
  1491.        
  1492.        
  1493.         final class Fanolint_FlyFlower : PathFollower {
  1494.                 Fanolint_FlyFlower(const EnemyArguments &in arg) {
  1495.                         super(arg, jjDifficulty >= 2 ? 4 : 5);
  1496.                         _speed = 2;
  1497.                         _killAnimExplosion = true;
  1498.                         _killAnimRepetitionCounts = 11;
  1499.                         preset.killAnim = 1;
  1500.                         preset.points = 30;
  1501.                         preset.energy = 1;
  1502.                         preset.animSpeed = 5;
  1503.                 }
  1504.         }
  1505.        
  1506.         final class Fanolint_PottedPlant : StandOnFloor {
  1507.                 Fanolint_PottedPlant(const EnemyArguments &in arg) {
  1508.                         super(arg);
  1509.                         preset.points = 20;
  1510.                         preset.energy = 3;
  1511.                         preset.animSpeed = 5;
  1512.                         @_animFrames = array<int8> = {0, 1, 0, 2};
  1513.                         _killAnimExplosion = true;
  1514.                         _killAnimRepetitionCounts = 11;
  1515.                         preset.killAnim = 1;
  1516.                         _supportsFire = true;
  1517.                         _fireDelay = 200;
  1518.                         _bullets.insertLast(_bulletPreferences(-4,1, 2,0, _bulletDirection::Either));
  1519.                         _bullets.insertLast(_bulletPreferences( 4,1, 2,0, _bulletDirection::Either));
  1520.                         preset.var[_objVar::DirectionCurrent] = 1;
  1521.                 }
  1522.         }
  1523.        
  1524.         final class Fanolint_SuperTankety : Walker {
  1525.                 Fanolint_SuperTankety(const EnemyArguments &in arg) {
  1526.                         super(arg);
  1527.                         _speed = 1.333333;
  1528.                         preset.points = 20;
  1529.                         preset.energy = 1;
  1530.                         preset.animSpeed = 4;
  1531.                         _direction = Directions::Left;
  1532.                         _killAnimExplosion = true;
  1533.                         _supportsFire = true;
  1534.                         @_animFrames = array<int8> = {0,1};
  1535.                         @_fireFrames = array<int8> = {2,2,2,2,2};
  1536.                         _bullets.insertLast(_bulletPreferences(-4,0, 1,1, _bulletDirection::Left));
  1537.                         _bullets.insertLast(_bulletPreferences( 4,0, 1,0, _bulletDirection::Right));
  1538.                         _fireDelay = 100;
  1539.                 }
  1540.         }
  1541.        
  1542.        
  1543.         final class Scraparap_GunnerDrone : StalkerGhost {
  1544.                 Scraparap_GunnerDrone(const EnemyArguments &in arg) {
  1545.                         super(arg);
  1546.                         _speed = 1;
  1547.                         _direction = Directions::Left;
  1548.                         @_animFrames = array<int8> = {0, 0, 0, 0, 1, 1};
  1549.                         preset.killAnim = -2;
  1550.                         preset.points = 20;
  1551.                         preset.energy = 1;
  1552.                         preset.animSpeed = 0;
  1553.                         _supportsFire = true;
  1554.                         _bullets.insertLast(_bulletPreferences(-8,0, 1,1, _bulletDirection::Left));
  1555.                         _bullets.insertLast(_bulletPreferences( 8,0, 1,0, _bulletDirection::Right));
  1556.                         _fireDelay = 100;
  1557.                 }
  1558.                 void onBehave(jjOBJ@ obj) override {
  1559.                         if (obj.state == STATE::KILL && !_useJJ2DeathAnimation) {
  1560.                                 if (_deathSound >= 0)
  1561.                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(_deathSound));
  1562.                                 jjOBJ@ deathAnim = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT, BEHAVIOR::INACTIVE)];
  1563.                                 deathAnim.curFrame = obj.curFrame;
  1564.                                 deathAnim.killAnim = obj.killAnim;
  1565.                                 deathAnim.state = STATE::KILL;
  1566.                                 deathAnim.playerHandling = HANDLING::DYING;
  1567.                                 deathAnim.counterEnd = 0;
  1568.                                 deathAnim.behavior = jjVOIDFUNCOBJ(DyingDescent);
  1569.                         }
  1570.                         else StalkerGhost::onBehave(obj);
  1571.                 }
  1572.                 array<array<int>> DescentFrames = {
  1573.                         {1,2,2},
  1574.                         {1,4,4},
  1575.                         {-1,6,6},
  1576.                         {-1,6,8},
  1577.                         {1,6,18},
  1578.                         {1,8,20},
  1579.                         {-1,8,23},
  1580.                         {-1,8,32},
  1581.                         {1,8,37},
  1582.                         {1,8,50},
  1583.                         {-1,16,64},
  1584.                         {-1,16,80},
  1585.                         {1,16,96},
  1586.                         {1,16,117}
  1587.                 };
  1588.                 void DyingDescent(jjOBJ@ obj) {
  1589.                         if ((jjGameTicks & 1) == 0) {
  1590.                                 if (obj.counterEnd < DescentFrames.length) {
  1591.                                         const auto@ newPosition = DescentFrames[obj.counterEnd++];
  1592.                                         obj.direction = newPosition[0];
  1593.                                         obj.xPos = obj.xOrg + newPosition[1] * 4 * _scaledObjectSpeed;
  1594.                                         obj.yPos = obj.yOrg + newPosition[2] * _scaledObjectSpeed;
  1595.                                 } else {
  1596.                                         Enemy::onBehave(obj);
  1597.                                 }
  1598.                         }
  1599.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction);
  1600.                 }
  1601.         }
  1602.        
  1603.         final class Scraparap_LaunchCart : Walker {
  1604.                 Scraparap_LaunchCart(const EnemyArguments &in arg) {
  1605.                         super(arg);
  1606.                         _speed = 1.333333;
  1607.                         preset.points = 20;
  1608.                         preset.energy = 3;
  1609.                         preset.animSpeed = 10;
  1610.                         _direction = Directions::Left;
  1611.                         _killAnimExplosion = true;
  1612.                         _supportsFire = true;
  1613.                         _bullets.insertLast(_bulletPreferences(-4,-3, 1,0, _bulletDirection::Either));
  1614.                         _bullets.insertLast(_bulletPreferences( 4,-3, 1,0, _bulletDirection::Either));
  1615.                         _fireDelay = 50;
  1616.                 }
  1617.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  1618.                         if (bullet !is null)
  1619.                                 return Enemy::onObjectHit(obj,bullet,player,force);
  1620.                         return true; //no collision damage
  1621.                 }
  1622.                 void myBehave(jjOBJ@ obj) const override {
  1623.                         const auto lastX = obj.xPos, lastY = obj.yPos;
  1624.                         Walker::myBehave(obj);
  1625.                         obj.bePlatform(lastX,lastY);
  1626.                 }
  1627.                 void onBehave(jjOBJ@ obj) override {
  1628.                         if (obj.state == STATE::KILL || obj.state == STATE::DEACTIVATE)
  1629.                                 obj.clearPlatform();
  1630.                         Walker::onBehave(obj);
  1631.                 }
  1632.         }
  1633.        
  1634.         final class Scraparap_RoboTurtleDrone : Walker {
  1635.                 Scraparap_RoboTurtleDrone(const EnemyArguments &in arg) {
  1636.                         super(arg);
  1637.                         _speed = 2;
  1638.                         _cliffReaction = CliffReaction::Fall;
  1639.                         preset.points = 20;
  1640.                         preset.energy = 1;
  1641.                         preset.animSpeed = 3;
  1642.                         _direction = Directions::Left;
  1643.                 }
  1644.         }
  1645.        
  1646.        
  1647.         final class Megairbase_Doofusguard : Walker {
  1648.                 Megairbase_Doofusguard(const EnemyArguments &in arg) {
  1649.                         super(arg);
  1650.                         _speed = 2;
  1651.                         preset.points = 30;
  1652.                         preset.energy = 3;
  1653.                         preset.animSpeed = 4;
  1654.                         _direction = Directions::Left;
  1655.                         _killAnimExplosion = true;
  1656.                         _supportsFire = true;
  1657.                         _bullets.insertLast(_bulletPreferences(-12,0, 1,0, _bulletDirection::Left));
  1658.                         _bullets.insertLast(_bulletPreferences( 12,0, 1,0, _bulletDirection::Right));
  1659.                         _fireDelay = 100;
  1660.                 }
  1661.         }
  1662.        
  1663.         final class Megairbase_Missile : Missile {
  1664.                 Megairbase_Missile(const EnemyArguments &in arg) {
  1665.                         super(arg);
  1666.                         _speed = 4;
  1667.                         _direction = Directions::FaceJazz;
  1668.                         _killAnimExplosion = true;
  1669.                         preset.points = 20;
  1670.                         preset.energy = 1;
  1671.                         preset.animSpeed = 3;
  1672.                 }
  1673.         }
  1674.        
  1675.         final class Megairbase_SuperSpark : StalkerGhost {
  1676.                 Megairbase_SuperSpark(const EnemyArguments &in arg) {
  1677.                         super(arg);
  1678.                         _speed = 2;
  1679.                         _direction = Directions::Left;
  1680.                         _killAnimExplosion = true;
  1681.                         _killAnimRepetitionCounts = 5;
  1682.                         preset.killAnim = 1;
  1683.                         preset.points = 20;
  1684.                         preset.energy = 1;
  1685.                         preset.animSpeed = 0;
  1686.                 }
  1687.         }
  1688.        
  1689.        
  1690.         final class Turtemple_JeTurtle : Enemy {
  1691.                 Turtemple_JeTurtle(const EnemyArguments &in arg) {
  1692.                         super(arg);
  1693.                         preset.points = 20;
  1694.                         preset.energy = 1;
  1695.                         preset.animSpeed = 7;
  1696.                         _killAnimExplosion = true;
  1697.                         _supportsFire = true;
  1698.                         _bullets.insertLast(_bulletPreferences(-12,0, 1,0, _bulletDirection::Left));
  1699.                         _bullets.insertLast(_bulletPreferences( 12,0, 1,0, _bulletDirection::Right));
  1700.                         _fireDelay = 100;
  1701.                         _supportsDirection = true;
  1702.                         _flipSpriteWhenMovingLeft = true;
  1703.                         _direction = Directions::FaceJazz;
  1704.                 }
  1705.                 void myBehave(jjOBJ@ obj) const override {
  1706.                         _adjustObjectDirection(obj);
  1707.                         if (obj.state == STATE::START)
  1708.                                 obj.state = STATE::WAIT;
  1709.                         else if (obj.state == STATE::BOUNCE)
  1710.                                 obj.yPos = jjWaterLevel;
  1711.                         else if (obj.yPos >= jjWaterLevel - 10)
  1712.                                 obj.state = STATE::BOUNCE;
  1713.                 }
  1714.         }
  1715.        
  1716.         final class Turtemple_ScorpWeenie : Walker {
  1717.                 Turtemple_ScorpWeenie(const EnemyArguments &in arg) {
  1718.                         super(arg);
  1719.                         _speed = 1.333333;
  1720.                         _cliffReaction = CliffReaction::Fall;
  1721.                         @_animFrames = array<int8> = {0, 1, 0, 2};
  1722.                         preset.points = 10;
  1723.                         preset.energy = 1;
  1724.                         preset.animSpeed = 3;
  1725.                         _direction = Directions::Right;
  1726.                 }
  1727.         }
  1728.        
  1729.         final class Nippius_SnowGoon : StandOnFloor {
  1730.                 Nippius_SnowGoon(const EnemyArguments &in arg) {
  1731.                         super(arg);
  1732.                         preset.points = 10;
  1733.                         preset.energy = 1;
  1734.                         preset.animSpeed = 4;
  1735.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1736.                         _direction = Directions::Left;
  1737.                         _supportsDirection = _flipSpriteWhenMovingLeft = true;
  1738.                         _killAnimExplosion = true;
  1739.                         _killAnimRepetitionCounts = 19;
  1740.                         preset.killAnim = 1;
  1741.                 }
  1742.         }
  1743.        
  1744.         final class Nippius_SkatePen : Walker {
  1745.                 Nippius_SkatePen(const EnemyArguments &in arg) {
  1746.                         super(arg);
  1747.                         _speed = 1.333333;
  1748.                         _cliffReaction = CliffReaction::Fall;
  1749.                         preset.points = 10;
  1750.                         preset.energy = 1;
  1751.                         preset.animSpeed = 4;
  1752.                         _direction = Directions::Left;
  1753.                 }
  1754.         }
  1755.        
  1756.         final class Nippius_SkiTurtle : Walker {
  1757.                 Nippius_SkiTurtle(const EnemyArguments &in arg) {
  1758.                         super(arg);
  1759.                         _speed = 4; //2 in level 2
  1760.                         preset.points = 10;
  1761.                         preset.energy = 1;
  1762.                         preset.animSpeed = 3;
  1763.                         _direction = Directions::Left;
  1764.                 }
  1765.         }
  1766.        
  1767.        
  1768.         final class Jungrock_JetSnake : Walker {
  1769.                 Jungrock_JetSnake(const EnemyArguments &in arg) {
  1770.                         super(arg);
  1771.                         _speed = 2;
  1772.                         preset.points = 40;
  1773.                         preset.energy = 1;
  1774.                         preset.animSpeed = 3;
  1775.                         _direction = Directions::Left;
  1776.                         _killAnimExplosion = true;
  1777.                 }
  1778.         }
  1779.        
  1780.         final class Jungrock_RedBuzzer : PathFollower {
  1781.                 Jungrock_RedBuzzer(const EnemyArguments &in arg) {
  1782.                         super(arg, 6);
  1783.                         _speed = 2;
  1784.                         _killAnimExplosion = true;
  1785.                         preset.points = 10;
  1786.                         preset.energy = 1;
  1787.                         preset.animSpeed = 4;
  1788.                 }
  1789.         }
  1790.        
  1791.         final class Jungrock_YellowBuzzer : Walker { //yellow buzzer also has a variant that seeks jazz out, but it appears exactly once across both levels
  1792.                 Jungrock_YellowBuzzer(const EnemyArguments &in arg) {
  1793.                         super(arg);
  1794.                         _speed = 1.333333;
  1795.                         preset.points = 50;
  1796.                         preset.energy = 1;
  1797.                         preset.animSpeed = 3;
  1798.                         _direction = Directions::Left;
  1799.                         _killAnimExplosion = true;
  1800.                 }
  1801.         }
  1802.        
  1803.        
  1804.         final class Marbelara_Drageen : Snake {
  1805.                 Marbelara_Drageen(const EnemyArguments &in arg) {
  1806.                         super(arg, 7);
  1807.                         _speed = 2;
  1808.                         _killAnimExplosion = true;
  1809.                         preset.points = 0;
  1810.                         preset.energy = 3;
  1811.                         _segmentSeparation = 5;
  1812.                         preset.animSpeed = 4;
  1813.                 }
  1814.         }
  1815.        
  1816.         final class Marbelara_Firebomb : Hopper {
  1817.                 Marbelara_Firebomb(const EnemyArguments &in arg) {
  1818.                         super(arg, 10, 10);
  1819.                         preset.points = 20;
  1820.                         preset.energy = 6;
  1821.                         preset.animSpeed = 0;
  1822.                         _killAnimExplosion = true;
  1823.                 }
  1824.         }
  1825.        
  1826.         final class Marbelara_Schwarzenguard : Walker {
  1827.                 Marbelara_Schwarzenguard(const EnemyArguments &in arg) {
  1828.                         super(arg);
  1829.                         _speed = 0.8f;
  1830.                         preset.points = 50;
  1831.                         preset.energy = 4;
  1832.                         preset.animSpeed = 6;
  1833.                         _direction = Directions::Left;
  1834.                         _supportsFire = true;
  1835.                         @_animFrames = array<int8> = {0,1,2,3,4,5};
  1836.                         @_fireFrames = array<int8> = {6,6,6,6};
  1837.                         _bullets.insertLast(_bulletPreferences(-8,0, 1,0, _bulletDirection::Left));
  1838.                         _bullets.insertLast(_bulletPreferences( 8,0, 1,0, _bulletDirection::Right));
  1839.                         _fireDelay = 100;
  1840.                 }
  1841.         }
  1842.        
  1843.        
  1844.         final class Sluggion_Dragoon : Snake {
  1845.                 Sluggion_Dragoon(const EnemyArguments &in arg) {
  1846.                         super(arg, 8);
  1847.                         _speed = 2;
  1848.                         _killAnimExplosion = true;
  1849.                         preset.points = 20;
  1850.                         preset.energy = 5;
  1851.                         _segmentSeparation = 5;
  1852.                         preset.animSpeed = 6;
  1853.                 }
  1854.         }
  1855.        
  1856.         final class Sluggion_RedBat : PathFollower {
  1857.                 Sluggion_RedBat(const EnemyArguments &in arg) {
  1858.                         super(arg, 9);
  1859.                         _speed = 1.333333;
  1860.                         _killAnimExplosion = true;
  1861.                         preset.points = 50;
  1862.                         preset.energy = 1;
  1863.                         preset.animSpeed = 3;
  1864.                 }
  1865.         }
  1866.        
  1867.         final class Sluggion_Sluggi : Walker {
  1868.                 Sluggion_Sluggi(const EnemyArguments &in arg) {
  1869.                         super(arg);
  1870.                         _speed = 1;
  1871.                         _cliffReaction = CliffReaction::Fall;
  1872.                         preset.points = 50;
  1873.                         preset.energy = 1;
  1874.                         preset.animSpeed = 4;
  1875.                         _direction = Directions::Left;
  1876.                 }
  1877.         }
  1878.        
  1879.        
  1880.         final class Dreempipes_Minite : Hopper {
  1881.                 Dreempipes_Minite(const EnemyArguments &in arg) {
  1882.                         super(arg, 3, 0);
  1883.                         preset.points = 20;
  1884.                         preset.energy = 3;
  1885.                         preset.animSpeed = 7;
  1886.                         preset.killAnim = 1;
  1887.                         _killAnimExplosion = true;
  1888.                         _killAnimRepetitionCounts = 5;
  1889.                 }
  1890.         }
  1891.        
  1892.         final class Dreempipes_Overgrown : Hopper {
  1893.                 Dreempipes_Overgrown(const EnemyArguments &in arg) {
  1894.                         super(arg, 2, 0);
  1895.                         preset.points = 30;
  1896.                         preset.energy = 6;
  1897.                         preset.animSpeed = 7;
  1898.                         preset.killAnim = 1;
  1899.                         _killAnimExplosion = true;
  1900.                         _killAnimRepetitionCounts = 5;
  1901.                 }
  1902.         }
  1903.        
  1904.         final class Dreempipes_TerrapinSwimmer : Walker {
  1905.                 Dreempipes_TerrapinSwimmer(const EnemyArguments &in arg) {
  1906.                         super(arg);
  1907.                         _speed = 2;
  1908.                         preset.points = 30;
  1909.                         preset.energy = 3;
  1910.                         preset.animSpeed = 6;
  1911.                         _direction = Directions::Left;
  1912.                 }
  1913.                 void onBehave(jjOBJ@ obj) override {
  1914.                         if (
  1915.                                 (obj.yPos >= jjWaterLevel)
  1916.                                 //(jjGameTicks % 280 < 210)
  1917.                                 || obj.state == STATE::START || obj.state == STATE::DEACTIVATE || obj.state == STATE::FREEZE || obj.state == STATE::KILL
  1918.                         )
  1919.                                 Walker::onBehave(obj);
  1920.                         else //sleeping above water
  1921.                                 obj.curFrame = jjAnimations[obj.curAnim + 1].firstFrame + ((((obj.var[_objVar::AnimCounter] = obj.var[_objVar::AnimCounter] + 1) >> 1) / 7) % 5);
  1922.                 }
  1923.         }
  1924.        
  1925.        
  1926.         final class Pezrox_ClammyHorizontal : Floater {
  1927.                 Pezrox_ClammyHorizontal(const EnemyArguments &in arg) {
  1928.                         super(arg);
  1929.                         _speed = 2;
  1930.                         preset.points = 30;
  1931.                         preset.energy = 1;
  1932.                         preset.animSpeed = 0;
  1933.                         _direction = Directions::Left;
  1934.                 }
  1935.         }
  1936.        
  1937.         final class Pezrox_ClammyVertical : Enemy {
  1938.                 Pezrox_ClammyVertical(const EnemyArguments &in arg) {
  1939.                         super(arg);
  1940.                         _supportsSpeed = _supportsDirection = true;
  1941.                         _speed = 1;
  1942.                         preset.points = 30;
  1943.                         preset.energy = 1;
  1944.                         preset.animSpeed = 0;
  1945.                         _direction = Directions::Left;
  1946.                 }
  1947.                 void myBehave(jjOBJ@ obj) const override {
  1948.                         if (obj.state == STATE::START) {
  1949.                                 _adjustObjectDirection(obj);
  1950.                                 obj.direction = (obj.var[_objVar::DirectionCurrent] == -1) ? SPRITE::FLIPNONE : SPRITE::FLIPV;
  1951.                                 obj.state = STATE::FLY;
  1952.                         }
  1953.                        
  1954.                         const int direction = obj.var[_objVar::DirectionCurrent];
  1955.                         const jjANIMFRAME@ animFrame = jjAnimFrames[obj.curFrame];
  1956.                         const int positionForwards = int(obj.yPos + direction * animFrame.height / 2);
  1957.                        
  1958.                         if (
  1959.                                 positionForwards < 0 ||
  1960.                                 (jjEventGet(int(obj.xPos) / 32, positionForwards / 32) == AREA::STOPENEMY) ||
  1961.                                 (jjMaskedHLine(int(obj.xPos + animFrame.hotSpotX), animFrame.width, positionForwards)) || //wall
  1962.                                 positionForwards >= _levelHeight
  1963.                         ) {
  1964.                                 _reverseDirection(obj);
  1965.                                 obj.direction ^= 0x40; //vertically flipped
  1966.                         } else
  1967.                                 obj.yPos += direction * obj.xAcc;
  1968.                 }
  1969.         }
  1970.        
  1971.         final class Pezrox_GreenSnake : Walker {
  1972.                 Pezrox_GreenSnake(const EnemyArguments &in arg) {
  1973.                         super(arg);
  1974.                         _speed = 2;
  1975.                         preset.points = 20;
  1976.                         preset.energy = 1;
  1977.                         preset.animSpeed = 4;
  1978.                         _direction = Directions::Left;
  1979.                         _killAnimExplosion = true;
  1980.                 }
  1981.         }
  1982.        
  1983.        
  1984.         final class Crysilis_GoldenBounceSpike : Enemy {
  1985.                 Crysilis_GoldenBounceSpike(const EnemyArguments &in arg) {
  1986.                         super(arg);
  1987.                         preset.points = 40;
  1988.                         preset.energy = 5;
  1989.                 }
  1990.                 void myBehave(jjOBJ@ obj) const override {
  1991.                         int xOffset = (obj.var[_objVar::AnimCounter] >> 3) % 6;
  1992.                         if (xOffset > 2) xOffset = 5 - xOffset; //0, 1, 2, 2, 1, 0
  1993.                         obj.xPos = obj.xOrg - (xOffset << 2) * _scale;
  1994.                 }
  1995.         }
  1996.        
  1997.         final class Crysilis_LooGuard : PathFollower {
  1998.                 Crysilis_LooGuard(const EnemyArguments &in arg) {