Downloads containing HH24_Guardian.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Holiday Hare 24Featured Download PurpleJazz Single player 10 Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(array<MLLEWeaponApply@> = {null, null, null, null, null, null, null, null, WeaponVMega::GravityWell::Weapon()}); ///@MLLE-Generated
#include "MLLE-Include-1.7w.asc" ///@MLLE-Generated
#pragma require "HH24_Guardian-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "Cracco Castle 11.j2t" ///@MLLE-Generated
#pragma require "Legendary.j2t" ///@MLLE-Generated
#pragma require "xlmdamnice.j2t" ///@MLLE-Generated
#pragma require "coldcavern.j2t" ///@MLLE-Generated
#pragma require "Medivo.j2t" ///@MLLE-Generated
#pragma require "BioWinter.j2t" ///@MLLE-Generated
#pragma require "Aztec2.j2t" ///@MLLE-Generated
#pragma require "SSWorlds_SET7.j2t" ///@MLLE-Generated
#pragma require "HH24_Guardian.j2l" ///@MLLE-Generated
#include "WeaponVMega7.asc" ///@MLLE-Generated
#pragma require "WeaponVMega7.asc" ///@MLLE-Generated
#pragma require "SExmas.j2a"
#pragma require "HH18E1.j2a"
#pragma require "HH17_Roar.wav"
#include "hh17enemies_HH24.asc"
#include "SmokeBossUtilsV1.asc"
#pragma require "ShowgemsV.png"
#include "HH24.asc"

/*Custom enemies for Holiday Hare 18, by SmokeNC.
I didn't make any of the sprite animation myself and the credits go
to their rightul owners. They were mainly taken from the
Sprites Resource and Sprites-Inc*/

const uint8 ChromaKeyIndex = 32;
int LayerZ;

int elapsed = 0;
bool camera, halfmsg, showOnce = false;

uint labelFrameID;

void InitChroma()
{
  auto layers = jjLayerOrderGet();
  LayerZ = 4 - layers.findByRef(jjLayers[4]) + 1;
  jjLAYER storm(1, 1);
  storm.spriteMode = SPRITE::CHROMAKEY;
  storm.spriteParam = ChromaKeyIndex;
  storm.textureSurface = SURFACE::FULLSCREEN;
  storm.xSpeed = storm.ySpeed = 1.1;
  jjPIXELMAP stormImage(TEXTURE::DESOLATION);
  array<uint8> recolor(256);
  for (uint i = 0; i < 32; ++i)
    recolor[176 + i] = 32 + uint8(i / 5.2);
  stormImage.recolor(recolor).makeTexture(storm);
  storm.textureStyle = TEXTURE::WAVE;
  storm.tileWidth = storm.tileHeight = true;
  layers.insertAt(0, storm);
  jjLayerOrderSet(layers);

  // mess with these numbers and see what looks good
  storm.wave.amplitudeX = 0.125;
  storm.wave.wavelengthX = 127;
  storm.wave.amplitudeY = 0.25;
  storm.wave.wavelengthY = 255;
  storm.wave.waveSpeed = 3;
}

void onLevelLoad()
{
  InitChroma();
  jjAnimSets[ANIM::CUSTOM[17]].load(8, "HH18E1.j2a");
  jjAnimSets[ANIM::CUSTOM[27]].load(12, "HH18E1.j2a");
  // jjAnimSets[ANIM::CUSTOM[18]].load(3, "HH18E1.j2a");
  jjAnimSets[ANIM::CUSTOM[21]].load(6, "HH18E1.j2a");
  jjAnimSets[ANIM::DEVILDEVAN].load();
    jjAnimSets[ANIM::ROCK].load();
  jjAnimSets[ANIM::RAPIER].load();

  jjObjectPresets[OBJECT::CARROT].behavior = SMOKE::StillPickup();
    // jjObjectPresets[OBJECT::GOLDCOIN].behavior = SMOKE::NoDeactivateGenerator();
        jjObjectPresets[OBJECT::GOLDCOIN].deactivates = false;

        jjObjectPresets[OBJECT::GOLDCOIN].playerHandling = HANDLING::SPECIAL;
        jjObjectPresets[OBJECT::GOLDCOIN].scriptedCollisions = true;
        
          jjObjectPresets[OBJECT::SILVERCOIN].behavior = SMOKE::GeneratorGenerator();
        jjObjectPresets[OBJECT::SILVERCOIN].playerHandling = HANDLING::SPECIAL;
        jjObjectPresets[OBJECT::SILVERCOIN].scriptedCollisions = true;
        jjObjectPresets[OBJECT::SILVERCOIN].deactivates = false;

   jjObjectPresets[OBJECT::CARROT].deactivates = false;
  SMOKE::ICEDRAGON(OBJECT::DRAGONFLY, OBJECT::CRAB);
  
   HH17::setEnemy(OBJECT::LABRAT);
   HH17::setEnemy(OBJECT::BAT);
   HH17::setEnemy(OBJECT::DRAGON);
   
   	uint src = jjAnimSets[ANIM::CUSTOM[255]].load(0, "SExmas.j2a");
	uint dest = jjAnimSets[ANIM::PICKUPS];
	for (int i = 0; i < 95; i++) {
		const jjANIMATION@ anim = jjAnimations[src + i];
		if (anim.frameCount != 0)
			jjAnimations[dest + i] = anim;
	}
   
   if (jjAnimSets[ANIM::PLUS_BETA] == 0)
		jjAnimSets[ANIM::PLUS_BETA].load();
	
	uint setID;
	jjANIMFRAME@ frame;
	for (uint customAnimSetID = 0; customAnimSetID < 256; ++customAnimSetID)
		if (jjAnimSets[setID = ANIM::CUSTOM[customAnimSetID]] == 0) {
			@frame = jjAnimFrames[labelFrameID = jjAnimations[jjAnimSets[setID].load(jjPIXELMAP("ShowgemsV.png"), 85,42)]];
			break;
		}
	frame.hotSpotX = -22;
	frame.hotSpotY = -22;
	@frame = jjAnimFrames[labelFrameID + 1];
	frame.hotSpotX = -20;
	frame.hotSpotY = -20;
	
	HH24::levelLoad();
}

void onLevelReload() {
	MLLE::SpawnOffgridsLocal();
	
	HH17::processEnemyColors();
	jjMusicLoad("jm-deepd3.it");
	elapsed = 0;
	camera = false;
	halfmsg = false;
	
	HH24::levelReload();
}

namespace SMOKE
{
  const float PI = 3.14159265359;
  float max(float x, float y) { return x > y ? x : y; }
  int max(int x, int y) { return x > y ? x : y; }
  int min(int x, int y) { return x < y ? x : y; }

  int difficulty_dec()
  {
    return max(2 - jjDifficulty, 0);
  }

  int difficulty_inc()
  {
    return min(jjDifficulty, 2);
  }

  float abs(float x) { return x > 0 ? x : -x; }
  int headId;


class StillPickup : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {

		if (obj.xPos > 100*32)
			obj.direction = 1;
		if (obj.xPos < 100*32)
			obj.direction = -1;
		if (obj.state == STATE::FLOATFALL) obj.state = STATE::FLOAT;
		obj.behave(BEHAVIOR::PICKUP);
	}
}

class GeneratorGenerator :  jjBEHAVIORINTERFACE
{
  void onBehave(jjOBJ@ obj)
  {
    int idx = jjAddObject(OBJECT::GOLDCOIN, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT);

    jjObjects[idx].behavior = NoDeactivateGenerator();
    obj.delete();
  }
}

