Downloads containing Jazz1Enemies v03.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Holiday Hare '17Featured Download ShadowGPW Single player 8.8 Download file
TSF with JJ2+ Only: Holiday Hare '18Featured Download SmokeNC Single player 8.9 Download file

File preview

  1. /*************************
  2.  
  3.                                                                                 Jazz1Enemies v03.asc
  4.                                                                                 version 0.3
  5.  
  6. API:
  7.  
  8.  
  9.  
  10.         Jazz1::Enemy@ Jazz1::MakeEnemy(uint8 eventID, Jazz1::Enemies enemyID, bool useTrueColor = false)
  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 final 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(), you should use the TrueColor::EnableCaching function (see TrueColor.asc's documentation for details).
  22.                
  23.                 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 v03.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.:
  24.                
  25.                         Jazz1::MakeEnemy(OBJECT::NORMTURTLE, Jazz1::Enemies::Diamondus_TurtleGoon).SetDirection(Jazz1::Directions::Random).SetSpeed(6);
  26.                        
  27.                 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.
  28.                
  29.                 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:
  30.                
  31.                         Jazz1::MakeEnemy(OBJECT::APPLE, Jazz1::Enemies::Tubelectric_Blaster).SetDirection(Jazz1::Directions::Right);
  32.                         Jazz1::MakeEnemy(OBJECT::BANANA, Jazz1::Enemies::Tubelectric_Blaster).SetDirection(Jazz1::Directions::Left);
  33.                
  34.                
  35.                
  36.         List of Jazz1::Enemy methods (the return type is always Jazz1::Enemy@):
  37.                 SetSpeed(float speed)
  38.                         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.
  39.                        
  40.                 SetSpeedBasedOnParameter(uint offset, uint length, float modifier = 1.0f)
  41.                         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.
  42.                        
  43.                 SetDirection(int offsetOrConstant)
  44.                         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.
  45.                         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.
  46.                        
  47.                 SetFireDelay(uint delay)
  48.                         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.
  49.                        
  50.                 SetUsesJJ2StyleDeathAnimation(bool setTo)
  51.                         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.
  52.                         (This is a PURELY visual change. Either way the player will get points and potentially a pickup for killing the enemy.)
  53.                        
  54.                 SetWalkingEnemyCliffReaction(Jazz1::CliffReaction)
  55.                         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).
  56.                        
  57.                 SetDeathSound(SOUND::Sample sample)
  58.                 SetBulletFireSound(SOUND::Sample sample)
  59.                 SetBulletExplosionSound(SOUND::Sample sample)
  60.                         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.
  61.                        
  62.                        
  63.         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.
  64.        
  65.         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.)
  66.                        
  67.         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.
  68.        
  69.        
  70.        
  71.        
  72.        
  73.        
  74. Packaging Instructions:
  75.  
  76.  
  77.         In the most basic case, your script needs to include only one line:
  78.                 #include "Jazz1Enemies v03.asc"
  79.         This will automatically work in multiplayer servers; this library automatically inserts enough preprocessor instructions for clients to know to download the relevant files.
  80.        
  81.         If you are packaging a .zip archive for a level/mutator using this library, you will need to include all three of these files:
  82.                 Jazz1Enemies v03.asc
  83.                 Jazz1Enemies.j2a
  84.                 TrueColor.asc
  85.        
  86.        
  87.         If you make one or more calls to Jazz1::MakeEnemy with the "useTrueColor" argument equalling true, however, you will need to handle the .bmp 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 .bmp.
  88.  
  89.         For example, suppose you have two calls to Jazz1::MakeEnemy, one of them passing "useTrueColor" as false and one passing it as true:
  90.                 Jazz1::MakeEnemy(OBJECT::BUMBEE, Jazz1::Enemies::Diamondus_BumblingBee, false);
  91.                 Jazz1::MakeEnemy(OBJECT::NORMTURTLE, Jazz1::Enemies::Diamondus_TurtleGoon, true);
  92.         In this case, Diamondus_BumblingBee requires no special treatment (because it is not using True Color), but your script should include both these lines:
  93.                 #include "Jazz1Enemies v03.asc"
  94.                 #pragma require "Jazz1Enemies_Diamondus_TurtleGoon.bmp"
  95.         And your .zip archive should include these four files:
  96.                 Jazz1Enemies v03.asc
  97.                 Jazz1Enemies.j2a
  98.                 Jazz1Enemies_Diamondus_TurtleGoon.bmp
  99.                 TrueColor.asc
  100.                
  101.                
  102.         The pattern for True Color image filenames is perfectly regular: the name of the enemy, with "Jazz1Enemies_" instead of "Jazz1::" as the prefix, followed by the file extension .bmp. So:
  103.                 Jazz1::Enemies::Letni_BugCeiling -> Jazz1Enemies_Letni_BugCeiling.bmp
  104.                 Jazz1::Enemies::Marbelara_Schwarzenguard -> Jazz1Enemies_Marbelara_Schwarzenguard.bmp
  105.                 Jazz1::Enemies::Battleships_Generator -> Jazz1Enemies_Battleships_Generator.bmp
  106.         ...etc.
  107.                
  108. *************************/
  109.  
  110. namespace Jazz1 {
  111.         enum Enemies { _Misc_Anims,
  112.         //Full list of Enemy IDs for passing as the second argument to Jazz1::MakeEnemy (but remember to precede any name with "Jazz1::Enemies::"):
  113.                 Diamondus_BumblingBee, Diamondus_TurtleGoon,
  114.                 Tubelectric_BlasterHorizontal, Tubelectric_BlasterVertical, Tubelectric_Spark, Tubelectric_SparkBarrier, //for BlasterVertical, right=on floor, left=on ceiling
  115.                 Medivo_GhostRapierHorizontal, Medivo_GhostRapierVertical, Medivo_Helmut,
  116.                 Letni_Bug, Letni_BugCeiling, Letni_ElecBarrier,
  117.                 Technoir_MiniMine, Technoir_Misfire, Technoir_TanketyTankTank,
  118.                 Orbitus_BeholderPurple, Orbitus_BeholderSilver, Orbitus_SilverSnake,
  119.                 /* todo add more enemy code
  120.                 Fanolint_FlyFlower, Fanolint_PottedPlant, Fanolint_SuperTankety,
  121.                 Scraparap_GunnerDrone, Scraparap_LaunchCart, Scraprap_RoboTurtleDrone,
  122.                 Megairbase_Doofusguard, Megairbase_Missile, Megairbase_SuperSpark, //Megairbase also reuses Tubelectric_Spark, but that does not warrant a separate enum value
  123.                 Turtemple_JeTurtle, Turtemple_ScorpWeenie,*/
  124.                 Nippius_SkatePen, Nippius_SkiTurtle, Nippius_SnowGoon,
  125.                 /*Jungrock_JetSnake, Jungrock_RedBuzzer, Jungrock_YellowBuzzer,
  126.                 Marbelara_Drageen, Marbelara_Firebomb, Marbelara_Schwarzenguard,
  127.                 Slugion_Dragoon, Slugion_RedBat, Slugion_Sluggi,
  128.                 Dreempipes_Minite, Dreempipes_Overgrown, Dreempipes_TerrapinSwimmer,
  129.                 Pezrox_ClammyLR, Pezrox_ClammyUD, Pezrox_GreenSnake,
  130.                 Crysilis_GoldenBounceSpike, Crysilis_LooGuard,
  131.                 Battleships_ArmorDoofi, Battleships_BounceSpike, Battleships_Generator, Battleships_SuperBee,
  132.                 //todo figure out names for episode ABC enemies*/
  133.                 Holidaius_BlueDog, Holidaius_Devil, Holidaius_HandHorizontal, Holidaius_HandVertical, Holidaius_SkiTurtle, Holidaius_SnowMonkey, //for HandVertical, right=on floor, left=on ceiling
  134.                
  135.                 LAST
  136.         };
  137.        
  138.         //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.
  139.        
  140.        
  141.        
  142.        
  143.        
  144.        
  145.        
  146.        
  147.        
  148.        
  149.        
  150.        
  151.        
  152.        
  153.        
  154.        
  155.        
  156.        
  157.        
  158.        
  159.        
  160.        
  161.        
  162.        
  163.        
  164.        
  165.        
  166.        
  167.        
  168.        
  169.        
  170.        
  171.        
  172.        
  173.        
  174.        
  175.        
  176.        
  177.        
  178.        
  179.        
  180.        
  181.        
  182.        
  183.        
  184.         enum Directions { Left = -4, Right = -3, FaceJazz = -2, Random = -1 };
  185.         enum CliffReaction { TurnAround, Fall, Careen };
  186.        
  187.         enum _objVar { AnimCounter, DirectionCurrent, FireDelayCounter };
  188.         enum _causeOfDeath { Bullet, OrangeShards, BlueShards, GrayShards, PhysicalAttack, FrozenBullet, FrozenPhysicalAttack, AlreadyPerformedAnimation};
  189.         const float _levelWidth = jjLayerWidth[4] * 32;
  190.         const float _levelHeight = jjLayerHeight[4] * 32;
  191.        
  192.         array<jjANIMSET@> _animSets(Enemies::LAST, null);
  193.        
  194.         bool _trueColorHasProcessedPalette = false;
  195.        
  196.         int _getParameter(jjOBJ@ obj, int offset, int length) {
  197.                 return jjParameterGet(int(obj.xOrg) / 32, int(obj.yOrg) / 32, offset, length);
  198.         }
  199.         bool _maskedPixelFloat(float x, float y) {
  200.                 return jjMaskedPixel(int(x), int(y));
  201.         }
  202.         jjANIMSET@ _getAnimSet(Enemies enemyID) {
  203.                 if (_animSets[enemyID] is null)
  204.                         @_animSets[enemyID] = jjAnimSets[TrueColor::FindCustomAnim()].load(enemyID, "Jazz1Enemies v03.j2a");
  205.                 return _animSets[enemyID];
  206.         }
  207.         int _getPresetRelativeAnimID(uint8 eventID, int relativeAnimID) {
  208.                 const jjOBJ@ preset = jjObjectPresets[eventID];
  209.                 if (relativeAnimID < 0) //negative numbers mean generic Jazz1 animations
  210.                         return _getAnimSet(Enemies::_Misc_Anims).firstAnim - 1 - relativeAnimID;
  211.                 else if (relativeAnimID < 20) //small positive numbers mean animations specific to this Jazz1 enemy
  212.                         return preset.curAnim + relativeAnimID;
  213.                 else //large positive numbers are common JJ2 animations
  214.                         return relativeAnimID;
  215.                
  216.         }
  217.        
  218.         void _explosion(jjOBJ@ obj) {
  219.                 if (obj.ySpeed != 0) {
  220.                         obj.xPos += obj.xSpeed;
  221.                         obj.yPos += obj.ySpeed += 0.125f;
  222.                 }
  223.                 if (jjRandom() & 7 < uint(obj.ySpeed == 0 ? 3 : 5)) { //advance frame
  224.                         jjANIMATION@ anim = jjAnimations[obj.curAnim];
  225.                         if(++obj.frameID >= int(anim.frameCount)) {
  226.                                 obj.frameID = 0;
  227.                                 if (++obj.counterEnd >= obj.creatorID) { //_killAnimRepetitionCounts
  228.                                         obj.delete();
  229.                                         return;
  230.                                 }
  231.                         }
  232.                         obj.curFrame = anim.firstFrame + obj.frameID;
  233.                 }
  234.                 jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
  235.         }
  236.        
  237.         enum _bulletDirection { Left = -1, Either = 0, Right = 1 };
  238.         class _bulletPreferences {
  239.                 //_bullets.insertLast(_bulletPreferences(0, 3, 1, 0, _bulletDirection::Left));
  240.                 private float xSpeed, ySpeed;
  241.                 private uint animID, frameID;
  242.                 _bulletDirection direction;
  243.                 _bulletPreferences(){}//array purposes
  244.                 _bulletPreferences(float x, float y, uint a, uint f, _bulletDirection d) { xSpeed = x; ySpeed = y; animID = a; frameID = f; direction = d; }
  245.                 jjOBJ@ fire(const jjOBJ@ obj, int sound) const {
  246.                         int x1, x2, y1, y2;
  247.                         { //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
  248.                                 const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
  249.                                 if (obj.direction >= 0) {
  250.                                         x1 = frame.hotSpotX;
  251.                                         x2 = frame.gunSpotX;
  252.                                 } else { //horizontally flipped
  253.                                         x2 = frame.hotSpotX;
  254.                                         x1 = frame.gunSpotX;
  255.                                 }
  256.                                 if (obj.direction & 0xC0 == 0) {
  257.                                         y1 = frame.hotSpotY;
  258.                                         y2 = frame.gunSpotY;
  259.                                 } else { //vertically flipped
  260.                                         y2 = frame.hotSpotY;
  261.                                         y1 = frame.gunSpotY;
  262.                                 }
  263.                         }
  264.                         jjOBJ@ bullet = jjObjects[jjAddObject(
  265.                                 OBJECT::BULLET,
  266.                                 obj.xPos + x1 - x2,
  267.                                 obj.yPos + y1 - y2,
  268.                                 obj.objectID, CREATOR::OBJECT,
  269.                                 BEHAVIOR::INACTIVE
  270.                         )];
  271.                         bullet.xSpeed = xSpeed; bullet.ySpeed = ySpeed;
  272.                         bullet.curFrame = jjAnimations[jjObjectPresets[obj.eventID].curAnim + animID] + frameID;
  273.                         bullet.behavior = _bullet;
  274.                         bullet.special = sound;
  275.                         return bullet;
  276.                 }
  277.         }
  278.         void _bullet(jjOBJ@ obj) {
  279.                 if (obj.state == STATE::START) {
  280.                         obj.state = STATE::FLY;
  281.                         obj.playerHandling = HANDLING::ENEMYBULLET;
  282.                         obj.animSpeed = 1;
  283.                         obj.lightType = LIGHT::POINT2;
  284.                 }
  285.                 obj.xPos += obj.xSpeed;
  286.                 obj.yPos += obj.ySpeed;
  287.                 if (obj.curAnim == 0 || jjColorDepth == 8)
  288.                         jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame);
  289.                 else
  290.                         TrueColor::DrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curAnim);
  291.                 if (obj.state == STATE::EXPLODE || obj.xPos < 0 || obj.yPos < 0 || obj.xPos >= _levelWidth || obj.yPos >= _levelHeight || _maskedPixelFloat(obj.xPos, obj.yPos)) {
  292.                         obj.playerHandling = HANDLING::EXPLOSION;
  293.                         obj.curAnim = _getAnimSet(Enemies::_Misc_Anims).firstAnim + 1;
  294.                         obj.behavior = _explosion;
  295.                         obj.creatorID = 1; //repetitions
  296.                         obj.ySpeed = 0; //don't move
  297.                         obj.counterEnd = 0;
  298.                         if (obj.special >= 0)
  299.                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(obj.special));
  300.                 }
  301.         }
  302.        
  303.         abstract class Enemy : jjBEHAVIORINTERFACE {
  304.                 private string _behaviorName;
  305.                 private bool _useTrueColor;
  306.                 private TrueColor::Bitmap@ _trueColorBitmap;
  307.                 private 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
  308.                 private uint _firstFramePaletted, _firstFrameTrueColor;
  309.                
  310.                 protected array<int8>@ _animFrames = array<int8>(0), _fireFrames = null;
  311.                 protected uint _fireAnimID = 0;
  312.                
  313.                 Enemy(jjOBJ@ preset, bool tc, string name) {
  314.                         _behaviorName = name;
  315.                         _firstFramePaletted = preset.curFrame;
  316.                        
  317.                         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
  318.                                 _animFrames.insertLast(i);
  319.                        
  320.                         preset.behavior = this;
  321.                         preset.playerHandling = HANDLING::SPECIAL;
  322.                         preset.bulletHandling = HANDLING::DETECTBULLET;
  323.                         preset.scriptedCollisions = true;
  324.                         preset.isTarget = true;
  325.                         preset.isFreezable = true;
  326.                         preset.isBlastable = false;
  327.                         preset.triggersTNT = true;
  328.                         preset.causesRicochet = false;
  329.                         preset.deactivates = true;
  330.                         preset.direction = 1;
  331.                         preset.state = STATE::START;
  332.                         preset.freeze = 0;
  333.                         preset.energy = preset.points = preset.animSpeed = 0; //these should be overridden
  334.                         preset.killAnim = -1; //first misc anim
  335.                         preset.var[_objVar::AnimCounter] = 0;
  336.                         preset.var[_objVar::FireDelayCounter] = 0;
  337.                        
  338.                         if (_useTrueColor = tc) {
  339.                                 if (!_trueColorHasProcessedPalette) {
  340.                                         TrueColor::ProcessPalette();
  341.                                         _trueColorHasProcessedPalette = true;
  342.                                 }
  343.                                
  344.                                 uint numberOfAnimationsInAnimSet = 0; //this number is not directly stored anywhere, so I have to infer it from when I run out of used animations
  345.                                 {
  346.                                         uint animID = preset.curAnim;
  347.                                         while (jjAnimations[animID++].frameCount != 0)
  348.                                                 numberOfAnimationsInAnimSet += 1;
  349.                                 }
  350.                                
  351.                                 uint leftPostionOfAnimFrameInSpriteSheet = 0;
  352.                                 for (uint i = 0; i < numberOfAnimationsInAnimSet; ++i) { //generate an array<TrueColor::Coordinates> based on the animset's animations' frames' properties
  353.                                         const jjANIMATION@ animation = jjAnimations[preset.curAnim + i];
  354.                                         for (uint j = 0; j < animation.frameCount; ++j) {
  355.                                                 const jjANIMFRAME@ animFrame = jjAnimFrames[animation + j];
  356.                                                 _trueColorCoordinates[0].insertLast(TrueColor::Coordinates(
  357.                                                         leftPostionOfAnimFrameInSpriteSheet, 0,
  358.                                                         animFrame.width, animFrame.height,
  359.                                                         animFrame.hotSpotX, animFrame.hotSpotY/*, //the other properties aren't needed
  360.                                                         animFrame.gunSpotX, animFrame.gunSpotY,
  361.                                                         animFrame.coldSpotX, animFrame.coldSpotY*/
  362.                                                 ));
  363.                                                 leftPostionOfAnimFrameInSpriteSheet += animFrame.width; //the next sprite on the spritesheet, if any, will be placed immediately to the right of this one
  364.                                         }
  365.                                 }
  366.                                
  367.                                 const ANIM::Set trueColorAnimSet = TrueColor::FindCustomAnim();
  368.                                 TrueColor::AllocateSpriteSheet(
  369.                                         trueColorAnimSet,
  370.                                         @_trueColorBitmap = TrueColor::Bitmap("Jazz1Enemies_" + name),
  371.                                         _trueColorCoordinates
  372.                                 );
  373.                                 _firstFrameTrueColor = jjAnimations[jjAnimSets[trueColorAnimSet]];
  374.                         }
  375.                 }
  376.                 void onBehave(jjOBJ@ obj) {
  377.                         if (obj.state == STATE::DEACTIVATE)
  378.                                 obj.deactivate();
  379.                         else if (obj.state == STATE::KILL) {
  380.                                 if (_useJJ2DeathAnimation) {
  381.                                         switch (_died) {
  382.                                                 case _causeOfDeath::Bullet:
  383.                                                         obj.particlePixelExplosion(0);
  384.                                                         break;
  385.                                                 case _causeOfDeath::OrangeShards:
  386.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
  387.                                                         obj.particlePixelExplosion(1);
  388.                                                         break;
  389.                                                 case _causeOfDeath::BlueShards:
  390.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
  391.                                                         obj.particlePixelExplosion(32);
  392.                                                         break;
  393.                                                 case _causeOfDeath::GrayShards:
  394.                                                         jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BURN);
  395.                                                         obj.particlePixelExplosion(72);
  396.                                                         break;
  397.                                                 case _causeOfDeath::PhysicalAttack:
  398.                                                         obj.particlePixelExplosion(2);
  399.                                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_SPLAT1 + (jjRandom() & 3)));
  400.                                                         break;
  401.                                                 case _causeOfDeath::FrozenBullet:
  402.                                                         obj.unfreeze(0);
  403.                                                         break;
  404.                                                 case _causeOfDeath::FrozenPhysicalAttack:
  405.                                                         obj.unfreeze(1);
  406.                                                         break;
  407.                                                 case _causeOfDeath::AlreadyPerformedAnimation:
  408.                                                         break;
  409.                                         }
  410.                                 } else { //Jazz1-style
  411.                                         const auto killAnim = _getPresetRelativeAnimID(obj.eventID, int(obj.killAnim));
  412.                                         for (int i = 0; i < 7; ++i) {
  413.                                                 jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, _killAnimRepetitionCounts)];
  414.                                                 shard.curAnim = killAnim;
  415.                                                 shard.behavior = _explosion;
  416.                                                 shard.counterEnd = 0;
  417.                                                 if (_killAnimExplosion) {
  418.                                                         shard.behave(BEHAVIOR::SHARD); //get some random directions
  419.                                                         shard.ySpeed += 0.0001; //non-zero
  420.                                                 } else
  421.                                                         break; //only one
  422.                                         }
  423.                                 }
  424.                                 if (_deathSound >= 0)
  425.                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(_deathSound));
  426.                                 obj.delete();
  427.                         } else if (obj.state == STATE::FREEZE) {
  428.                                 if (obj.freeze-- <= 1) {
  429.                                         obj.freeze = 0;
  430.                                         obj.state = obj.oldState;
  431.                                 }
  432.                         } else {
  433.                                 if (obj.state == STATE::START) {
  434.                                         if (_supportsSpeed) {
  435.                                                 if (_speedParamOffset >= 0)
  436.                                                         obj.xSpeed = (_getParameter(obj, _speedParamOffset, _speedParamLength) + 1) * _speed;
  437.                                                 else
  438.                                                         obj.xSpeed = _speed;
  439.                                         }
  440.                                         if (jjDifficulty >= 3) //turbo
  441.                                                 obj.energy += 1;
  442.                                 }
  443.                                
  444.                                 if (_supportsFire) {
  445.                                         if (obj.counterEnd > _fireDelay) { //showing fire animation
  446.                                                 obj.var[_objVar::AnimCounter] = obj.var[_objVar::AnimCounter] + 1;
  447.                                                 if (obj.var[_objVar::AnimCounter] > obj.animSpeed) {
  448.                                                         obj.var[_objVar::AnimCounter] = 0;
  449.                                                         if (uint(++obj.frameID) >= _fireFrames.length) {
  450.                                                                 _fireBullets(obj);
  451.                                                                 obj.counterEnd = 0;
  452.                                                                 return;
  453.                                                         }
  454.                                                 }
  455.                                                 obj.curFrame = jjAnimations[_getPresetRelativeAnimID(obj.eventID, _fireAnimID)].firstFrame + _fireFrames[obj.frameID];
  456.                                                 return;
  457.                                         } else if (++obj.counterEnd > _fireDelay) { //start firing
  458.                                                 if (_fireFrames !is null && _fireFrames.length > 0) {
  459.                                                         obj.var[_objVar::AnimCounter] = 0;
  460.                                                         obj.frameID = 0;
  461.                                                         obj.curFrame = jjAnimations[_getPresetRelativeAnimID(obj.eventID, _fireAnimID)].firstFrame + _fireFrames[obj.frameID];
  462.                                                         return;
  463.                                                 } else {
  464.                                                         _fireBullets(obj);
  465.                                                         obj.counterEnd = 0;
  466.                                                         //fall through...
  467.                                                 }
  468.                                         }
  469.                                 }
  470.                                
  471.                                 myBehave(obj);
  472.                                
  473.                                 jjANIMATION@ anim = jjAnimations[obj.curAnim];
  474.                                 obj.var[_objVar::AnimCounter] = obj.var[_objVar::AnimCounter] + 1;
  475.                                 obj.curFrame = anim.firstFrame + _animFrames[(((obj.var[_objVar::AnimCounter] >> 1) / (obj.animSpeed+1)) % _animFrames.length)];
  476.                                
  477.                                 if (_flipSpriteWhenMovingLeft) //otherwise always face right
  478.                                         obj.direction = obj.var[_objVar::DirectionCurrent];
  479.                         }
  480.                 }
  481.                 protected void myBehave(jjOBJ@ obj) const { obj.state = STATE::IDLE; } //here to be overridden
  482.                
  483.                 protected void _drawBodyFrame(const jjOBJ@ obj, float xPos, float yPos, uint frameID, int direction) const {
  484.                         if (!_useTrueColor || jjColorDepth == 8 || obj.freeze != 0 || obj.justHit != 0)
  485.                                 jjDrawSpriteFromCurFrame(
  486.                                         xPos, yPos, frameID, direction,
  487.                                         (obj.freeze == 0) ? (obj.justHit == 0) ? SPRITE::NORMAL : SPRITE::SINGLECOLOR : SPRITE::FROZEN,
  488.                                         15
  489.                                 );
  490.                         else
  491.                                 TrueColor::DrawSpriteFromCurFrame(
  492.                                         xPos, yPos,
  493.                                         _firstFrameTrueColor + (frameID - _firstFramePaletted) * TrueColor::NumberOfFramesPerImage,
  494.                                         direction
  495.                                 );
  496.                 }
  497.                 void onDraw(jjOBJ@ obj) {
  498.                         if (obj.isActive)
  499.                                 _drawBodyFrame(obj, obj.xPos, obj.yPos, obj.curFrame, obj.direction);
  500.                 }
  501.                
  502.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) { //mostly copied from plus52Scripting.j2as
  503.                         if (bullet !is null) {
  504.                                 //recreation of HANDLING::HURTBYBULLET with HANDLING::ENEMY
  505.                                 if (obj.causesRicochet) {
  506.                                         if ((bullet.var[6] & 6) == 0) //not fire-based, not a laser beam
  507.                                                 bullet.ricochet();
  508.                                         else if ((bullet.var[6] & 4) == 0) //not a laser beam
  509.                                                 bullet.delete();
  510.                                 } else if ((bullet.var[6] & 16) == 0) //not a fireball
  511.                                         bullet.state = STATE::EXPLODE;
  512.                                 if (obj.freeze > 0 && force < 3)
  513.                                         force = 3;
  514.                                 obj.energy -= force;
  515.                                 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.
  516.                                 if (obj.energy <= 0) { //killed
  517.                                         obj.energy = 0;
  518.                                         if (obj.freeze > 0)
  519.                                                 _died = _causeOfDeath::FrozenBullet;
  520.                                         else if ((bullet.var[6] & 2) == 0) //not fire-based
  521.                                                 _died = _causeOfDeath::Bullet;
  522.                                         else if ((bullet.var[6] & 4) != 0) //laser beam
  523.                                                 _died = _causeOfDeath::GrayShards;
  524.                                         else
  525.                                                 _died = ((bullet.var[6] & 8) != 0) ? _causeOfDeath::BlueShards : _causeOfDeath::OrangeShards; //powered-up (blue) or not (orange)
  526.                                         if (player !is null) {
  527.                                                 obj.grantPickup(player, (uint(bullet.curAnim) == jjAnimSets[ANIM::AMMO].firstAnim + 17) ? 5 : 10);
  528.                                                 givePlayerPointsForObject(player, obj);
  529.                                         }
  530.                                         jjKillObject(obj.objectID);
  531.                                 } else
  532.                                         obj.freeze = 0;
  533.                         } else { //recreation of HANDLING::ENEMY; player guaranteed to be non-null
  534.                                 if (force != 0) { //attacking via special attack, e.g. buttstomp
  535.                                         obj.energy -= 4; //constant amount of damage for special attacks
  536.                                         if (obj.energy <= 0) { //killed
  537.                                                 obj.energy = 0;
  538.                                                 if (obj.freeze > 0)
  539.                                                         _died = _causeOfDeath::FrozenPhysicalAttack;
  540.                                                 else
  541.                                                         _died = _causeOfDeath::PhysicalAttack;
  542.                                                 givePlayerPointsForObject(player, obj);
  543.                                                 jjKillObject(obj.objectID);
  544.                                         } else { //only wounded
  545.                                                 obj.justHit = 5;
  546.                                                 if (obj.freeze <= 0)
  547.                                                         jjSample(obj.xPos, obj.yPos, SOUND::Sample(SOUND::COMMON_SPLAT1 + (jjRandom() & 3)));
  548.                                         }
  549.                                        
  550.                                         if (force > 0) { //buttstomp or sugar rush
  551.                                                 player.buttstomp = 50; //landing
  552.                                                 player.ySpeed = player.ySpeed / -2 - 8;
  553.                                                 player.yAcc = 0;
  554.                                                 player.extendInvincibility(-70);
  555.                                         } else if (force == -101) { //running into frozen enemy
  556.                                                 player.xAcc = 0;
  557.                                                 player.xSpeed /= -2;
  558.                                                 player.ySpeed = -6;
  559.                                                 player.extendInvincibility(-10);
  560.                                         }
  561.                                 } else  { //not attacking
  562.                                         if (obj.freeze == 0)
  563.                                                 player.hurt();
  564.                                 }
  565.                         }
  566.                         return true;
  567.                 }
  568.                
  569.                 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.
  570.                         if (player is null)
  571.                                 return false;
  572.                         if (obj.points != 0 && (jjGameMode == GAME::SP || jjGameMode == GAME::COOP)) {
  573.                                 player.score += obj.points; //todo add some other options for how to translate these scores into JJ2 scores... multiply by 50, or round up to the nearest 50
  574.                                 jjPARTICLE@ particle = jjAddParticle(PARTICLE::STRING);
  575.                                 if (particle !is null) {
  576.                                         particle.xPos = obj.xPos;
  577.                                         particle.yPos = obj.yPos;
  578.                                         particle.xSpeed = (-32768 - int(jjRandom() & 0x3FFF)) / 65536.f;
  579.                                         particle.ySpeed = (-65536 - int(jjRandom() & 0x7FFF)) / 65536.f;
  580.                                         particle.string.text = formatInt(obj.points);
  581.                                 }
  582.                                 obj.points = 0;
  583.                                 return true;
  584.                         }
  585.                         return false;
  586.                 }
  587.                 private void _fireBullets(const jjOBJ@ obj) const {
  588.                         for (uint i = 0; i < _bullets.length; ++i)
  589.                                 if (-obj.var[_objVar::DirectionCurrent] != _bullets[i].direction) { //compatible with the object's current direction
  590.                                         jjOBJ@ bullet = _bullets[i].fire(obj, _explosionSound);
  591.                                         bullet.curAnim = (!_useTrueColor ? 0 : (_firstFrameTrueColor + (bullet.curFrame - _firstFramePaletted) * TrueColor::NumberOfFramesPerImage));
  592.                                 }
  593.                         if (_fireSound >= 0)
  594.                                 jjSample(obj.xPos, obj.yPos, SOUND::Sample(_fireSound));
  595.                 }
  596.                
  597.                 protected bool
  598.                         _supportsSpeed = false,
  599.                         _supportsDirection = false,
  600.                         _supportsFire = false,
  601.                         _flipSpriteWhenMovingLeft = false;
  602.                
  603.                
  604.                 protected int _adjustObjectDirection(jjOBJ@ obj) {
  605.                         switch (_direction) {
  606.                                 case Directions::Right:
  607.                                         obj.var[_objVar::DirectionCurrent] = 1;
  608.                                         break;
  609.                                 case Directions::Left:
  610.                                         obj.var[_objVar::DirectionCurrent] = -1;
  611.                                         break;
  612.                                 case Directions::FaceJazz:
  613.                                         obj.var[_objVar::DirectionCurrent] = (obj.xPos > jjLocalPlayers[0].xPos) ? -1 : 1;
  614.                                         break;
  615.                                 case Directions::Random:
  616.                                         obj.var[_objVar::DirectionCurrent] = int(jjRandom() & 1) * 2 - 1;
  617.                                         break;
  618.                                 default:
  619.                                         obj.var[_objVar::DirectionCurrent] = _getParameter(obj, _direction, 1) * 2 - 1;
  620.                                         break;
  621.                         }
  622.                         return obj.var[_objVar::DirectionCurrent];
  623.                 }
  624.                        
  625.                 protected int _direction = Directions::Right;
  626.                 Enemy@ SetDirection(int dir) {
  627.                         if (_supportsDirection)
  628.                                 _direction = dir;
  629.                         else
  630.                                 jjDebug(_behaviorName + " does not support the SetDirection method.");
  631.                         return this;
  632.                 }
  633.                 protected void _reverseDirection(jjOBJ@ obj) {
  634.                         obj.var[_objVar::DirectionCurrent] = -obj.var[_objVar::DirectionCurrent];
  635.                 }
  636.                
  637.                 protected float _speed;
  638.                 private int _speedParamOffset = -1;
  639.                 private uint _speedParamLength;
  640.                 Enemy@ SetSpeed(float speed) {
  641.                         if (_supportsSpeed) {
  642.                                 _speed = abs(speed);
  643.                                 _speedParamOffset = -1;
  644.                         } else
  645.                                 jjDebug(_behaviorName + " does not support the SetSpeed* methods.");
  646.                         return this;
  647.                 }
  648.                 Enemy@ SetSpeedBasedOnParameter(uint o, uint l, float m = 1.0f) {
  649.                         if (_supportsSpeed) {
  650.                                 _speed = abs(m);
  651.                                 _speedParamOffset = o;
  652.                                 _speedParamLength = l;
  653.                         } else
  654.                                 jjDebug(_behaviorName + " does not support the SetSpeed* methods.");
  655.                         return this;
  656.                 }
  657.                
  658.                 protected uint _fireDelay = 0;
  659.                 protected array<_bulletPreferences> _bullets(0);
  660.                 Enemy@ SetFireDelay(uint delay) {
  661.                         if (_supportsFire)
  662.                                 _fireDelay = delay;
  663.                         else
  664.                                 jjDebug(_behaviorName + " does not support the SetFireDelayTo method.");
  665.                         return this;
  666.                 }
  667.                
  668.                 private int _deathSound = -1, _fireSound = -1, _explosionSound = -1;
  669.                 Enemy@ SetDeathSound(SOUND::Sample sample) {
  670.                         _deathSound = sample;
  671.                         return this;
  672.                 }
  673.                 Enemy@ SetBulletFireSound(SOUND::Sample sample) {
  674.                         if (_supportsFire)
  675.                                 _fireSound = sample;
  676.                         else
  677.                                 jjDebug(_behaviorName + " does not support the SetBulletFireSound method.");
  678.                         return this;
  679.                 }
  680.                 Enemy@ SetBulletExplosionSound(SOUND::Sample sample) {
  681.                         if (_supportsFire)
  682.                                 _explosionSound = sample;
  683.                         else
  684.                                 jjDebug(_behaviorName + " does not support the SetBulletExplosionSound method.");
  685.                         return this;
  686.                 }
  687.                
  688.                 private _causeOfDeath _died = _causeOfDeath::Bullet;
  689.                 protected bool _killAnimExplosion = false;
  690.                 protected uint _killAnimRepetitionCounts = 1;
  691.                 private bool _useJJ2DeathAnimation = false;
  692.                 Enemy@ SetUsesJJ2StyleDeathAnimation(bool setTo) {
  693.                         _useJJ2DeathAnimation = setTo;
  694.                         return this;
  695.                 }
  696.                
  697.                 Enemy@ SetWalkingEnemyCliffReaction(CliffReaction) { //overridden by Walker
  698.                         jjDebug(_behaviorName + " is not a walking enemy.");
  699.                         return this;
  700.                 }
  701.         }
  702.         Enemy@ MakeEnemy(uint8 eventID, Enemies enemyID, bool useTrueColor = false) {
  703.                 if (enemyID <= Enemies::_Misc_Anims || enemyID >= Enemies::LAST) {
  704.                         jjDebug("Invalid Jazz1::Enemies enum value " + enemyID + ".");
  705.                         return null;
  706.                 }
  707.                
  708.                 jjOBJ@ preset = jjObjectPresets[eventID];
  709.                 preset.curAnim = _getAnimSet(enemyID).firstAnim;
  710.                 preset.curFrame = jjAnimations[preset.curAnim].firstFrame;
  711.                
  712.                 switch (enemyID) {
  713.                         case Enemies::Diamondus_TurtleGoon:                             Diamondus_TurtleGoon                    (preset, useTrueColor); break;
  714.                         case Enemies::Diamondus_BumblingBee:                    Diamondus_BumblingBee                   (preset, useTrueColor); break;
  715.                         case Enemies::Tubelectric_BlasterHorizontal:    Tubelectric_BlasterHorizontal   (preset, useTrueColor); break;
  716.                         case Enemies::Tubelectric_BlasterVertical:              Tubelectric_BlasterVertical             (preset, useTrueColor); break;
  717.                         case Enemies::Tubelectric_Spark:                                Tubelectric_Spark                               (preset, useTrueColor); break;
  718.                         case Enemies::Tubelectric_SparkBarrier:                 Tubelectric_SparkBarrier                (preset, useTrueColor); break;
  719.                         case Enemies::Medivo_GhostRapierHorizontal:             Medivo_GhostRapierHorizontal    (preset, useTrueColor); break;
  720.                         case Enemies::Medivo_GhostRapierVertical:               Medivo_GhostRapierVertical              (preset, useTrueColor); break;
  721.                         case Enemies::Medivo_Helmut:                                    Medivo_Helmut                                   (preset, useTrueColor); break;
  722.                         case Enemies::Letni_Bug:                                                Letni_Bug                                               (preset, useTrueColor); break;
  723.                         case Enemies::Letni_BugCeiling:                                 Letni_BugCeiling                                (preset, useTrueColor); break;
  724.                         case Enemies::Letni_ElecBarrier:                                Letni_ElecBarrier                               (preset, useTrueColor); break;
  725.                         case Enemies::Technoir_MiniMine:                                Technoir_MiniMine                               (preset, useTrueColor); break;
  726.                         case Enemies::Technoir_Misfire:                                 Technoir_Misfire                                (preset, useTrueColor); break;
  727.                         case Enemies::Technoir_TanketyTankTank:                 Technoir_TanketyTankTank                (preset, useTrueColor); break;
  728.                         case Enemies::Orbitus_BeholderPurple:                   Orbitus_BeholderPurple                  (preset, useTrueColor); break;
  729.                         case Enemies::Orbitus_BeholderSilver:                   Orbitus_BeholderSilver                  (preset, useTrueColor); break;
  730.                         case Enemies::Orbitus_SilverSnake:                              Orbitus_SilverSnake                             (preset, useTrueColor); break;
  731.                         case Enemies::Nippius_SkatePen:                                 Nippius_SkatePen                                (preset, useTrueColor); break;
  732.                         case Enemies::Nippius_SkiTurtle:                                Nippius_SkiTurtle                               (preset, useTrueColor); break;
  733.                         case Enemies::Nippius_SnowGoon:                                 Nippius_SnowGoon                                (preset, useTrueColor); break;
  734.                         case Enemies::Holidaius_BlueDog:                                Holidaius_BlueDog                               (preset, useTrueColor); break;
  735.                         case Enemies::Holidaius_Devil:                                  Holidaius_Devil                                 (preset, useTrueColor); break;
  736.                         case Enemies::Holidaius_HandHorizontal:                 Holidaius_HandHorizontal                (preset, useTrueColor); break;
  737.                         case Enemies::Holidaius_HandVertical:                   Holidaius_HandVertical                  (preset, useTrueColor); break;
  738.                         case Enemies::Holidaius_SkiTurtle:                              Holidaius_SkiTurtle                             (preset, useTrueColor); break;
  739.                         case Enemies::Holidaius_SnowMonkey:                             Holidaius_SnowMonkey                    (preset, useTrueColor); break;
  740.                         default: return null;
  741.                 }
  742.                
  743.                 return cast<Enemy>(cast<jjBEHAVIORINTERFACE>(preset.behavior));
  744.         }
  745.        
  746.        
  747.        
  748.         abstract class Walker : Enemy {
  749.                 protected CliffReaction _cliffReaction = CliffReaction::TurnAround;
  750.                 Walker(jjOBJ@ preset, bool tc, string name) {
  751.                         super(preset, tc, name);
  752.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  753.                 }
  754.                 void myBehave(jjOBJ@ obj) const override {
  755.                         if (obj.state == STATE::START) {
  756.                                 _adjustObjectDirection(obj);
  757.                                 obj.state = STATE::WALK;
  758.                                 obj.putOnGround(true);
  759.                                 obj.special = jjMaskedTopVLine(int(obj.xPos), int(obj.yPos), 64); //properDistanceFromGround
  760.                         }
  761.                        
  762.                         const int direction = obj.var[_objVar::DirectionCurrent];
  763.                         const int spriteWidth = jjAnimFrames[obj.curFrame].width / 2;
  764.                         const float positionForwards = obj.xPos + direction * spriteWidth;
  765.                         const float positionBackwards = obj.xPos - direction * spriteWidth;
  766.                         int distanceFromGroundForwards = jjMaskedTopVLine(int(positionForwards), int(obj.yPos), 64);
  767.                         int distanceFromGroundBackwards = jjMaskedTopVLine(int(positionBackwards), int(obj.yPos), 64);
  768.                        
  769.                         if (jjEventGet(int(positionForwards) / 32, int(obj.yPos) / 32) == AREA::STOPENEMY) {
  770.                                 _reverseDirection(obj); //it takes a tick to turn around
  771.                         } else {
  772.                                 if (distanceFromGroundForwards > obj.special && distanceFromGroundBackwards > obj.special) { //in the air
  773.                                         obj.yPos += obj.ySpeed += 0.125f;
  774.                                         if (obj.ySpeed > 4.f) obj.ySpeed = 4;
  775.                                         distanceFromGroundForwards = jjMaskedTopVLine(int(positionForwards), int(obj.yPos), 64);
  776.                                         distanceFromGroundBackwards = jjMaskedTopVLine(int(positionBackwards), int(obj.yPos), 64);
  777.                                         int currentDistanceFromGround = (distanceFromGroundForwards < distanceFromGroundBackwards) ? distanceFromGroundForwards : distanceFromGroundBackwards;
  778.                                         if (currentDistanceFromGround <= obj.special) { //landed
  779.                                                 obj.yPos -= obj.special - currentDistanceFromGround;
  780.                                                 distanceFromGroundForwards = obj.special; //don't instantly turn around
  781.                                                 obj.ySpeed = 0;
  782.                                         }
  783.                                         if (_cliffReaction != CliffReaction::Careen)
  784.                                                 return; //no horizontal movement
  785.                                 }
  786.                                
  787.                                 if (distanceFromGroundForwards < obj.special) {
  788.                                         _reverseDirection(obj);
  789.                                 } else if (distanceFromGroundForwards > obj.special && _cliffReaction == CliffReaction::TurnAround) {
  790.                                         _reverseDirection(obj);
  791.                                 } else {
  792.                                         obj.xPos += direction * obj.xSpeed;
  793.                                 }
  794.                         }
  795.                 }
  796.         }
  797.        
  798.         const array<array<int8>> _paths = {
  799.                 {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
  800.                 {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
  801.                 {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
  802.                 {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
  803.         };
  804.         abstract class PathFollower : Enemy {
  805.                 PathFollower(jjOBJ@ preset, bool tc, string name, int pathID) {
  806.                         super(preset, tc, name);
  807.                         preset.special = pathID;
  808.                         preset.xAcc = 0; //distance moved
  809.                         _flipSpriteWhenMovingLeft = _supportsSpeed = true;
  810.                 }
  811.                 void myBehave(jjOBJ@ obj) const override {
  812.                         if (obj.state == STATE::START) {
  813.                                 _adjustObjectDirection(obj);
  814.                                 obj.state = STATE::FLY;
  815.                                 obj.xSpeed /= 4; //adjust for not moving in blocks of 4 pixels at a time anymore
  816.                         }
  817.                         const array<int8>@ path = _paths[obj.special];
  818.                         const int numberOfPointsOnPath = path.length / 2;
  819.                         obj.xAcc += obj.xSpeed; //move forwards
  820.                         const int point1 = (int(obj.xAcc) % numberOfPointsOnPath) * 2;
  821.                         const float nearnessToNextPoint = obj.xAcc % 1.f; //instead of jumping from point to point, move smoothly during the transition gameticks
  822.                        
  823.                         const float directionMovingAhead = path[(point1+3) % path.length] - path[point1+1];
  824.                         if (directionMovingAhead > 0)
  825.                                 obj.var[_objVar::DirectionCurrent] = 1;
  826.                         else if (directionMovingAhead < 0)
  827.                                 obj.var[_objVar::DirectionCurrent] = -1;
  828.                        
  829.                         obj.xPos = obj.xOrg + (path[point1+1] + nearnessToNextPoint * directionMovingAhead) * 4;
  830.                         obj.yPos = obj.yOrg + path[point1+0] + nearnessToNextPoint * (path[(point1+2) % path.length] - path[point1+0]);
  831.                 }
  832.         }
  833.         abstract class Snake : PathFollower {
  834.                 protected uint
  835.                         _segmentStrength = 1, //jjOBJ::energy should be a multiple of this
  836.                         _segmentSeparation = 1;
  837.                 Snake(jjOBJ@ preset, bool tc, string name, int pathID) {
  838.                         super(preset, tc, name, pathID);
  839.                 }
  840.                 void onDraw(jjOBJ@ obj) override {
  841.                         const array<int8>@ path = _paths[obj.special];
  842.                         const uint numberOfSegments = obj.energy / _segmentStrength + 1;
  843.                         const uint numberOfPointsOnPath = path.length / 2;
  844.                        
  845.                         float oldPosition = obj.xAcc - obj.xSpeed * _segmentSeparation * numberOfSegments;
  846.                         uint i = 0;
  847.                         while (true) {
  848.                                 const float nearnessToNextPoint = (oldPosition) % 1.f; //instead of jumping from point to point, move smoothly during the transition gameticks
  849.                                 int pointIndex = int(oldPosition);
  850.                                 while (pointIndex < 0) pointIndex += numberOfPointsOnPath; //% doesn't really work with negative numbers
  851.                                 pointIndex %= numberOfPointsOnPath;
  852.                                 const uint pointIndex2 = ((pointIndex + 1) % numberOfPointsOnPath) * 2;
  853.                                 _drawBodyFrame(
  854.                                         obj,
  855.                                         obj.xOrg + (path[pointIndex * 2 + 1] + nearnessToNextPoint * (path[pointIndex2 + 1] - path[pointIndex * 2 + 1])) * 4,
  856.                                         obj.yOrg + (path[pointIndex * 2 + 0] + nearnessToNextPoint * (path[pointIndex2 + 0] - path[pointIndex * 2 + 0])),
  857.                                         jjAnimations[obj.curAnim + 1] + (i == 0 ? 1 : 0), //hardcoded: snake body, tail are the two frames immediately following the head's animation
  858.                                         1
  859.                                 );
  860.                                 if (++i >= numberOfSegments)
  861.                                         break;
  862.                                 oldPosition += obj.xSpeed * _segmentSeparation;
  863.                         }
  864.                        
  865.                         Enemy::onDraw(obj);
  866.                 }
  867.                 bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) override {
  868.                         const auto numberOfSegmentsOld = obj.energy / _segmentStrength;
  869.                         Enemy::onObjectHit(obj, bullet, player, force);
  870.                         if (obj.isActive && (obj.energy / _segmentStrength) < numberOfSegmentsOld) { //hurt; drop a segment
  871.                                 jjOBJ@ shard = jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos, _killAnimRepetitionCounts)];
  872.                                 shard.curAnim = _getPresetRelativeAnimID(obj.eventID, int(obj.killAnim));
  873.                                 shard.behavior = _explosion;
  874.                                 shard.counterEnd = 0;
  875.                                 shard.xSpeed = ((bullet !is null) ? bullet.xSpeed : player.xSpeed) / 2;
  876.                                 shard.ySpeed = ((bullet !is null) ? bullet.ySpeed : player.ySpeed) / 2 + 0.0001; //non-zero
  877.                         }
  878.                         return true;
  879.                 }
  880.         }
  881.        
  882.         abstract class StalkerGhost : Enemy {
  883.                 StalkerGhost(jjOBJ@ preset, bool tc, string name) {
  884.                         super(preset, tc, name);
  885.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  886.                 }
  887.                 void myBehave(jjOBJ@ obj) const override {
  888.                         if (obj.state == STATE::START) {
  889.                                 _adjustObjectDirection(obj); //get an initial direction in case the player is facing us
  890.                                 obj.state = STATE::FLY;
  891.                         }
  892.                         const int nearestPlayerID = obj.findNearestPlayer(320*320);
  893.                         if (nearestPlayerID >= 0) {
  894.                                 const jjPLAYER@ play = jjPlayers[nearestPlayerID];
  895.                                 const bool objectToRightOfPlayer = obj.xPos > play.xPos;
  896.                                 if ((play.direction >= 0) != objectToRightOfPlayer && abs(obj.xPos - play.xPos) > 20) { //player looking away from a sufficiently distant enemy
  897.                                         obj.var[_objVar::DirectionCurrent] = objectToRightOfPlayer ? -1 : 1;
  898.                                         obj.xPos += obj.xSpeed * obj.var[_objVar::DirectionCurrent];
  899.                                         const float yDist = obj.yPos - play.yPos;
  900.                                         if (abs(yDist) > 20) { //move vertically... but only do so if moving horizontally
  901.                                                 if (yDist > 0)
  902.                                                         obj.yPos -= obj.xSpeed / 4;
  903.                                                 else
  904.                                                         obj.yPos += obj.xSpeed / 4;
  905.                                         }
  906.                                 }
  907.                         }
  908.                 }
  909.         }
  910.        
  911.         abstract class Missile : Enemy {
  912.                 Missile(jjOBJ@ preset, bool tc, string name) {
  913.                         super(preset, tc, name);
  914.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  915.                 }
  916.                 void myBehave(jjOBJ@ obj) const override {
  917.                         if (obj.state == STATE::START) {
  918.                                 _adjustObjectDirection(obj); //get an initial direction in case the player is facing us
  919.                                 obj.state = STATE::FLY;
  920.                         }
  921.                         obj.xPos += obj.xSpeed * obj.var[_objVar::DirectionCurrent];
  922.                 }
  923.         }
  924.        
  925.         abstract class Floater : Enemy {
  926.                 Floater(jjOBJ@ preset, bool tc, string name, int pathID) {
  927.                         super(preset, tc, name);
  928.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  929.                 }
  930.                 void myBehave(jjOBJ@ obj) const override {
  931.                         if (obj.state == STATE::START) {
  932.                                 _adjustObjectDirection(obj);
  933.                                 obj.state = STATE::FLY;
  934.                         }
  935.                        
  936.                         const int direction = obj.var[_objVar::DirectionCurrent];
  937.                         const jjANIMFRAME@ animFrame = jjAnimFrames[obj.curFrame];
  938.                         const float positionForwards = obj.xPos + direction * animFrame.width / 2;
  939.                        
  940.                         if (
  941.                                 (jjEventGet(int(positionForwards) / 32, int(obj.yPos) / 32) == AREA::STOPENEMY) ||
  942.                                 (jjMaskedVLine(int(positionForwards), int(obj.yPos + animFrame.hotSpotY), animFrame.height)) //wall
  943.                         )
  944.                                 _reverseDirection(obj);
  945.                         else
  946.                                 obj.xPos += direction * obj.xSpeed;
  947.                 }
  948.         }
  949.        
  950.        
  951.        
  952.         abstract class WallStickerHorizontal : Enemy {
  953.                 WallStickerHorizontal(jjOBJ@ preset, bool tc, string name) {
  954.                         super(preset, tc, name);
  955.                         _flipSpriteWhenMovingLeft = _supportsDirection = true;
  956.                 }
  957.                 void myBehave(jjOBJ@ obj) const override {
  958.                         if (obj.state == STATE::START) {
  959.                                 if (_adjustObjectDirection(obj) == 1) {
  960.                                         while (obj.xPos > 0 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  961.                                                 obj.xPos -= 1;
  962.                                 } else {
  963.                                         obj.direction = SPRITE::FLIPH;
  964.                                         while (obj.xPos < _levelWidth-2 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  965.                                                 obj.xPos += 1;
  966.                                 }
  967.                                 obj.state = STATE::WAIT;
  968.                         }
  969.                 }
  970.         }
  971.         abstract class WallStickerVertical : Enemy {
  972.                 WallStickerVertical(jjOBJ@ preset, bool tc, string name) {
  973.                         super(preset, tc, name);
  974.                         _supportsDirection = true;
  975.                 }
  976.                 void myBehave(jjOBJ@ obj) const override {
  977.                         if (obj.state == STATE::START) {
  978.                                 if (_adjustObjectDirection(obj) == 1) { //right = floor
  979.                                         while (obj.yPos < _levelHeight-2 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  980.                                                 obj.yPos += 1;
  981.                                 } else { //left = ceiling
  982.                                         obj.direction = SPRITE::FLIPV;
  983.                                         while (obj.yPos > 0 && !_maskedPixelFloat(obj.xPos, obj.yPos))
  984.                                                 obj.yPos -= 1;
  985.                                 }
  986.                                 obj.state = STATE::WAIT;
  987.                         }
  988.                 }
  989.         }
  990.        
  991.        
  992.        
  993.         final class Diamondus_TurtleGoon : Walker {
  994.                 Diamondus_TurtleGoon(jjOBJ@ preset, bool tc) {
  995.                         super(preset, tc, "Diamondus_TurtleGoon");
  996.                         _speed = 1.333333;
  997.                         _direction = Directions::Right;
  998.                         _killAnimExplosion = true;
  999.                         preset.killAnim = -1;
  1000.                         preset.points = 100;
  1001.                         preset.energy = 1;
  1002.                         preset.animSpeed = 4;
  1003.                 }
  1004.         }
  1005.        
  1006.         final class Diamondus_BumblingBee : PathFollower {
  1007.                 Diamondus_BumblingBee(jjOBJ@ preset, bool tc) {
  1008.                         super(preset, tc, "Diamondus_BumblingBee", 0);
  1009.                         _speed = 2;
  1010.                         _killAnimExplosion = true;
  1011.                         preset.killAnim = -1;
  1012.                         preset.points = 50;
  1013.                         preset.energy = 1;
  1014.                         preset.animSpeed = 3;
  1015.                 }
  1016.         }
  1017.        
  1018.        
  1019.         final class Tubelectric_BlasterHorizontal : WallStickerHorizontal {
  1020.                 Tubelectric_BlasterHorizontal(jjOBJ@ preset, bool tc) {
  1021.                         super(preset, tc, "Tubelectric_BlasterHorizontal");
  1022.                         _supportsFire = true;
  1023.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1024.                         @_fireFrames = array<int8> = {2,2,2,2,2};
  1025.                         _bullets.insertLast(_bulletPreferences(-3,0, 1,0, _bulletDirection::Left));
  1026.                         _bullets.insertLast(_bulletPreferences( 3,0, 1,0, _bulletDirection::Right));
  1027.                         _fireDelay = 75;
  1028.                         preset.playerHandling = HANDLING::SELFCOLLISION;
  1029.                         preset.animSpeed = 4;
  1030.                 }
  1031.         }
  1032.         final class Tubelectric_BlasterVertical : WallStickerVertical {
  1033.                 Tubelectric_BlasterVertical(jjOBJ@ preset, bool tc) {
  1034.                         super(preset, tc, "Tubelectric_BlasterVertical");
  1035.                         _supportsFire = true;
  1036.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1037.                         @_fireFrames = array<int8> = {2,2,2,2,2};
  1038.                         _bullets.insertLast(_bulletPreferences(0, 3, 1,0, _bulletDirection::Left));
  1039.                         _bullets.insertLast(_bulletPreferences(0,-3, 1,0, _bulletDirection::Right));
  1040.                         _fireDelay = 75;
  1041.                         preset.playerHandling = HANDLING::SELFCOLLISION;
  1042.                         preset.animSpeed = 4;
  1043.                 }
  1044.         }
  1045.        
  1046.         final class Tubelectric_Spark : StalkerGhost {
  1047.                 Tubelectric_Spark(jjOBJ@ preset, bool tc) {
  1048.                         super(preset, tc, "Tubelectric_Spark");
  1049.                         _speed = 1.33333333;
  1050.                         _direction = Directions::Left;
  1051.                         @_animFrames = array<int8> = {0, 0, 0, 1}; //spend thrice as much time not-flashing as flashing
  1052.                         _killAnimExplosion = true;
  1053.                         _killAnimRepetitionCounts = 6;
  1054.                         preset.killAnim = 1;
  1055.                         preset.points = 20;
  1056.                         preset.energy = 1;
  1057.                         preset.animSpeed = 0;
  1058.                 }
  1059.         }
  1060.        
  1061.         final class Tubelectric_SparkBarrier : Enemy {
  1062.                 Tubelectric_SparkBarrier(jjOBJ@ preset, bool tc) {
  1063.                         super(preset, tc, "Tubelectric_SparkBarrier");
  1064.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1065.                         _killAnimExplosion = true;
  1066.                         _killAnimRepetitionCounts = 7;
  1067.                         preset.killAnim = 1;
  1068.                         preset.points = 20;
  1069.                         preset.energy = 3;
  1070.                         preset.animSpeed = 2;
  1071.                 }
  1072.         }
  1073.        
  1074.        
  1075.         final class Medivo_GhostRapierHorizontal : Missile {
  1076.                 Medivo_GhostRapierHorizontal(jjOBJ@ preset, bool tc) {
  1077.                         super(preset, tc, "Medivo_GhostRapierHorizontal");
  1078.                         _speed = 2;
  1079.                         _direction = Directions::FaceJazz;
  1080.                         preset.points = 10;
  1081.                         preset.energy = 1;
  1082.                         preset.animSpeed = 1;
  1083.                 }
  1084.         }
  1085.        
  1086.         final class Medivo_GhostRapierVertical : PathFollower {
  1087.                 Medivo_GhostRapierVertical(jjOBJ@ preset, bool tc) {
  1088.                         super(preset, tc, "Medivo_GhostRapierVertical", 1);
  1089.                         _speed = 1.333333;
  1090.                         _flipSpriteWhenMovingLeft = false;
  1091.                         preset.points = 10;
  1092.                         preset.energy = 1;
  1093.                         preset.animSpeed = 1;
  1094.                 }
  1095.         }
  1096.        
  1097.         final class Medivo_Helmut : Walker {
  1098.                 Medivo_Helmut(jjOBJ@ preset, bool tc) {
  1099.                         super(preset, tc, "Medivo_Helmut");
  1100.                         _speed = 1.333333;
  1101.                         _cliffReaction = CliffReaction::Fall;
  1102.                         preset.points = 10;
  1103.                         preset.energy = 1;
  1104.                         preset.animSpeed = 3;
  1105.                         _direction = Directions::Left;
  1106.                 }
  1107.         }
  1108.        
  1109.        
  1110.         final class Letni_Bug : Walker {
  1111.                 Letni_Bug(jjOBJ@ preset, bool tc) {
  1112.                         super(preset, tc, "Letni_Bug");
  1113.                         _speed = 1.333333;
  1114.                         preset.points = 50;
  1115.                         preset.energy = 1;
  1116.                         preset.animSpeed = 3;
  1117.                         _direction = Directions::Left;
  1118.                 }
  1119.         }
  1120.         final class Letni_BugCeiling : Enemy {
  1121.                 Letni_BugCeiling(jjOBJ@ preset, bool tc) {
  1122.                         super(preset, tc, "Letni_BugCeiling");
  1123.                         _speed = 1.333333;
  1124.                         preset.points = 50;
  1125.                         preset.energy = 1;
  1126.                         preset.animSpeed = 3;
  1127.                         _flipSpriteWhenMovingLeft = _supportsSpeed = _supportsDirection = true;
  1128.                         _direction = Directions::Left;
  1129.                         _killAnimExplosion = true; //different from floor variation for some reason
  1130.                 }
  1131.                 void myBehave(jjOBJ@ obj) const override { //like Walker, but on ceiling and without any falling options
  1132.                         if (obj.state == STATE::START) {
  1133.                                 {
  1134.                                         _adjustObjectDirection(obj);
  1135.                                         obj.direction ^= 0xC0; //vertically flipped
  1136.                                         while (obj.yPos >= 5 && !_maskedPixelFloat(obj.xPos, obj.yPos - 5))
  1137.                                                 obj.yPos -= 1;
  1138.                                 }
  1139.                                 obj.state = STATE::WALK;
  1140.                         }
  1141.                        
  1142.                         const int direction = obj.var[_objVar::DirectionCurrent];
  1143.                         const float positionForwards = obj.xPos + direction * jjAnimFrames[obj.curFrame].width / 2;
  1144.                        
  1145.                         if (
  1146.                                 (jjEventGet(int(positionForwards) / 32, int(obj.yPos) / 32) == AREA::STOPENEMY) ||
  1147.                                 (!_maskedPixelFloat(positionForwards, obj.yPos - 5)) || //cliff
  1148.                                 (_maskedPixelFloat(positionForwards, obj.yPos - 4)) //wall
  1149.                         )
  1150.                                 _reverseDirection(obj);
  1151.                         else
  1152.                                 obj.xPos += direction * obj.xSpeed;
  1153.                 }
  1154.         }
  1155.        
  1156.         final class Letni_ElecBarrier : Enemy {
  1157.                 Letni_ElecBarrier(jjOBJ@ preset, bool tc) {
  1158.                         super(preset, tc, "Letni_ElecBarrier");
  1159.                         _killAnimExplosion = true;
  1160.                         preset.killAnim = -2;
  1161.                         preset.points = 50;
  1162.                         preset.energy = 3;
  1163.                         preset.animSpeed = 3;
  1164.                 }
  1165.         }
  1166.        
  1167.        
  1168.         final class Technoir_MiniMine : Floater {
  1169.                 Technoir_MiniMine(jjOBJ@ preset, bool tc) {
  1170.                         super(preset, tc, "Technoir_MiniMine", 1);
  1171.                         _speed = 0.8f;
  1172.                         _flipSpriteWhenMovingLeft = false;
  1173.                         _killAnimExplosion = true;
  1174.                         _killAnimRepetitionCounts = 10;
  1175.                         preset.killAnim = 1;
  1176.                         preset.points = 0;
  1177.                         preset.energy = 3;
  1178.                         preset.animSpeed = 1;
  1179.                 }
  1180.         }
  1181.        
  1182.         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
  1183.                 Technoir_Misfire(jjOBJ@ preset, bool tc) {
  1184.                         super(preset, tc, "Technoir_Misfire");
  1185.                         _speed = 1.333333;
  1186.                         _cliffReaction = CliffReaction::Fall;
  1187.                         preset.points = 50;
  1188.                         preset.energy = 1;
  1189.                         preset.animSpeed = 3;
  1190.                         _direction = Directions::Left;
  1191.                         _killAnimExplosion = true;
  1192.                 }
  1193.         }
  1194.        
  1195.         final class Technoir_TanketyTankTank : Walker {
  1196.                 Technoir_TanketyTankTank(jjOBJ@ preset, bool tc) {
  1197.                         super(preset, tc, "Technoir_TanketyTankTank");
  1198.                         _speed = 1;
  1199.                         preset.points = 50;
  1200.                         preset.energy = 2;
  1201.                         preset.animSpeed = 3;
  1202.                         _direction = Directions::Left;
  1203.                         _killAnimExplosion = true;
  1204.                         _supportsFire = true;
  1205.                         _fireAnimID = 1;
  1206.                         @_animFrames = array<int8> = {0, 1, 2, 3};
  1207.                         @_fireFrames = array<int8> = {4,4,4,4,4,4};
  1208.                         _bullets.insertLast(_bulletPreferences(-4,0, 1,1, _bulletDirection::Left));
  1209.                         _bullets.insertLast(_bulletPreferences( 4,0, 1,0, _bulletDirection::Right));
  1210.                         _fireDelay = 100;
  1211.                 }
  1212.         }
  1213.        
  1214.        
  1215.         final class Orbitus_BeholderPurple : Enemy {
  1216.                 Orbitus_BeholderPurple(jjOBJ@ preset, bool tc) {
  1217.                         super(preset, tc, "Orbitus_BeholderPurple");
  1218.                         preset.points = 30;
  1219.                         preset.energy = 4;
  1220.                         preset.animSpeed = 4;
  1221.                         @_animFrames = array<int8> = {0, 1, 2, 3, 2, 1};
  1222.                         _killAnimExplosion = true;
  1223.                         _killAnimRepetitionCounts = 9;
  1224.                         preset.killAnim = 1;
  1225.                 }
  1226.         }
  1227.        
  1228.         final class Orbitus_BeholderSilver : Walker {
  1229.                 Orbitus_BeholderSilver(jjOBJ@ preset, bool tc) {
  1230.                         super(preset, tc, "Orbitus_BeholderSilver");
  1231.                         _speed = 1;
  1232.                         preset.points = 30;
  1233.                         preset.energy = 1;
  1234.                         preset.animSpeed = 5;
  1235.                         @_animFrames = array<int8> = {0, 1, 2, 3, 2, 1};
  1236.                         _direction = Directions::Left;
  1237.                         _flipSpriteWhenMovingLeft = false;
  1238.                         _killAnimExplosion = true;
  1239.                         _killAnimRepetitionCounts = 9;
  1240.                         preset.killAnim = 1;
  1241.                 }
  1242.         }
  1243.        
  1244.         final class Orbitus_SilverSnake : Snake {
  1245.                 Orbitus_SilverSnake(jjOBJ@ preset, bool tc) {
  1246.                         super(preset, tc, "Orbitus_SilverSnake", 2);
  1247.                         _speed = 4;
  1248.                         _killAnimExplosion = true;
  1249.                         _killAnimRepetitionCounts = 19;
  1250.                         preset.killAnim = 1;
  1251.                         preset.points = 20;
  1252.                         preset.energy = 10;
  1253.                         preset.animSpeed = 6;
  1254.                 }
  1255.         }
  1256.        
  1257.        
  1258.         final class Nippius_SnowGoon : Enemy {
  1259.                 Nippius_SnowGoon(jjOBJ@ preset, bool tc) {
  1260.                         super(preset, tc, "Nippius_SnowGoon");
  1261.                         preset.points = 10;
  1262.                         preset.energy = 1;
  1263.                         preset.animSpeed = 4;
  1264.                         @_animFrames = array<int8> = {0, 1, 2, 1};
  1265.                         _direction = Directions::Left;
  1266.                         _supportsDirection = _flipSpriteWhenMovingLeft = true;
  1267.                         _killAnimExplosion = true;
  1268.                         _killAnimRepetitionCounts = 19;
  1269.                         preset.killAnim = 1;
  1270.                 }
  1271.                 void myBehave(jjOBJ@ obj) const override {
  1272.                         if (obj.state == STATE::START) {
  1273.                                 obj.putOnGround(true);
  1274.                                 obj.state = STATE::WAIT;
  1275.                         }
  1276.                         _adjustObjectDirection(obj);
  1277.                 }
  1278.         }
  1279.        
  1280.         final class Nippius_SkatePen : Walker {
  1281.                 Nippius_SkatePen(jjOBJ@ preset, bool tc) {
  1282.                         super(preset, tc, "Nippius_SkatePen");
  1283.                         _speed = 1.333333;
  1284.                         _cliffReaction = CliffReaction::Fall;
  1285.                         preset.points = 10;
  1286.                         preset.energy = 1;
  1287.                         preset.animSpeed = 4;
  1288.                         _direction = Directions::Left;
  1289.                 }
  1290.         }
  1291.        
  1292.         final class Nippius_SkiTurtle : Walker {
  1293.                 Nippius_SkiTurtle(jjOBJ@ preset, bool tc) {
  1294.                         super(preset, tc, "Nippius_SkiTurtle");
  1295.                         _speed = 4; //2 in level 2
  1296.                         preset.points = 10;
  1297.                         preset.energy = 1;
  1298.                         preset.animSpeed = 3;
  1299.                         _direction = Directions::Left;
  1300.                 }
  1301.         }
  1302.        
  1303.        
  1304.         final class Holidaius_BlueDog : Walker {
  1305.                 Holidaius_BlueDog(jjOBJ@ preset, bool tc) {
  1306.                         super(preset, tc, "Holidaius_BlueDog");
  1307.                         _speed = 1.333333;
  1308.                         preset.points = 30;
  1309.                         preset.energy = 3;
  1310.                         preset.animSpeed = 4;
  1311.                         _direction = Directions::Left;
  1312.                         _killAnimExplosion = true;
  1313.                         _supportsFire = true;
  1314.                         _fireAnimID = 1;
  1315.                         @_fireFrames = array<int8> = {0,0, 1,1,2,2, 1,1,2,2, 1,1,2,2, 1,1,2,2};
  1316.                         _fireDelay = 150;
  1317.                 }
  1318.         }
  1319.        
  1320.         final class Holidaius_Devil : PathFollower {
  1321.                 Holidaius_Devil(jjOBJ@ preset, bool tc) {
  1322.                         super(preset, tc, "Holidaius_Devil", 3);
  1323.                         _speed = 1.333333;
  1324.                         _killAnimExplosion = true;
  1325.                         preset.points = 50;
  1326.                         preset.energy = 3;
  1327.                         preset.animSpeed = 3;
  1328.                 }
  1329.         }
  1330.        
  1331.         final class Holidaius_HandHorizontal : WallStickerHorizontal {
  1332.                 Holidaius_HandHorizontal(jjOBJ@ preset, bool tc) {
  1333.                         super(preset, tc, "Holidaius_HandHorizontal");
  1334.                         @_animFrames = array<int8> = {0, 1, 2, 3, 4, 5, 6,6,6,6,6,6,6,6,6,6,6,6,6};
  1335.                         preset.animSpeed = 5;
  1336.                         preset.points = 60;
  1337.                         preset.energy = 1;
  1338.                         _killAnimExplosion = true;
  1339.                 }
  1340.         }
  1341.         final class Holidaius_HandVertical : WallStickerVertical {
  1342.                 Holidaius_HandVertical(jjOBJ@ preset, bool tc) {
  1343.                         super(preset, tc, "Holidaius_HandVertical");
  1344.                         @_animFrames = array<int8> = {0, 1, 2, 3, 4, 5, 6,6,6,6,6,6,6,6,6,6,6,6,6};
  1345.                         preset.animSpeed = 5;
  1346.                         preset.points = 60;
  1347.                         preset.energy = 1;
  1348.                         _killAnimExplosion = true;
  1349.                 }
  1350.         }
  1351.        
  1352.         final class Holidaius_SkiTurtle : Walker {
  1353.                 Holidaius_SkiTurtle(jjOBJ@ preset, bool tc) {
  1354.                         super(preset, tc, "Holidaius_SkiTurtle");
  1355.                         _speed = 2;
  1356.                         preset.points = 50;
  1357.                         preset.energy = 1;
  1358.                         preset.animSpeed = 3;
  1359.                         _direction = Directions::Left;
  1360.                         _killAnimExplosion = true;
  1361.                 }
  1362.         }
  1363.        
  1364.         final class Holidaius_SnowMonkey : Walker {
  1365.                 Holidaius_SnowMonkey(jjOBJ@ preset, bool tc) {
  1366.                         super(preset, tc, "Holidaius_SnowMonkey");
  1367.                         _speed = 4;
  1368.                         preset.points = 30;
  1369.                         preset.energy = 1;
  1370.                         preset.animSpeed = 4;
  1371.                         _direction = Directions::Left;
  1372.                         _killAnimExplosion = true;
  1373.                         _supportsFire = true;
  1374.                         @_fireFrames = array<int8> = {5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5};
  1375.                         _fireDelay = 50;
  1376.                 }
  1377.         }
  1378. }
  1379.  
  1380. #pragma require "Jazz1Enemies v03.asc"
  1381. #pragma require "Jazz1Enemies v03.j2a"
  1382. #include "TrueColor.asc"