class NoDeactivateGenerator :  jjBEHAVIORINTERFACE
{
int spawnTime = 70 * 20;
int _t = 0;
int sonID = -1;
  void onBehave(jjOBJ@ obj)
  {

    if(sonID == -1)
    {
      sonID = jjAddObject(OBJECT::CARROT, obj.xPos, obj.yPos, obj.objectID, CREATOR::OBJECT);

    }
    else
    {
      if(jjObjects[sonID].isActive == false || jjObjects[sonID].eventID != OBJECT::CARROT)
      {
        _t++;
;        if(_t == spawnTime)
        {
                  sonID = -1;
                  _t = 0;
        }
      }
    }
  }
}

  void ICEDRAGON(OBJECT::Object eventID1, OBJECT::Object eventID2)
  {
    jjAnimSets[ANIM::CUSTOM[22]].load(14, "HH18E1.j2a");
    jjObjectPresets[eventID1].behavior = SMOKE::IceDrag();
    jjObjectPresets[eventID2].behavior = SMOKE::IceDragBody();
    jjObjectPresets[eventID2].determineCurAnim(ANIM::CUSTOM[22], 0);
    jjObjectPresets[eventID2].energy = 127;
  }

  float g_maxR = 32;
  int g_lastHeadIdx = 0;
  float g_lastHpIDx = g_hpBossMax;
  float g_hpBossMax = 300 + (difficulty_inc()*100);
  float g_currBossHp = g_hpBossMax;
  float g_hpBodyMax = 1;
  int g_numCloseThresh = 32;
  bool g_bossHalfHP = false;
  const int NUM_BODY_PARTS = 250;
  array<uint8> g_split_timestamps(int(g_hpBossMax*2/3), int(g_hpBossMax/3));

  enum snakeHeadAttaks
  {
    STATE_FOLLOW_ACC,
    STATE_FOLLOW,
    STATE_FOLLOW_ARC,
    STATE_FOLLOW_ACC_HALF_HP
  };

  class HandleFollowAcc : SyncTimer
  {
    bool shouldReset = false;
    HandleFollowAcc(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void Perform(jjOBJ @obj, float cSpeed)
    {
      if (shouldReset)
      {
        obj.xSpeed = 0;
        obj.ySpeed = 0;
        shouldReset = false;
      }

      if (_t < 70)
      {
        cSpeed = -0.04;
      }

      // GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
      //      jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, cSpeed, 128);

      float angle = GetAnglePlayer(obj);

      if (abs(obj.xSpeed + sin(angle) * cSpeed) < 5)
        obj.xSpeed += sin(angle) * cSpeed;
      if (abs(obj.ySpeed + cos(angle) * cSpeed) < 5)
        obj.ySpeed += cos(angle) * cSpeed;

      obj.xPos += obj.xSpeed;
      obj.yPos += obj.ySpeed;
    }

    void reset() override
    {
      SyncTimer::reset();
      shouldReset = true;
    }
  }

  
  class HandleFollowArc : SyncTimer
  {
    bool shouldReset = false;
    HandleFollowArc(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void Perform(jjOBJ @obj, float cSpeed)
    {
      if (shouldReset)
      {
        obj.xSpeed = 0;
        obj.ySpeed = 0;
        shouldReset = false;
      }

      if(GetDistPlayer(obj) > 600 )
      GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
      jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, 8, 0);

      
      GoArc(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
           jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, 10, 0);

      if(GetDistPlayer(obj) < 500 )
      GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
      jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, -8, 0);

    }

    void reset() override
    {
      SyncTimer::reset();
      shouldReset = true;
    }
  }

  class FireSingleShot : SyncTimer
  {

    ChangeHeadAnim openMouth(1 * 70, true, 0);
    ChangeHeadAnim closeMouth(2, false, 0.5 * 70);
    FireSingleShot(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void FireShot(jjOBJ @obj)
    {
    }

    void TelegraphShot(jjOBJ @obj)
    {

    }

    void Perform(jjOBJ @obj)
    {
      if (openMouth.isReady(true))
      {
        openMouth.Perform(obj, 1);
        TelegraphShot(obj);
      }
      else if (openMouth.JustFinished())
      {
        FireShot(obj);
        closeMouth.reset();
      }
      else if (closeMouth.isReady(true))
      {
        closeMouth.Perform(obj, 0);
      }
    }

    void reset() override
    {
      SyncTimer::reset();
      openMouth.reset();
    }

  }

  class FireStreamShots : SyncTimer
  {
    ChangeHeadAnim openMouthInitial(70, true, 0);

    ChangeHeadAnim openMouth(0, false, 0);
    ChangeHeadAnim closeMouth(2, false, 0);
    int _numShots;
    int _numShotsRemain;
    FireStreamShots(int dur, bool started = false, int off = 0, int numShots = 1, int diffBetweenShots = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
      _numShots = numShots;

      _numShotsRemain = numShots;
      openMouth._dur = (diffBetweenShots);
      _dur = (diffBetweenShots)*numShots + 70 + 70;
    }

    void FireShot(jjOBJ @obj)
    {
    }
    void TelegraphShot(jjOBJ @obj)
    {

    }
    void Perform(jjOBJ @obj)
    {
      if (openMouthInitial.isReady(true))
      {
        openMouthInitial.Perform(obj, 1);
        TelegraphShot(obj);
      }
      else if (openMouthInitial.JustFinished())
      {
        openMouth.reset();
      }
      else if (openMouth.isReady(true))
      {
        openMouth.Perform(obj, 1);
      }
      else if (openMouth.JustFinished())
      {
        FireShot(obj);
        _numShotsRemain--;

        if (_numShotsRemain > 0)
        {
          openMouth.reset();
        }
        if (_numShotsRemain == 0)
        {
          closeMouth.reset();
        }
      }
      else if (closeMouth.isReady(true))
      {
        closeMouth.Perform(obj, 0);
      }
    }

    void reset() override
    {
      SyncTimer::reset();
      openMouth.reset();
      openMouthInitial.reset();
      _numShotsRemain = _numShots;
    }

  }

  class Fire3Icicle : FireSingleShot
  {
    Fire3Icicle(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }
    
    void FireShot(jjOBJ @obj)
    {
      for (int i = -1; i < 2; i++)
      {
        float xSpeed = 4 * obj.direction;
        float ySpeed = i * 4;
        jjOBJ @bullet = FireBullet(obj.xPos + obj.direction * 16,
                                   obj.yPos, xSpeed > 0 ? 1 : -1, xSpeed, ySpeed, obj.objectID,
                                   ANIM::CUSTOM[17], 3, OBJECT::BLASTERBULLET, true, 0.03 * xSpeed, 0 * ySpeed, true);
        bullet. counterEnd = 57;
        bullet.behavior = DullBullet();
        if (g_bossHalfHP)
          bullet.behavior = LaserGun();

        bullet.playerHandling = HANDLING::SPECIAL;
        bullet.scriptedCollisions = true;
      }
    }

    void TelegraphShot(jjOBJ @obj)
    {
      
	  obj.var[10] = 1;
	  
	  if(jjGameTicks % 10 < 6)
      {
      for (int i = -1; i < 2; i++)
      {
        float xSpeed = 5 * obj.direction;
        float ySpeed = i * 5;
        float angle = atan2(xSpeed, ySpeed) - PI / 2;

      jjDrawRotatedSprite(obj.xPos +  8*xSpeed, obj.yPos + ySpeed * 8,  ANIM::CUSTOM[17], 3, 0, int((512 / PI) * angle), 1, 1 , SPRITE::TRANSLUCENT, 0, 2);
      }
      }
    }
  }

  class FireChaosAtt : FireSingleShot
  {
    FireChaosAtt(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void FireShot(jjOBJ @obj)
    {
    int bulletID3 = jjAddObject(OBJECT::FIREBALLBULLET, obj.xPos + obj.direction * 16, obj.yPos + 8, obj.objectID, CREATOR::OBJECT);
    jjOBJ @o = jjObjects[bulletID3];
    o.direction = obj.direction;

    float angle = GetAnglePlayer(obj);

    o.xSpeed = 0;
    o.ySpeed = 0;

    o.animSpeed = 1;
    o.determineCurAnim(ANIM::ROCK, 0);

    ChaosBall chaosBall = ChaosBall();
    chaosBall.halfHP = g_bossHalfHP;
    o.behavior = chaosBall;

    o.playerHandling = HANDLING::SPECIAL;
    o.scriptedCollisions = true;
          jjSamplePriority(SOUND::AMMO_LASER);
              }

    void TelegraphShot(jjOBJ @obj)
    {
		
	obj.var[10] = 2;
	
            if(jjGameTicks % 10 < 6)
      {
      jjDrawSprite(obj.xPos + obj.direction * 16, obj.yPos + 8 , ANIM::ROCK, 0, 0, obj.direction, SPRITE::TRANSLUCENTCOLOR, ChromaKeyIndex, 2);
      }

    }
  }

  void
  IceCloud(jjOBJ @obj)
  {
              obj.frameID = 0;
          obj.determineCurFrame();
    obj.behave(BEHAVIOR::BULLET, true);
    int playerID = obj.findNearestPlayer(30000);
    if (obj.state == STATE::FLY && obj.doesCollide(jjPlayers[playerID], true) && jjPlayers[playerID].blink == 0)
    {
      jjPlayers[playerID].freeze(true);
      float angle = GetAnglePlayer(obj);
      jjPlayers[playerID].xSpeed = -16 * sin(angle);
      jjPlayers[playerID].ySpeed = -16 * cos(angle);
    }
    if(obj.counter > 200)
    {
      obj.delete();
    }
  }

  float RandFloat(float min, float max)
  {
    float scale = jjRandom() / float(2147483647); /* [0, 1.0] */
    return min + scale * (max - min);             /* [min, max] */
  }

  class DullBullet : jjBEHAVIORINTERFACE
  {
    bool explodeOnContact = true;

    void onBehave(jjOBJ @obj)
    {
      if (obj.state == STATE::START)
      {
        obj.state = STATE::FLY;
        if (obj.creatorType == CREATOR::PLAYER)
          obj.xSpeed += obj.var[7] / 65536.0; // xSpeed of the player when firing the bullet
      }
      else if (obj.state == STATE::DEACTIVATE)
      {
        obj.delete();
      }
      else if (obj.state == STATE::EXPLODE)
      {
        obj.behavior = BEHAVIOR::EXPLOSION2;
        obj.frameID = 0; // display the full .killAnim animation
      }
      else
      {
        obj.xSpeed += obj.xAcc;
        obj.ySpeed += obj.yAcc;
        if ((--obj.counterEnd == 0) || (explodeOnContact && jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + obj.ySpeed))))
        {
          obj.state = STATE::EXPLODE;
        }
        else
        {
          obj.xPos += obj.xSpeed;
          obj.yPos += obj.ySpeed;
          obj.frameID = obj.objectID + jjGameTicks / 4;
          obj.determineCurFrame();
          Draw(obj);
          // jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL, 0, 1);
        }
      }
    }

    void Draw(jjOBJ @obj)
    {

      float angle = atan2(obj.xSpeed, obj.ySpeed) - PI / 2;

      jjDrawRotatedSpriteFromCurFrame(
          obj.xPos,
          obj.yPos, obj.curFrame,
          int((512 / PI) * angle));
    }

    bool onObjectHit(
        jjOBJ @obj, jjOBJ @bullet, jjPLAYER @player,
        int force)
    {
      if (bullet is null && player.blink == 0)
      {
        player.buttstomp = 122;
        player.hurt(1);
      }
      return true;
    }
  }

  class ChaosBall : jjBEHAVIORINTERFACE
  {

    int counter = 160; //200
    bool explodeOnContact = false;
    bool halfHP = false;

    void onBehave(jjOBJ @obj)
    {
      if (obj.state == STATE::START)
      {
        obj.state = STATE::FLY;
        if (obj.creatorType == CREATOR::PLAYER)
          obj.xSpeed += obj.var[7] / 65536.0; // xSpeed of the player when firing the bullet
      }
      else if (obj.state == STATE::DEACTIVATE)
      {
        obj.delete();
      }
      else if (obj.state == STATE::EXPLODE)
      {
        obj.behavior = BEHAVIOR::EXPLOSION2;
        obj.frameID = 0; // display the full .killAnim animation
      }
      else
      {
        // obj.xSpeed += obj.xAcc;
        // obj.ySpeed += obj.yAcc;
        if ((--counter == 0) || (explodeOnContact && jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + obj.ySpeed))))
        {
          obj.state = STATE::EXPLODE;
        }
        else
        {
          if (halfHP && jjGameTicks % 20 == 0)
          {
            int bulletID3 = jjAddObject(OBJECT::ELECTROBULLETPU, obj.xPos - obj.direction * 8, obj.yPos + 8, obj.objectID, CREATOR::OBJECT);
            jjOBJ @o = jjObjects[bulletID3];
            o.direction = obj.direction;

            float angle = GetAnglePlayer(obj);

            o.xAcc = 0;
            o.yAcc = 0;
            o.xSpeed = sin(angle) * 4;
            o.ySpeed = cos(angle) * 4;

            o.animSpeed = 1;

            o.counterEnd = 250;

            o.playerHandling = HANDLING::ENEMYBULLET;
          }
          float cSpeed = 0.05;
          //     GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
          //  jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, cSpeed, 128);

          float angle = GetAnglePlayer(obj);

          if (abs(obj.xSpeed + sin(angle) * cSpeed) < 8)
            obj.xSpeed += sin(angle) * cSpeed;
          if (abs(obj.ySpeed + cos(angle) * cSpeed) < 8)
            obj.ySpeed += cos(angle) * cSpeed;

          obj.xPos += obj.xSpeed;
          obj.yPos += obj.ySpeed;

          obj.frameID = obj.objectID + jjGameTicks / 4;
          obj.determineCurFrame();
          Draw(obj);
          // jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL, 0, 1);
        }
      }
    }
    void Draw(jjOBJ @obj)
    {

      //         float angle = atan2(obj.xSpeed, obj.ySpeed) - PI/2;

      // jjDrawRotatedSpriteFromCurFrame(
      //     obj.xPos,
      //     obj.yPos, obj.curFrame,
      //     int((512 / PI) * angle));
      jjDrawSpriteFromCurFrame(obj.xPos,
                               obj.yPos, obj.curFrame, 0, SPRITE::SINGLECOLOR, ChromaKeyIndex);
    }

    bool onObjectHit(
        jjOBJ @obj, jjOBJ @bullet, jjPLAYER @player,
        int force)
    {
      if (bullet is null)
      {
        player.buttstomp = 122;
        player.hurt(1);
      }
      return true;
    }
  }


  class IceCloud2 : jjBEHAVIORINTERFACE
  {

    int counter = 160; //200
    bool explodeOnContact = false;
    bool halfHP = false;

    void onBehave(jjOBJ @obj)
    {
      if (obj.state == STATE::START)
      {
        obj.state = STATE::FLY;
        if (obj.creatorType == CREATOR::PLAYER)
          obj.xSpeed += obj.var[7] / 65536.0; // xSpeed of the player when firing the bullet
      }
      else if (obj.state == STATE::DEACTIVATE)
      {
        obj.delete();
      }
      else if (obj.state == STATE::EXPLODE)
      {
        obj.behavior = BEHAVIOR::EXPLOSION2;
        obj.frameID = 0; // display the full .killAnim animation
      }
      else
      {
        // obj.xSpeed += obj.xAcc;
        // obj.ySpeed += obj.yAcc;
        if ((--counter == 0) || (explodeOnContact && jjMaskedPixel(int(obj.xPos + obj.xSpeed), int(obj.yPos + obj.ySpeed))))
        {
          obj.state = STATE::EXPLODE;
        }
        else
        {

          // float cSpeed = 0.05;
          // //     GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
          // //  jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, cSpeed, 128);

          // float angle = GetAnglePlayer(obj);

          // if (abs(obj.xSpeed + sin(angle) * cSpeed) < 8)
          //   obj.xSpeed += sin(angle) * cSpeed;
          // if (abs(obj.ySpeed + cos(angle) * cSpeed) < 8)
          //   obj.ySpeed += cos(angle) * cSpeed;

          obj.xPos += obj.xSpeed;
          obj.yPos += obj.ySpeed;

          obj.frameID = obj.objectID + jjGameTicks / 4;
          obj.determineCurFrame();
          // obj.frameID = 0;
          // obj.determineCurFrame();
          Draw(obj);
          // jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL, 0, 1);
        }
      }
    }
    void Draw(jjOBJ @obj)
    {

              float angle = atan2(obj.xSpeed, obj.ySpeed) - PI/2;

      jjDrawRotatedSpriteFromCurFrame(
          obj.xPos,
          obj.yPos, obj.curFrame,
          int((512 / PI) * angle), 1, 1, SPRITE::FROZEN);
      // jjDrawSpriteFromCurFrame(obj.xPos,
      //                          obj.yPos, obj.curFrame, 0, SPRITE::FROZEN);
    }

    bool onObjectHit(
        jjOBJ @obj, jjOBJ @bullet, jjPLAYER @player,
        int force)
    {
      if (bullet is null && player.blink == 0)
      {
        player.freeze(true);
        float angle = GetAnglePlayer(obj);
        player.xSpeed = -16 * sin(angle);
        player.ySpeed = -16 * cos(angle);
      }
      return true;
    }
  }



  class FireSnowBallStr : FireStreamShots
  {
    FireSnowBallStr(int dur, bool started = false, int off = 0, int numShots = 1, int diffBetweenShots = 0, int outReady = -1)
    {
      super(dur, started, off, numShots, diffBetweenShots, outReady);
    }

    void FireShot(jjOBJ @obj)
    {
    int bulletID3 = jjAddObject(OBJECT::BLASTERBULLET, obj.xPos + obj.direction * 16, obj.yPos + 8, obj.objectID, CREATOR::OBJECT);
    jjOBJ @o = jjObjects[bulletID3];
    o.direction = obj.direction;


    float angle = GetAnglePlayer(obj);

    o.xSpeed = sin(angle) * 5.5;
    o.ySpeed = cos(angle) * 5.5;
    o.animSpeed = 1;
    o.determineCurAnim(ANIM::DEVILDEVAN, 17);

    o.counterEnd = 80;
    DullBullet dull = DullBullet();
    dull.explodeOnContact = !g_bossHalfHP;
    o.behavior = dull;

    o.playerHandling = HANDLING::ENEMYBULLET;

    o.playerHandling = HANDLING::SPECIAL;
    o.scriptedCollisions = true;
          jjSamplePriority(SOUND::AMMO_LASER);

    }

        void TelegraphShot(jjOBJ @obj)
    {
		obj.var[10] = 3;
            if(jjGameTicks % 10 < 6)
      {
        
    float angle = GetAnglePlayer(obj);
      float xSpeed = sin(angle) * 8;
       float ySpeed = cos(angle) * 8;
         angle = atan2(xSpeed, ySpeed) - PI / 2;
        jjDrawRotatedSprite(obj.xPos + obj.direction * 64,  obj.yPos + 8,  ANIM::DEVILDEVAN, 17, 0, int((512 / PI) * angle), 1, 1 , SPRITE::TRANSLUCENT, 0, 2);

      }


    }
  }

  
  class FireIceCloudStr : FireStreamShots
  {
    float lastPlayerAngle;
    FireIceCloudStr(int dur, bool started = false, int off = 0, int numShots = 1, int diffBetweenShots = 0, int outReady = -1)
    {
      super(dur, started, off, numShots, diffBetweenShots, outReady);
    }

    void FireShot(jjOBJ @obj)
    {
    int bulletID3 = jjAddObject(OBJECT::FIREBALLBULLET, obj.xPos + obj.direction * 16, obj.yPos + 8, obj.objectID, CREATOR::OBJECT);
    jjOBJ @o = jjObjects[bulletID3];
    // o.direction = obj.direction;
    if(_numShotsRemain == _numShots)
    {
      lastPlayerAngle = GetAnglePlayer(obj);
                jjSamplePriority(SOUND::AMMO_ICEPU2);
    }
    float angle = lastPlayerAngle;
        o.behavior = IceCloud2();
    o.xAcc = 0;
    o.yAcc = 0;
    o.xSpeed = sin(angle) * 4;
    o.ySpeed = cos(angle) * 4;
    o.animSpeed = 1;
    o.determineCurAnim(ANIM::RAPIER, 0);

    o.counterEnd = 240;

    o.playerHandling = HANDLING::ENEMYBULLET;

    o.playerHandling = HANDLING::SPECIAL;
    o.scriptedCollisions = true;
          // jjSamplePriority(SOUND::AMMO_LASER);

    }

        void TelegraphShot(jjOBJ @obj)
    {
		obj.var[10] = 4;
    //         if(jjGameTicks % 10 < 6)
    //   {
        
    // float angle = GetAnglePlayer(obj);
    //   float xSpeed = sin(angle) * 8;
    //    float ySpeed = cos(angle) * 8;
    //      angle = atan2(xSpeed, ySpeed) - PI / 2;
    //     jjDrawRotatedSprite(obj.xPos + obj.direction * 64,  obj.yPos + 8,  ANIM::FLARE, 0, 0, int((512 / PI) * angle), 1, 1 , SPRITE::PALSHIFT, 16, 2);
    //   }
                if(jjGameTicks % 10 < 6)
      {
        
    float angle = GetAnglePlayer(obj);
      float xSpeed = sin(angle) * 8;
       float ySpeed = cos(angle) * 8;
         angle = atan2(xSpeed, ySpeed) - PI / 2;
        jjDrawRotatedSprite(obj.xPos + obj.direction * 64,  obj.yPos + 8,  ANIM::RAPIER, 0, 0, int((512 / PI) * angle), 1, 1 , SPRITE::FROZEN, 0, 2);

      }


    }
  }

  class HandleFollow : SyncTimer
  {
    int state = 0;
    Fire3Icicle fire3Icicle(1 * 70 + 0.5 * 70 + 70, true, 1 * 70);
    FireSnowBallStr fireSnowball(0, true, 100, 10, 8);
    FireIceCloudStr fireIceCloud(0, true, difficulty_dec() * 70, 1, 4);
    FireChaosAtt fireChaos(1 * 70 + 0.5 * 70 + 70, true, 1 * 70);
    bool shouldFireIceCloud = false;

    HandleFollow(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void Perform(jjOBJ @obj, float t)
    {
      if(shouldFireIceCloud)
      {
        float offset = obj.var[IS_MASTER_HEAD] == 1 ? -200 : 200;
      // if(GetDistPlayer(obj) > 300 )
      GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
      jjLocalPlayers[0].xPos+offset, jjLocalPlayers[0].yPos, 1, 0);
              obj.yPos += 1 * sin(t+offset);
              obj.xPos += 2 * sin(t+offset);

      
      // GoArc(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
      //      jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, 1, 0);

      // if(GetDistPlayer(obj) < 200 )
      // GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
      // jjLocalPlayers[0].xPos+offset, jjLocalPlayers[0].yPos, -1, 0);
      }

      else
      {
        GoTo(obj.xPos, obj.yPos, obj.xPos, obj.yPos, obj.xPos, obj.yPos,
            jjLocalPlayers[0].xPos, jjLocalPlayers[0].yPos, 1, 128);

        obj.yPos += 1 * sin(t);
      }


      // obj.xPos += 1 * cos(t);
      bool shouldFireSnowball = state == 0;
      bool shouldFireIcicle = state == 1;
      bool shouldFireChaos = state == 2;

      if (fireIceCloud.isReady(shouldFireIceCloud))
      {
        fireIceCloud.Perform(obj);
      }
      else if (fireIceCloud.JustFinished())
      {
        fireIceCloud.reset();
      }
      if(shouldFireIceCloud)
      {
        return;
      }


      if (fireSnowball.isReady(shouldFireSnowball))
      {
        fireSnowball.Perform(obj);
      }
      else if (fireSnowball.JustFinished())
      {
        fireSnowball.reset();
      }

      if (fire3Icicle.isReady(shouldFireIcicle))
      {
        fire3Icicle.Perform(obj);
      }
      else if (fire3Icicle.JustFinished())
      {
        fire3Icicle.reset();
      }

      if (fireChaos.isReady(shouldFireChaos))
      {
        fireChaos.Perform(obj);
      }
      else if (fireChaos.JustFinished())
      {
        fireChaos.reset();
      }
    }
    void reset() override
    {
      SyncTimer::reset();
      fire3Icicle.reset();
      fireSnowball.reset();
      fireIceCloud.reset();
      fireChaos.reset();
    }

    void SetState(int stateIn)
    {
      state = stateIn;
    }
  }

  class ChangeHeadAnim : SyncTimer
  {
    ChangeHeadAnim(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void Perform(jjOBJ @obj, int frameIdx)
    {
      obj.frameID = frameIdx;
      obj.determineCurFrame();
    }
  }

  class GoNext : SyncTimer
  {
    GoNext(int dur, bool started = false, int off = 0, int outReady = -1)
    {
      super(dur, started, off, outReady);
    }

    void Perform()
    {
      HH24::endLevel();
    }
  }
  

  float
  GetAnglePlayer(jjOBJ @obj)
  {
    return atan2((jjLocalPlayers[0].xPos - obj.xPos),
                 (jjLocalPlayers[0].yPos - obj.yPos));
  }

  class LaserGun : jjBEHAVIORINTERFACE
  {

    void onBehave(jjOBJ @obj)
    {
      if (obj.state == STATE::START)
      {
        obj.state = STATE::FLY;
      }
      if (jjMaskedPixel(int(obj.xPos + obj.xSpeed + obj.var[7] / 65536.f), int(obj.yPos)))
      {
        obj.xSpeed = -obj.xSpeed;
        obj.var[7] = -obj.var[7];
        obj.xAcc = -obj.xAcc;
        obj.ySpeed -= 1.5;
        obj.counter -= 5;
        // if (obj.state == STATE::FLY) randomSample(obj);
      }
      else if (jjMaskedPixel(int(obj.xPos), int(obj.yPos + obj.ySpeed)))
      {
        obj.ySpeed = -obj.ySpeed;
        obj.yAcc = -obj.yAcc;
        obj.xSpeed -= obj.direction == 1 ? -1.5 : 1.5;
        obj.counter -= 5;
        // if (obj.state == STATE::FLY) randomSample(obj);
      }
      obj.var[0] = int(atan2(-obj.ySpeed, obj.xSpeed) * (512.f * 0.318309886142228f));
      if (obj.state == STATE::FLY) // jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[0], 1, 0, obj.var[0], 0.9, 0.9, SPRITE::NORMAL);
        jjDrawRotatedSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.var[0]);
      obj.behave(BEHAVIOR::BULLET, obj.state == STATE::FLY ? false : true);
    }

    bool onObjectHit(
        jjOBJ @obj, jjOBJ @bullet, jjPLAYER @player,
        int force)
    {
      if (bullet is null)
      {
        player.buttstomp = 122;

        player.hurt(1);
      }
      return true;
    }

  }

  // launch laser
  jjOBJ @FireBullet(float x, float y, int direction, float xSpd, float ySpd, int objID, int set = -1, int anim = 0, int object = OBJECT::BLASTERBULLET, bool playsnd = true, float xAcc = 0, float yAcc = 0, bool longrange = false, int damage = 1)
  {
    jjOBJ @bullet = jjObjects[jjAddObject(object, x, y, objID)];
    // bullet.behavior = BEHAVIOR::BULLET;
    if (set != -1)
    {
      bullet.determineCurAnim(set, anim);
    }
    bullet.playerHandling = HANDLING::ENEMYBULLET;
    bullet.xSpeed = xSpd;
    bullet.ySpeed = ySpd;
    bullet.xAcc = xAcc;
    bullet.yAcc = yAcc;
    bullet.direction = direction;
    bullet.animSpeed = damage;
    if (playsnd == true)
    {
          jjSamplePriority(SOUND::AMMO_LASER);
    }
    if (longrange == true)
    {
      bullet.counterEnd = 120;
    }
    return bullet;
  }
  snakeHeadAttaks s_state = STATE_FOLLOW_ACC;
  float GetDistPlayer(jjOBJ @obj)
  {
    return sqrt((jjLocalPlayers[0].xPos - obj.xPos) * (jjLocalPlayers[0].xPos - obj.xPos) +
                (jjLocalPlayers[0].yPos - obj.yPos) * (jjLocalPlayers[0].yPos - obj.yPos));
  }
  class IceDragBody : jjBEHAVIORINTERFACE
  {
    int bodyIdx = -1;
    int test = 0;
    HandleFollowAcc handleFollowAcc(70 * 20 / 4, true, 0, STATE_FOLLOW);
    HandleFollowArc handleFollowArc(70 * 20 / 4, true, 0, STATE_FOLLOW);

    HandleFollow handleFollow(/*70 * 13 + 70 * 5 +*/ 70 * 10, false, 0, STATE_FOLLOW_ACC);
    GoNext goNext(1, true, 140*2);
    int respawnTime = 70 * 12;
    RandomArray randAttacks(3);
    int invincCntMax = 70;
    int invincCnt = 0;
	int armor = 0;
    bool shield = false;
    bool displayHalfHpWarn = true;
    bool DoSuperAttack()
    {
       return g_currBossHp < g_hpBossMax / 2;
    }
    // Calculate curr position of body part according to parent body part
    void CalcNextPos(jjOBJ @cObj, jjOBJ @pObj)
    {

      float dx = cObj.xPos - pObj.xPos;
      float dy = cObj.yPos - pObj.yPos;

      float dist = sqrt(dx * dx + dy * dy);
      if (dist < g_maxR)
      {
        return;
      }

      float ratio = g_maxR / dist;
      cObj.xPos = pObj.xPos + ratio * dx;
      cObj.yPos = pObj.yPos + ratio * dy;
    }

    float GetAngle(jjOBJ @cObj, jjOBJ @pObj)
    {
      float dx, dy;
      if (IsHead(cObj))
      {
        dx = cObj.xSpeed;
        dy = cObj.ySpeed;
      }
      else
      {
        dx = -cObj.xPos + pObj.xPos;
        dy = -cObj.yPos + pObj.yPos;
      }
      return atan2(dx, dy);
    }

    void BehaveHead(jjOBJ @obj)
    {
      invincCnt = max(invincCnt - 1, 0);
      // shield = invincCnt >= invincCntMax;
	  
	  if (obj.justHit != 0) obj.special = 0;
	  else obj.special++;
	  
	  if (obj.special > 0) {
		if (obj.special < 35) {
			if (obj.special % 5 == 0) {
				armor--;
			}
		}
		if (obj.special >= 70) {
			if (obj.special % 3 == 0) {
				armor--;
			}
		}
	  }
	  
	  if (armor > 30) armor = 30;
	  if (armor < 0) armor = 0;
	  
	  obj.isTarget = true;
      if(obj.var[IS_MASTER_HEAD] == 1)
      {
                    obj.energy = int(127 * g_currBossHp / g_hpBossMax);
      ActivateBoss(obj);

      }
      obj.age++;
	  
      obj.direction = jjLocalPlayers[0].xPos - obj.xPos > 0 ? 1 : -1;
      float cSpeed = 0.1; // originally 0.1
      float t = float(obj.age) / 70;

      if(IsHead(obj) && obj.var[BODYIDX] == int(NUM_BODY_PARTS * 1/3))
      {
        handleFollowArc.Perform(obj,0);
        return;
      }
      if (handleFollow.isReady(true))
      {

        s_state = STATE_FOLLOW;
        handleFollow.Perform(obj, t);
      }
      else if (handleFollow.JustFinished())
      {
        // g_bossHalfHP = obj.var[HP] < g_hpBossMax / 2;
                			            jjSamplePriority(SOUND::DOG_AGRESSIV);
        handleFollowAcc.reset();
		obj.var[10] = 0;
      }

      if (handleFollowAcc.isReady(true))
      {
        		obj.var[10] = 5;
        s_state = STATE_FOLLOW_ACC;
        handleFollowAcc.Perform(obj, cSpeed);
      }
      else if (handleFollowAcc.JustFinished())
      {
        // g_bossHalfHP = obj.var[HP] <= g_hpBossMax / 2;
        if(g_currBossHp < g_hpBossMax / 3)
        {
          // for(int i = 0; i < 5; i++)
          // jjAlert("The Ice Serpent's attacks gain special effects!");
          // displayHalfHpWarn = false;
          handleFollow.shouldFireIceCloud = true;
          handleFollow._dur = 7000000;
        }
        jjSamplePriority(SOUND::DOG_AGRESSIV);
        handleFollow.reset();
        handleFollow.SetState(randAttacks.GetNumber());
      }
    }

    void CollectNumClosePointsData(jjOBJ @obj)
    {
      // if(GetDistPlayer(obj) > )
    }
    void BehaveBody(jjOBJ @obj)
    {
      jjOBJ @pObj = GetParentNode(obj);

      if (CheckIfParentAlive(obj, pObj) == false)
      {
        return;
      }
      CalcNextPos(obj, pObj);
      if (IsUnActive(obj))
      {
        HandleUnActiveState(obj);
      }
      if (false)
      {
        if (jjRandom() % int(70 * 140 * 0.1 * 8) == 0)
        {
          float angle = GetAngle(obj, pObj);
          float xSpeed = cos(angle);
          float ySpeed = -sin(angle);

        jjOBJ @bullet = FireBullet(obj.xPos,
                                   obj.yPos, xSpeed > 0 ? 1 : -1, xSpeed, ySpeed, obj.objectID,
                                   ANIM::CUSTOM[17], 3, OBJECT::BLASTERBULLET, true, 0.03 * xSpeed, 0 * ySpeed, true);
        bullet. counterEnd = 57;
        bullet.behavior = DullBullet();

        bullet.playerHandling = HANDLING::SPECIAL;
        bullet.scriptedCollisions = true;
        }
      }
    }

    void ActivateBoss(jjOBJ @obj)
    {
      // if(jjGameTicks % 70 ==1)
      // jjAlert("id" + obj.objectID);
	  if (!camera && elapsed == 0) {
	    jjLocalPlayers[0].boss = obj.objectID;
		jjLocalPlayers[0].bossActivated = true;
		jjLocalPlayers[0].activateBoss();
		jjMusicLoad("Whare_AllanZax.ogg");
		jjLocalPlayers[0].cameraFreeze(obj.xPos, obj.yPos, true, false);
		//jjAlert("||THE PRIMORDIAL ICE SERPENT", false, STRING::MEDIUM);
		jjSamplePriority(SOUND::DOG_AGRESSIV);
		camera = true;
	  }
    }

    jjOBJ @GetParentNode(jjOBJ @obj)
    {
      return jjObjects[obj.var[PARENT]];
    }
    bool CheckIfParentAlive(jjOBJ @obj, jjOBJ @pObj)
    {
      if (pObj.state == STATE::BOUNCE)
      {
        obj.state = STATE::BOUNCE;
        obj.ySpeed = 3;
        return false;
      }
      return true;
    }
    bool IsHead(jjOBJ @obj)
    {
      return -1 == obj.var[PARENT];
    } // obj.var[PARENT] points to parent node, head has no parent

    bool IsTail(jjOBJ @obj)
    {
      return 1 == obj.var[IS_TAIL];
    } // condition used to specify tail

    bool IsUnActive(jjOBJ @obj)
    {
      return obj.var[IS_INACTIVE] == 1;
    }

    void HandleUnActiveState(jjOBJ @obj)
    {
      obj.age++;
      if (obj.age == respawnTime)
      {
        obj.age = 0;
        obj.var[IS_INACTIVE] = 0;
      }
    }
    void onBehave(jjOBJ @obj)
    {
      switch (obj.state)
      {
      case STATE::START:
        obj.determineCurFrame();
        obj.state = STATE::FLOAT;
        break;
      case STATE::FLOAT:
      // test++;
      // Split and turn to head
      if(g_lastHeadIdx == obj.var[BODYIDX])
      {
        if(obj.var[PARENT] != -1)
        {
          jjOBJ @pObj = GetParentNode(obj);
          pObj.var[IS_TAIL] = 1;
          obj.var[IS_INACTIVE] = 0;
		  jjSamplePriority(SOUND::FAN_FAN);
		  jjSamplePriority(SOUND::FAN_FAN); //twice the volume
          /*if (!halfmsg) jjAlert("||THE ICE SERPENT HAS SPLIT IN HALF!",false,STRING::MEDIUM);
		  halfmsg = true;*/
        }
          obj.var[PARENT] = -1;
      }

            if (IsHead(obj))
      {
        obj.determineCurAnim(ANIM::CUSTOM[27], 5);
		obj.var[9] = 1;
      }
      else if (IsTail(obj))
      {
        obj.determineCurAnim(ANIM::CUSTOM[27], 6);
      }
      else
      {
        obj.determineCurAnim(ANIM::CUSTOM[17], 2);
      }




        obj.determineCurFrame();
        if (IsHead(obj)) //  dragon head
        {
          BehaveHead(obj);
        }
        else
        {
          BehaveBody(obj);
        }

              if (g_currBossHp <= 0)
      { // killed
        if (IsHead(obj))
        {
          obj.state = STATE::BOUNCE;
          obj.ySpeed = 3;
		  jjMusicStop();
		  if (!showOnce) {
			jjLocalPlayers[0].showText("@@@@@@@@#||||~ICE SERPENT SLAIN", STRING::LARGE);
			showOnce = true;
		  }
        }

        // obj.particlePixelExplosion(2);
      }

        break;
      case STATE::FREEZE:
        obj.unfreeze(0);
        obj.state = obj.oldState;
        // consider calling jjOBJ::unfreeze() here
        break;

      case STATE::DEACTIVATE: // can be left out if level is MP-only

        obj.deactivate();
        break;
      case STATE::BOUNCE: // killed
        obj.yPos += obj.ySpeed;
        if (IsHead(obj) && jjGameTicks % 10 == 0)
        {
          jjLocalPlayers[0].bossActivated = false;
          obj.unfreeze(0);
        }
      if (goNext.isReady(obj.var[IS_MASTER_HEAD] == 1))
      {
        goNext.Perform();
      }

        break;
      case STATE::KILL: // can be left out if not using normal object energy
                        // handling
        obj.state = STATE::BOUNCE;

        // obj.delete();
        break;
      }
    }
    void onDraw(jjOBJ @obj)
    {
      // if (obj.state == STATE::BOUNCE)
      //   return;
      int layer = IsHead(obj) ? 1 : 2;
      auto sMode = obj.var[IS_INACTIVE] == 1                        ? SPRITE::SHADOW
                   : (obj.justHit != 0 || shield == true) ? SPRITE::SINGLECOLOR
                                                          : SPRITE::NORMAL;
      int param = shield == true ? 24 : obj.var[IS_INACTIVE] == 1? 50 : (jjLocalPlayers[0].blink == 0? 15:32);

      if (IsTail(obj))
      {
        obj.frameID = 3;
        obj.determineCurFrame();
        jjOBJ @pObj = jjObjects[obj.var[PARENT]];
        float angle = GetAngle(obj, pObj);

        jjDrawRotatedSpriteFromCurFrame(
            obj.xPos + (IsTail(obj) ? 26 * sin(angle) : 0),
            obj.yPos + (IsTail(obj) ? 26 * cos(angle) : 0), obj.curFrame,
            int((512 / PI) * angle), 1, 1, sMode, 15);
        return;
      }
      jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction,
                               sMode, param, layer);
    }

    bool onObjectHit(
        jjOBJ @obj, jjOBJ @bullet, jjPLAYER @player,
        int force)
    {
      if (obj.var[IS_INACTIVE] == 1 || obj.state == STATE::BOUNCE /* || bullet.creatorID == obj.objectID*/)
      {
        return true;
      }
      if (bullet !is null)
      {
          if ((bullet.var[6] & 16) == 0) // not a fireball
            bullet.state = STATE::EXPLODE;
          if (obj.freeze > 0 && force < 3)
            force = 3;
          if (true || invincCnt <= invincCntMax)
          {
            invincCnt += 30;

             obj.var[HP] = int(obj.var[HP] - damageCalc(obj, bullet, armor, player));
            // obj.energy = 127 * obj.var[HP] / g_hpBossMax;
			
			float multiplier = ((bullet.var[3] == 4 || bullet.var[3] == 5)? 0.33f : bullet.var[3] == 9? 0.075f : 0.5f) * (player.blink == 0? 1:0.25);
			
            if(IsHead(obj)) {
				g_currBossHp -= (damageCalc(obj, bullet, armor, player) > (bullet.animSpeed * multiplier))? damageCalc(obj, bullet, armor, player) : (bullet.animSpeed * multiplier);
				armor = armor + bullet.animSpeed;
				
				/*jjPARTICLE@ text = jjAddParticle(PARTICLE::STRING);
				if (text !is null) {
					text.xPos = obj.xPos;
					text.yPos = obj.yPos;
					text.ySpeed = -0.25;
					text.string.text = "" + (damageCalc(obj,bullet,armor, player) > (bullet.animSpeed * multiplier)? damageCalc(obj,bullet,armor, player) : (bullet.animSpeed * multiplier));
				}*/
			}

            // if(g_currBossHp <= g_lastHpIDx / 2 && (g_lastHpIDx / 2 > (g_hpBossMax / 2 - 10)))
            // {
            //   g_lastHeadIdx = g_lastHeadIdx / 2;
            //   g_lastHpIDx = g_lastHpIDx /  2;
            // }

            if(g_currBossHp <= g_lastHpIDx *1/3)
            {
              g_lastHeadIdx = int( NUM_BODY_PARTS* 1/3);
            }
            else if(g_currBossHp <= g_lastHpIDx *2/3)
            {
              g_lastHeadIdx = int(NUM_BODY_PARTS * 2/3);
            }
            

            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.
          }

          obj.freeze = 0;
      }
      else
      { // not attacking
        player.hurt();
      }


            if (obj.var[HP] <= 0)
      { // killed
        // if (IsHead(obj))
        // {
        //   obj.state = STATE::BOUNCE;
        //   obj.ySpeed = 3;
        // }
        // else
        if (!IsHead(obj))
        {
          obj.var[HP] = int(g_hpBodyMax);
          obj.var[IS_INACTIVE] = 1;
        }

        // obj.particlePixelExplosion(2);
      }

      return true;
    }

  }
  
  float damageCalc(jjOBJ@ obj, jjOBJ@ bullet, int armor, jjPLAYER@ play) {
	if (armor <= bullet.animSpeed) {
		return bullet.animSpeed * (play.blink == 0? 1:0.25);
	} else {
		return (bullet.animSpeed - ((0.0125f * (bullet.animSpeed*3)) * armor)) * (play.blink == 0? 1:0.25);
	}
  }
  
  enum sharedVars 
  {
    IS_TAIL,
    HP,
    IS_INACTIVE,
    PARENT,
    BODYIDX,
    IS_MASTER_HEAD
  };
  
  class IceDrag : jjBEHAVIORINTERFACE
  {
    int numBody = NUM_BODY_PARTS;

    int InitBodyPart(jjOBJ @obj, int idx, int parentObjIdx)
    {
      int dragBodyIdx =
          jjAddObject(OBJECT::CRAB, obj.xPos + obj.direction * idx * g_maxR,
                      obj.yPos, obj.objectID, CREATOR::OBJECT);
      jjOBJ @bodyObj = jjObjects[dragBodyIdx];
      bool isHead = idx == 0;
      bool isTail = idx == numBody - 1;

      if (isHead)
      {
        bodyObj.determineCurAnim(ANIM::CUSTOM[27], 5);
		bodyObj.var[9] = 1;
      }
      else if (isTail)
      {
        bodyObj.determineCurAnim(ANIM::CUSTOM[27], 6);
      }
      else
      {
        bodyObj.determineCurAnim(ANIM::CUSTOM[17], 2);
      }

      bodyObj.frameID = 0;
      bodyObj.direction = -obj.direction;
      bodyObj.deactivates = false;
      bodyObj.counter = 0;
      IceDragBody iceDragBody = IceDragBody();
      iceDragBody.bodyIdx = idx;
      bodyObj.behavior = iceDragBody;
      // auto@ b = cast<IceDragBody>(cast<jjBEHAVIORINTERFACE>(bodyObj.behavior));
      // b.bodyIdx++;
      bodyObj.var[PARENT] =
          isHead ? -1 : parentObjIdx; // obj.var[PARENT] points to parent node
      bodyObj.var[IS_TAIL] = isTail ? 1 : 0;
      bodyObj.var[HP] = isHead ? int(g_hpBossMax) : int(g_hpBodyMax);
      bodyObj.var[IS_INACTIVE] = 0;
      bodyObj.var[BODYIDX] = numBody - idx;
      bodyObj.var[IS_MASTER_HEAD] = idx == 0 ? 1 : 0;
      bodyObj.isTarget = isHead ? true : false;
      bodyObj.age = 0;
      bodyObj.playerHandling = HANDLING::SPECIAL;
      bodyObj.scriptedCollisions = true;
      bodyObj.bulletHandling = HANDLING::DETECTBULLET;
	  bodyObj.light = 8;
	  bodyObj.lightType = bodyObj.var[IS_INACTIVE] == 0? LIGHT::NORMAL : LIGHT::NONE;

      return dragBodyIdx;
    }

    void onBehave(jjOBJ @obj)
    {
      if (obj.state == STATE::START)
      {
        g_currBossHp = g_hpBossMax;
        g_lastHeadIdx = numBody;
        g_lastHpIDx = g_hpBossMax;
        // g_bossHalfHP = obj.var[HP] < g_hpBossMax / 2;
        obj.direction =
            jjPlayers[obj.findNearestPlayer(30000)].xPos - obj.xPos < 0 ? 1 : -1;

        int parentPart = InitBodyPart(obj, 0, 0);

        for (int i = 1; i < numBody; i++)
        {
          parentPart = InitBodyPart(obj, i, parentPart);
        }
        obj.state = STATE::KILL;
      }
      else
        obj.delete();
    }

  } void applyGenericEnemySettingsToPreset(jjOBJ @preset)
  {
    preset.playerHandling = HANDLING::ENEMY;
    preset.bulletHandling = HANDLING::HURTBYBULLET;
    preset.causesRicochet = false;
    preset.isBlastable = false;
    preset.triggersTNT = true;
    preset.isFreezable = true;
    preset.isTarget = true;
    preset.scriptedCollisions = false;
    preset.direction = 1;
    preset.freeze = 0;
  }
} // namespace SMOKE
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	return MLLE::WeaponHook.drawAmmo(player, canvas);
}

void onPlayer(jjPLAYER@ play) {
	
	if (camera) elapsed++;
	if (elapsed == 120) {
		play.cameraUnfreeze(false);
	}
	HH24::player(play);
	
	if (play.yPos < 20*32 && play.xPos < 40*32) {
		jjMusicLoad("slamjam.it");
	} else {
		if (play.yPos < 30*32) jjMusicLoad("jm-deepd3.it");
	}
}

void onLevelBegin() {
	MLLE::SpawnOffgrids();
	
	jjSampleLoad(SOUND::DOG_AGRESSIV, "HH17_Roar.wav");
	jjSampleLoad(SOUND::FAN_FAN, "HH17_Glass1.wav");
	
	jjObjectPresets[OBJECT::ELECTROBULLET].animSpeed = 1;
	//jjWeapons[WEAPON::GUN9].style = WEAPON::CAPPED;
	
	jjLocalPlayers[0].showText(0,0,STRING::SMALL);
}

void onMain() {
	
	HH17::handleEnemyProjectiles();
	array<jjLAYER@> layers = jjLayerOrderGet();
	layers[layers.length() - 3].hasTiles = jjColorDepth == 8? true:false;
	layers[layers.length() - 2].hasTiles = layers[layers.length() - 1].hasTiles = jjColorDepth == 16? true:false;
	HH24::main();

}

bool onDrawScore(jjPLAYER@ play, jjCANVAS@ canvas) {
	if (play is null)
		return false;
	const auto rightBorder = jjSubscreenWidth + 50, bottomBorder = jjSubscreenHeight + 50;
	for (int i = 1; i < jjObjectCount; ++i) {
		const jjOBJ@ obj = jjObjects[i];
		if (elapsed > 140 && obj.var[9] == 1 && obj.state != STATE::KILL && obj.state != STATE::BOUNCE) {
			const int gemX = int(obj.xPos - 18 - play.cameraX) + jjBorderWidth, gemY = int(obj.yPos - 47 - play.cameraY) + jjBorderHeight;
			int distance = int((abs(gemX) + abs(gemY))/32);
			if (gemX >= -50 && gemY >= -50 && gemX < rightBorder && gemY < bottomBorder) { //onscreen
				//do nothing lel
			} else {
				//https://stackoverflow.com/questions/32030488/get-the-coordinates-at-the-edge-of-the-screen-from-a-given-angle
				const int centerX = jjSubscreenWidth / 2, centerY = jjSubscreenHeight / 2;
				const int reducedWidth = centerX - 40, reducedHeight = centerY - 40;
				const auto xDiff = obj.xPos - (centerX + jjBorderWidth + play.cameraX), yDiff = obj.yPos - (centerY + jjBorderHeight + play.cameraY);
				const auto angle = atan2(yDiff, xDiff);
				float xEnd = cos(angle) * 1000, yEnd = sin(angle) * 1000; //as measured from 0,0
				float xFactor = reducedWidth / xEnd;
				if (xEnd < -reducedWidth)
					xFactor = -xFactor;
				else if (xEnd <= reducedWidth)
					xFactor = 1;
				xEnd *= xFactor;
				yEnd *= xFactor;
				float yFactor = reducedHeight / yEnd;
				if (yEnd < -reducedHeight)
					yFactor = -yFactor;
				else if (yEnd <= reducedHeight)
					yFactor = 1;
				xEnd *= yFactor;
				yEnd *= yFactor;
				xEnd += centerX;
				yEnd += centerY;
				canvas.drawRotatedSpriteFromCurFrame(int(xEnd),int(yEnd), labelFrameID + 1, int(angle * -512.0 * 0.318309886142228), 1,1, SPRITE::ALPHAMAP, distance > 60? 36 : distance > 50? 35 : distance > 40? 34 : 33);
				
				canvas.drawResizedSprite(int(xEnd), int(yEnd), ANIM::CUSTOM[27], 5, 0, 0.35, 0.35, SPRITE::NORMAL);
				
				if (obj.var[10] == 1) canvas.drawResizedSprite(int(xEnd + 12), int(yEnd + 12), ANIM::CUSTOM[17], 3, 0, 0.5, 0.5, SPRITE::NORMAL);
				if (obj.var[10] == 2) canvas.drawResizedSprite(int(xEnd + 12), int(yEnd + 12), ANIM::ROCK, 0, 0, 0.15, 0.15, SPRITE::SINGLECOLOR, ChromaKeyIndex);
				if (obj.var[10] == 3) canvas.drawResizedSprite(int(xEnd + 12), int(yEnd + 12), ANIM::DEVILDEVAN, 17, 0, 0.5, 0.5, SPRITE::NORMAL);
        if (obj.var[10] == 4) canvas.drawResizedSprite(int(xEnd + 12), int(yEnd + 12), ANIM::RAPIER, 0, 0, 0.5, 0.5, SPRITE::FROZEN);
        if (obj.var[10] == 5) canvas.drawResizedSprite(int(xEnd + 6), int(yEnd + 9), ANIM::FLAG, 0, 1, 0.5, 0.5);

			}
		}
	}
	
	HH24::score(play, canvas, false);
	
	return false;
}