Downloads containing primpLoriFortress.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Lori FortressFeatured Download Primpy Single player 8.7 Download file

File preview

const bool MLLESetupSuccessful = MLLE::Setup(); ///@MLLE-Generated
#include "MLLE-Include-1.6.asc" ///@MLLE-Generated
#pragma require "primpLoriFortress-MLLE-Data-3.j2l" ///@MLLE-Generated
#pragma require "primpLoriFortress-MLLE-Data-2.j2l" ///@MLLE-Generated
#pragma require "primpLoriFortress-MLLE-Data-1.j2l" ///@MLLE-Generated
#pragma require "primpLoriFortress.j2l" ///@MLLE-Generated
#pragma require "Meteor.j2a"
#pragma require "CosmicDust.j2a"
#pragma require "Mortar.j2a"
#pragma require "Lightningrod.j2a"
#pragma require "CloneMachine.j2a"
#pragma require "expmine.wav"
#pragma require "lowind.wav"
#pragma require "f_gren4.wav"
#pragma require "ZAPFIZZ1.wav"
#pragma require "ZAPFIZZ2.wav"
#pragma require "kaze_loop.ogg"
#pragma require "fireeye.ogg"

#include "ArcaneWeapon2.asc"
#include "ArcaneWeapon3.asc"
#include "ArcaneWeapon4.asc"
#include "ArcaneWeapon7.asc"
#include "ArcaneWeapon8.asc"
#include "LFgems.asc"

CosmicDust Duster();

uint FriendColor(uint8 a, uint8 b, uint8 c, uint8 d) { return a | (b << 8) | (c << 16) | (d << 24); }
uint8 loriCounter = 0;
bool bossDefeated = false, bossStarted = false;
	
void onLevelLoad()
{
	jjPLAYER@ Player = jjLocalPlayers[0];
	Player.morphTo(Player.charOrig = CHAR::LORI, false);
	jjAnimSets[ANIM::CUSTOM[22]].load(0, "Meteor.j2a");
	jjAnimSets[ANIM::CUSTOM[23]].load(0, "CosmicDust.j2a");
	jjAnimSets[ANIM::CUSTOM[24]].load(0, "Mortar.j2a");
	jjAnimSets[ANIM::CUSTOM[27]].load(0, "Lightningrod.j2a");
	jjAnimSets[ANIM::CUSTOM[28]].load(0, "CloneMachine.j2a");
	Foo(jjObjectPresets[OBJECT::MORPH]);
	CloneMachine(jjObjectPresets[OBJECT::ROBOT]);
	jjPlayers[28].fur = FriendColor(64,24,64,72);
}

void onMain() {
	gem::deleteCollectedGems();
	jjDrawSprite(1083.646, 4012.51, ANIM::JAZZ, RABBIT::CORPSE, 0, 0, SPRITE::NORMAL);
	jjDrawSprite(1597.939, 4072.51, ANIM::SPAZ, RABBIT::CORPSE, 0, 0, SPRITE::NORMAL);
	if (jjTriggers[1] == true && bossStarted == false) {
		jjDrawSpriteFromCurFrame(5007, 387, jjAnimations[jjAnimSets[ANIM::CUSTOM[28]]].firstFrame + 2, 0, SPRITE::NORMAL);
		}
}

void onPlayer(jjPLAYER@ play)
{
	gem::trackPlayerGems(play);
	gem::upgradeHealth(play);
}

void onLevelReload()
{
	gem::restorePlayerGems();
	jjLocalPlayers[0].lives++;
	loriCounter = 0;
	bossDefeated = false;
	bossStarted = false;
	jjMusicLoad("kaze_loop.ogg");
	CloneMachine(jjObjectPresets[OBJECT::ROBOT]);
}

bool onDrawLives(jjPLAYER@ play, jjCANVAS@ canvas)  { return true; }


enum FooWeapon { None, Meteor, Duster, Mortar, Rod, Sanguine };
bool FooSoundsLoaded = false;
array<MLLEWeapons::WeaponInterface@> ArcaneWeapons = {null, ArcaneWeapons::MeteorGun::Weapon(), ArcaneWeapons::CosmicDuster::Weapon(), ArcaneWeapons::MortarLauncher::Weapon(), ArcaneWeapons::LightningRod::Weapon(), ArcaneWeapons::SanguineSpear::Weapon()};

class Foo : jjBEHAVIORINTERFACE {

	Foo(jjOBJ@ preset) {
		preset.behavior = this;
		preset.playerHandling = HANDLING::ENEMY;
		preset.scriptedCollisions = true;
		preset.bulletHandling = HANDLING::HURTBYBULLET;
		preset.isTarget = true;
		preset.isFreezable = true;
		preset.isBlastable = false;
		preset.energy = 88; // 3 + 45 buffer (Lori does an absurd amount of damage with her special kick, just making sure...)
		preset.determineCurAnim(ANIM::LORI, RABBIT::STAND);
		preset.determineCurFrame();
		preset.direction = -1;
		preset.triggersTNT = false;
	}
	void onBehave(jjOBJ@ obj) override {
		uint frameID;

		const auto lastCurFrame = obj.curFrame;
		jjPLAYER@ Player = jjLocalPlayers[0];
		uint loriHurtFreq;
		const auto@ anim = jjAnimations[jjAnimSets[obj.special] + obj.curAnim];
		
		if ( (obj.energy <= 85 || bossDefeated == true) && obj.state != STATE::EXTRA) {
			obj.frameID = 0;
			obj.bulletHandling = HANDLING::IGNOREBULLET;
			obj.playerHandling = HANDLING::DYING;
			obj.isTarget = false;
			obj.isFreezable = false;
			obj.state = STATE::EXTRA;
			}
			
		switch (obj.state) {
			case STATE::START: {
				if (!FooSoundsLoaded) {
					FooSoundsLoaded = true;
					jjSampleLoad(SOUND::ORANGE_BOEMR, "expmine.wav");
					jjSampleLoad(SOUND::ORANGE_BOEML, "lowind.wav");
					jjSampleLoad(SOUND::ORANGE_MERGE, "f_gren4.wav");
					jjSampleLoad(SOUND::COMMON_ELECTRICHIT, "ZAPFIZZ1.wav");
					jjSampleLoad(SOUND::COMMON_ELECTRIC2, "ZAPFIZZ2.wav");
				}
					obj.special = ANIM::LORI;
				switch (obj.doesHurt = (getParameterAtOrigin(obj, 0, 3) % 6)) { //weapon
					case FooWeapon::Meteor:
						obj.state = STATE::BOUNCE;
						obj.curAnim = RABBIT::JUMPFIRERIGHT;
						break;
					case FooWeapon::Duster:
						obj.state = STATE::JUMP;
						obj.curAnim = RABBIT::JUMPFIRERIGHT;
						break;
					case FooWeapon::Mortar:
						obj.state = STATE::ATTACK;
						obj.curAnim = RABBIT::DIVEFIRERIGHT;
						obj.counter = jjRandom();
						break;
					case FooWeapon::Rod:
						obj.state = STATE::FLY;
						obj.curAnim = RABBIT::AIRBOARD;
						break;
					case FooWeapon::Sanguine:
					default:
						obj.state = STATE::WALK;
						break;
				}
				if (obj.doesHurt != FooWeapon::Rod)
					obj.putOnGround(true);
				break; }
			case STATE::BOUNCE: //Meteor
				if (obj.counter == 0) //starting out
					obj.yAcc = obj.yPos;
				obj.yPos = obj.yAcc - abs(jjSin(obj.counter += 8) * 128);
				if (obj.counter & 511 == 0) {
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_JUMP);
					jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos + 12)].determineCurAnim(ANIM::AMMO, 72);
				} else if (obj.counter & 511 == 256) {
					jjOBJ@ meteor = fireBullet(obj, lastCurFrame, OBJECT::BOUNCERBULLET);
					jjSample(meteor.xPos, meteor.yPos, SOUND::ORANGE_BOEMR, 42, 20000);
					meteor.determineCurAnim(ANIM::CUSTOM[22], 1);
					meteor.ySpeed = 0;
					meteor.killAnim = jjObjectPresets[OBJECT::SEEKERBULLET].killAnim;
					meteor.lightType = LIGHT::POINT;
					meteor.light = 10;
					meteor.xSpeed = abs(meteor.xSpeed);
					meteor.xAcc = abs(meteor.xAcc);
					meteor.behavior = Meteor;
				}
				obj.direction = (obj.xPos < Player.xPos) ? 1 : -1;
				frameID = obj.counter >> 6;
				break;
			case STATE::JUMP: //Duster
				if (obj.ySpeed < 0 && jjMaskedPixel(int(obj.xPos), int(obj.yPos + obj.ySpeed) - 12))
					obj.ySpeed = 0;
				else
					obj.yPos += obj.ySpeed += 0.25;
				if (jjMaskedPixel(int(obj.xPos), int(obj.yPos) + 12)) {
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_JUMP);
					jjObjects[jjAddObject(OBJECT::EXPLOSION, obj.xPos, obj.yPos + 12)].determineCurAnim(ANIM::AMMO, 72);
					obj.ySpeed = -8;
				} else {
					const float targetX = obj.xPos + obj.direction * 1.25 * (89 - obj.energy);
					if (jjMaskedVLine(int(targetX), int(obj.yPos) - 12, 24))
						obj.direction = -obj.direction;
					else
						obj.xPos = targetX;
				}
				if (++obj.counter % 50 == 1) {
					jjOBJ@ duster = fireBullet(obj, lastCurFrame, OBJECT::ICEBULLET);
					duster.determineCurAnim(ANIM::CUSTOM[23], 1);
					duster.counterEnd = 180;
					duster.lightType = LIGHT::BRIGHT;
					duster.light = 8;
					duster.playerHandling = HANDLING::PICKUP;
					duster.scriptedCollisions = true;
					duster.behavior = Duster;
				}
				obj.var[1] = jjSampleLooped(obj.xPos,obj.yPos,SOUND::ORANGE_BOEML,obj.var[1]);
				frameID = obj.counter >> 6;
				break;
			case STATE::ATTACK: //Mortar
				if (++obj.counter & (obj.creatorType == CREATOR::LEVEL ? 63 : 127) == 5) {
					jjOBJ@ mortar = fireBullet(obj, lastCurFrame, OBJECT::SEEKERBULLET);
					jjSample(mortar.xPos, mortar.yPos, SOUND::ORANGE_MERGE, 0, 0);
					mortar.determineCurAnim(ANIM::CUSTOM[22], 1);
					mortar.xSpeed = 7;
					mortar.xAcc = abs(mortar.xAcc);
					mortar.ySpeed = -9 + (jjRandom() & ((4 << (88 - obj.energy)) - 1)) / 2.f;
					mortar.counterEnd = 90;
					mortar.behavior = Mortar;
				}
				obj.direction = (obj.xPos < Player.xPos) ? 1 : -1;
				frameID = obj.counter >> 6;
				break;
			case STATE::FLY: //Rod
				obj.xPos = obj.xOrg + jjSin((++obj.counterEnd) << 2) * 100;
				obj.yPos = obj.yOrg + 8 * (1 - jjSin(jjGameTicks * 12));
				obj.direction = (((obj.counterEnd + 64) & 255) < 128) ? 1 : -1;
				if (obj.counterEnd & 127 == 64) {
					obj.var[0] = 22;
					obj.curAnim = RABBIT::AIRBOARDTURN;
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_AIRBTURN, 100, 16537);
				}
				if (obj.var[0] != 0) {
					if ((obj.var[0] = obj.var[0] - 1) == 0) {
						obj.curAnim = RABBIT::AIRBOARD;
					jjSample(obj.xPos, obj.yPos, SOUND::COMMON_AIRBTURN2, 0, 16537);
					} else
						frameID = obj.var[0] / 3;
				}
				if (obj.var[0] == 0) { //not turning
					frameID = obj.counterEnd >> 3;
					if (obj.age < -60 && (obj.energy < 88 || (Player.yPos > obj.yPos && abs(Player.xPos - obj.xPos) < 50)) && !jjMaskedHLine(int(obj.xPos) - 5, 10, int(obj.yPos))) {
						jjOBJ@ rod = fireBullet(obj, lastCurFrame, OBJECT::TNT);
						jjSample(rod.xPos, rod.yPos, SOUND::COMMON_MONITOR, 0, 12500);
						rod.determineCurAnim(ANIM::CUSTOM[27], 0);
						rod.playerHandling = HANDLING::PARTICLE;
						rod.bulletHandling = HANDLING::IGNOREBULLET;
						rod.counterEnd = 255;
						rod.var[4] = 1; //color
						rod.behavior = Lightningrod;
					}
				}
				obj.var[1] = jjSampleLooped(obj.xPos,obj.yPos,SOUND::COMMON_AIRBOARD,obj.var[1]);
				break;
			case STATE::WALK: //Sanguine/None
				obj.xSpeed = (89 - obj.energy) * 1.5 * obj.direction;
				obj.behave(BEHAVIOR::WALKINGENEMY, false);
				if (obj.doesHurt == FooWeapon::Sanguine) {
					if (obj.counter % 70 == 10) {
						for (uint i = obj.energy; i < 89; ++i) {
							jjOBJ@ spear = fireBullet(obj, lastCurFrame, OBJECT::FIREBALLBULLET);
							jjSample(spear.xPos, spear.yPos, SOUND::HATTER_PTOEI, 0, 20000);
							spear.xSpeed = abs(obj.xSpeed) * 4;
							spear.xAcc = abs(spear.xAcc);
							spear.ySpeed = -2;
							spear.counterEnd = 95;
							spear.var[6] = 8;
							spear.behavior = BloodSpear;
							if (obj.energy != 88 && i == 88)
								spear.ySpeed *= 2;
							else if (i == 87)
								spear.ySpeed = 0;
						}
					}
				}
				if (obj.energy == 88) obj.curAnim = RABBIT::RUN1;
				else obj.curAnim = RABBIT::RUN2 + (87 - obj.energy);
				frameID = ++obj.counter / int(10 - abs(obj.xSpeed));
				break;
			case STATE::EXTRA: //dying
				if (obj.justHit == 1 || bossDefeated == true) {
					if (Player.bossActivated == true && loriCounter != 0)
						loriCounter--;
					obj.curAnim = RABBIT::DIE;
					loriHurtFreq = jjRandom()%3;
					if (bossDefeated == false) {
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_DIE1, 0, (loriHurtFreq == 0) ? 35000 : (loriHurtFreq == 1) ? 30000 : 25000);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_SHOOT); } }
				if (not ((jjMaskedPixel(int(obj.xPos), int(obj.yPos)+22)) or (jjMaskedPixel(int(obj.xPos)+11, int(obj.yPos)+22)) or (jjMaskedPixel(int(obj.xPos)-11, int(obj.yPos)+22))))
					obj.yPos = obj.yPos + 2 + ((obj.curAnim == RABBIT::CORPSE) ? 23 : (obj.frameID * 2));

				if (uint(obj.frameID) < 23) {
					if (jjGameTicks % 7 == 0) obj.frameID++; }
				else {
					obj.curAnim = RABBIT::CORPSE;
					//obj.delete();
					return;
				}
				break;
			default: //deactivate/freeze/done
				obj.behave(BEHAVIOR::TUBETURTLE, false);
				return;
		}
		if (obj.justHit == 1 && obj.energy > 85) {
			uint loriHurt = jjRandom()%8;
			loriHurtFreq = jjRandom()%3;
			switch (loriHurtFreq) {
			case 0: loriHurtFreq = 35000; break;
			case 1: loriHurtFreq = 30000; break;
			case 2: loriHurtFreq = 25000; break;
			}
			switch (loriHurt) {
				case 0:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT0, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDRO, 40);
					break;
				case 1:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT1, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDRO2, 40);
					break;
				case 2:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT2, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDROFIL, 40);
					break;
				case 3:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT3, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDRO, 40);
					break;
				case 4:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT4, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDRO2, 40);
					break;
				case 5:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT5, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDROFIL, 40);
					break;
				case 6:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT6, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDRO, 40);
					break;
				case 7:
					jjSample(obj.xPos, obj.yPos, SOUND::LORISOUNDS_HURT7, 0, loriHurtFreq);
					jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_HYDROFIL, 40);
					break;
			}
		}
		if (obj.state != STATE::EXTRA)
			obj.curFrame = anim.firstFrame + (frameID % anim.frameCount); // hell if I know what was Violet doing with frameID...
		else obj.curFrame = anim.firstFrame + (obj.frameID % anim.frameCount);
	}

	jjOBJ@ fireBullet(jjOBJ@ obj, int lastCurFrame, OBJECT::Object eventID) const {
		obj.age = 5;
		obj.curFrame = lastCurFrame; //for gunspot, etc.
		jjOBJ@ bullet = jjObjects[obj.fireBullet(eventID)];
		bullet.playerHandling = HANDLING::ENEMYBULLET;
		bullet.animSpeed = 1;
		return bullet;
	}
	
	void onDraw(jjOBJ@ obj) {
		const SPRITE::Mode mode = obj.state == STATE::FREEZE ? SPRITE::FROZEN : obj.justHit == 0 ? SPRITE::PLAYER : SPRITE::SINGLECOLOR;
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, mode, 28);
		if (obj.state == STATE::DONE)
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::TRANSLUCENTCOLOR, 28); //got to do it this way, because TINTED would erase the player colors and make it look like jazz instead of purplejazz
		else if (obj.age-- > 0) { //flare time
			const jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
			jjDrawSprite(obj.xPos + (frame.hotSpotX - frame.gunSpotX) * obj.direction, obj.yPos + frame.hotSpotY - frame.gunSpotY, ANIM::AMMO, 16, 0, obj.direction, SPRITE::PLAYER, 28); //flare
		}
	}
}

int getParameterAtOrigin(const jjOBJ@ obj, int offset, int length) /*const*/ {
	return jjParameterGet(uint(obj.xOrg) >> 5, uint(obj.yOrg) >> 5, offset, length);
}

void Meteor(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BULLET, obj.state == STATE::EXPLODE? true:false);
	
	if (obj.state != STATE::EXPLODE) {
		obj.var[2] = 0;
		obj.age += obj.direction == 0? 10 : 10 * obj.direction;
		
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[22], obj.eventID == OBJECT::BOUNCERBULLET? 1:0, 0, -obj.age, 1, 1, obj.eventID == OBJECT::BOUNCERBULLET || obj.var[4] == 1? SPRITE::SINGLEHUE : SPRITE::NORMAL, 72);
		
		jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
		if (smoke !is null) {
			smoke.xPos = smoke.xPos;
			smoke.yPos = smoke.yPos;
		}
		
		if (obj.eventID == OBJECT::BOUNCERBULLETPU && obj.var[4] == 0) {
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[22], 0, 0, -obj.age, 1, 1, SPRITE::TRANSLUCENTSINGLEHUE, 40);
			jjPARTICLE@ cinders = jjAddParticle(PARTICLE::FIRE);
			if (cinders !is null) {
				cinders.xPos = int(obj.xPos - 8) + jjRandom()%17;
				cinders.yPos = int(obj.yPos - 8) + jjRandom()%17;
			}
		}
		
		if (obj.yPos > jjWaterLevel) {
			obj.var[4] = 1;
			obj.xSpeed = obj.xSpeed * 0.875;
			obj.ySpeed = obj.ySpeed * 0.875;
		}
	
		switch (obj.direction) {
			case 1: obj.xSpeed -= obj.eventID == OBJECT::BOUNCERBULLET? 0.1:0.15; obj.ySpeed += obj.eventID == OBJECT::BOUNCERBULLET? 0.15:0.2; break;
			case -1: obj.xSpeed += obj.eventID == OBJECT::BOUNCERBULLET? 0.1:0.15; obj.ySpeed += obj.eventID == OBJECT::BOUNCERBULLET? 0.15:0.2; break;
		}
		
		if (obj.xSpeed == 0) obj.ySpeed += 0.4;
		if (obj.ySpeed > 8) obj.ySpeed = 8;
		

		
	} else {
		obj.age = 0;
		if (obj.var[2] == 0) {
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_BENZIN1, 0, 0);
			obj.var[2] = 1;
			
			if (obj.eventID == OBJECT::BOUNCERBULLETPU) {
				for (int i = -1; i <= 1; i+= 2) {
					jjOBJ@ rock = jjObjects[jjAddObject(OBJECT::SHARD, int(obj.xPos + (i * 12)), int(obj.yPos - 8), obj.creatorID, CREATOR::OBJECT, Rock)];
					rock.determineCurAnim(ANIM::CUSTOM[22], 1);
					rock.playerHandling = HANDLING::ENEMYBULLET;
					rock.var[3] = 2;
					rock.var[4] = obj.var[4];
					rock.var[6] = 8;
					rock.animSpeed = 1;
					rock.direction = i;
					rock.xSpeed = 6 * i;
					rock.ySpeed = -3;
					rock.state = STATE::FLY;
					rock.lightType = LIGHT::POINT;
					rock.light = 10;
					rock.counterEnd = jjObjectPresets[OBJECT::BOUNCERBULLET].counterEnd;
					rock.killAnim = jjObjectPresets[OBJECT::BOUNCERBULLET].killAnim;
				}
			}
		}
	}
}

void Rock(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BULLET, obj.state == STATE::EXPLODE? true:false);
	
	if (obj.state == STATE::FLY) {
		obj.age += obj.direction == 0? 10 : 10 * obj.direction;
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[22], 1, 0, -obj.age, 1, 1, obj.var[4] == 1? SPRITE::SINGLEHUE : SPRITE::NORMAL, 72);
		
		switch (obj.direction) {
			case 1: obj.xSpeed -= 0.05; obj.ySpeed += 0.1; break;
			case -1: obj.xSpeed += 0.05; obj.ySpeed += 0.1; break;
		}
		
		if (obj.yPos > jjWaterLevel) {
			obj.var[4] = 1;
			obj.xSpeed = obj.xSpeed * 0.875;
			obj.ySpeed = obj.ySpeed * 0.875;
		}
		
		jjPARTICLE@ smoke = jjAddParticle(PARTICLE::SMOKE);
		if (smoke !is null) {
			smoke.xPos = obj.xPos;
			smoke.yPos = obj.yPos;
		}
		
	}
	
}

class CosmicDust : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		const jjOBJ@ creator = jjObjects[obj.creatorID];
		if (creator.eventID != OBJECT::MORPH) { //ded
			obj.delete();
			return;
		}
		obj.behave(obj.state == STATE::EXPLODE? BEHAVIOR::BULLET : BEHAVIOR::TNT, false);
		obj.var[0] = obj.var[0] + (5 * obj.direction);
		
		if (obj.state != STATE::EXPLODE) {
			obj.xPos = creator.xPos + (obj.eventID == OBJECT::ICEBULLETPU? 160:120)*jjSin((obj.counter + 1)*12) * (obj.direction != 0? obj.direction : 1);
			obj.yPos = creator.yPos - (obj.eventID == OBJECT::ICEBULLETPU? 80:60)*jjCos((obj.counter + 1)*12);
		} else {
			obj.delete();
		}
	}
	void onDraw(jjOBJ@ obj) {
		if (obj.state != STATE::EXPLODE && obj.counter > 1) {
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[23], obj.eventID == OBJECT::ICEBULLETPU? 0:1, 0, -obj.var[0], 1, 1, SPRITE::ALPHAMAP, obj.eventID == OBJECT::ICEBULLETPU? 34:72);
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[23], obj.eventID == OBJECT::ICEBULLETPU? 0:1, 0, -obj.var[0], 1, 1, SPRITE::ALPHAMAP, obj.eventID == OBJECT::ICEBULLETPU? 34:72);
		}
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		player.frozen = obj.freeze;
		obj.delete();
		return true;
	}
}

void Mortar(jjOBJ@ obj) {
	obj.behave(obj.state == STATE::EXPLODE? BEHAVIOR::RFBULLET : BEHAVIOR::BULLET, false);
	if (obj.yPos <= 0) obj.state = STATE::EXPLODE;
	obj.var[0] = int(atan2(-obj.ySpeed, obj.xSpeed) * (512.f * 0.318309886142228f));
	
	switch (obj.state) {
		case STATE::START:
			obj.state = STATE::FLY;
			obj.lightType = LIGHT::POINT;
			obj.var[2] = 0;
		break;
		
		case STATE::FLY:
			jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[24], obj.eventID == OBJECT::SEEKERBULLETPU? 2:0, jjGameTicks >> 2, obj.var[0], 0.75, 0.75, SPRITE::NORMAL);
			
			if (obj.counter % 5 == 0 && !jjLowDetail) {
				jjOBJ@ trail = jjObjects[jjAddObject(OBJECT::EXPLOSION, int(obj.xPos - jjCos(obj.var[0])), int(obj.yPos - jjSin(obj.var[0])))];
				trail.determineCurAnim(ANIM::AMMO, 3);
				trail.lightType = LIGHT::NONE;
				trail.playerHandling = HANDLING::PARTICLE;
				trail.bulletHandling = HANDLING::IGNOREBULLET;
				trail.isBlastable = false;
			}
			
			switch (obj.direction) {
				case 1: obj.xSpeed -= 0.275; obj.ySpeed += 0.225; break;
				case -1: obj.xSpeed += 0.275; obj.ySpeed += 0.225; break;
			}
			if (obj.xSpeed == 0) obj.ySpeed += 0.3;
			if (obj.ySpeed > 12) obj.ySpeed = 12;
		break;
		
		case STATE::EXPLODE:
			jjDrawResizedSprite(obj.xPos, obj.yPos, ANIM::AMMO, 5, obj.curFrame + 5, 2, 2, SPRITE::NORMAL);
		
			if (obj.var[2] == 0) {
				jjOBJ@ blast = jjObjects[jjAddObject(OBJECT::BULLET, obj.xPos, obj.yPos, obj.creatorID, CREATOR::OBJECT, MortarShockwave)];
				obj.var[2] = 1;
				blast.var[2] = 1;
				blast.animSpeed = obj.animSpeed;
			}
		break;
	}
}

void MortarShockwave(jjOBJ@ obj) {
	obj.playerHandling = HANDLING::PARTICLE;
	obj.bulletHandling = HANDLING::IGNOREBULLET;
	
	if (obj.var[2] == 1) {
		obj.lightType = obj.var[2] == 1? LIGHT::RING2 : LIGHT::NONE;
		obj.var[1] = obj.var[1] + 1;
		obj.light += 2;
		obj.var[4] = obj.light * 4;
		
		jjPLAYER@ player = jjLocalPlayers[0];
		float dx = player.xPos - obj.xPos, dy = player.yPos - obj.yPos;
		if (dx * dx + dy * dy < obj.var[4] * obj.var[4])
			player.hurt(obj.animSpeed);
	}
	
	if (obj.var[1] == 14) {
		obj.var[1] = 0;
		obj.var[2] = 0;
		obj.var[4] = 0;
		obj.delete();
	}
}
		
void BloodSpear(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BULLET, false);
	obj.var[0] = int(atan2(-obj.ySpeed, obj.xSpeed) * (512.f * 0.318309886142228f));
	
	if (obj.state == STATE::FLY) {
		jjDrawRotatedSprite(obj.xPos, obj.yPos, ANIM::HATTER, 3, jjGameTicks >> 2, obj.var[0], 2, 1, SPRITE::SINGLEHUE, obj.eventID == OBJECT::FIREBALLBULLETPU? 15:24);
		switch (obj.direction) {
			case 1: obj.xSpeed -= 0.1; obj.ySpeed += 0.1; break;
			case -1: obj.xSpeed += 0.1; obj.ySpeed += 0.1; break;
		}
		if (obj.xSpeed == 0) obj.ySpeed += 0.15;
		
		jjPARTICLE@ blood = jjAddParticle(PARTICLE::ICETRAIL);
		blood.xPos = obj.xPos;
		blood.yPos = obj.yPos;
		blood.icetrail.color = obj.eventID == OBJECT::FIREBALLBULLETPU? 16:24;
		blood.icetrail.colorStop = obj.eventID == OBJECT::FIREBALLBULLETPU? 24:32;
	}
	
	if (obj.state == STATE::EXPLODE) {
		jjDrawSprite(obj.xPos, obj.yPos, ANIM::MONKEY, 1, obj.curFrame, obj.direction, SPRITE::SINGLEHUE, obj.eventID == OBJECT::FIREBALLBULLETPU? 15:24);
		if (obj.var[1] < 27) {
			if (obj.var[1] == 1) jjSample(obj.xPos, obj.yPos, SOUND::HATTER_SPLOUT, 0, 20000);
			obj.var[1] = obj.var[1] + 1;
		}
		if (obj.var[1] == 27) {
			obj.var[1] = 0;
			obj.delete();
		}
	}
}

void Lightningrod(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BULLET, false);
	
	jjDrawSprite(obj.xPos, obj.yPos, ANIM::CUSTOM[27], obj.var[0] == 1? obj.var[4] : 0, obj.var[0] == 1? jjGameTicks / 10 % 6 : 0, obj.direction, SPRITE::NORMAL);
	
	if (obj.counter == 2) {
		obj.age = 0;
		obj.var[1] = 0;
	}
	
	if (obj.counter == 127) obj.counter = 2; 
	
	if (!jjMaskedHLine(int(obj.xPos - 16), 24, int(obj.yPos + 8))) {
		obj.yPos += 6;
		obj.var[0] = 0;
		obj.lightType = LIGHT::POINT;
	} else {
		obj.var[0] = 1;
	}
	
	if (obj.var[0] == 1) {
		obj.age++;
		if (obj.var[1] == 0) {
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_LANDCAN1, 0, 30000);
			obj.var[1] = 1;
		}
		obj.lightType = LIGHT::BRIGHT;
		obj.light = 10;
		if (obj.age % 20 == 0) {
			for (int i = -4; i <= 4; i += 4) {
				int id = jjAddObject(OBJECT::LIGHTNINGSHIELDBULLET, obj.xPos, obj.yPos - 12, obj.creatorID, CREATOR::OBJECT, Electricity);
				if (id != 0) {
					jjOBJ@ zap = jjObjects[id];
					zap.lightType = LIGHT::NONE;
					zap.counterEnd = 6;
					zap.direction = obj.direction;
					zap.lightType = LIGHT::NONE;
					zap.playerHandling = HANDLING::ENEMYBULLET;
					zap.var[3] = 1;
					zap.var[6] = 8;
					zap.xSpeed = i;
					zap.xAcc = 0;
					zap.animSpeed = 1;
					if (i == 0) zap.ySpeed = -4;
				}
			}
		}
	}
	
	if (obj.age == 420) {
		obj.var[0] = obj.var[1] = 0;
		obj.particlePixelExplosion(0);
		obj.delete();
	}
}

void Electricity(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::BULLET, false);
}

void onFunction0(jjPLAYER@ player) {
       player.activateBoss();
	   jjTriggers[1] = true;
	   player.invincibility = 0;
	   jjMusicLoad("fireeye.ogg");
	   player.limitXScroll(uint(136), uint(26));
}
  
class CloneMachine: jjBEHAVIORINTERFACE {
	uint recharge, maxHP;
	int currentHP;
	CloneMachine(jjOBJ@ preset) {
		preset.behavior = this;
		preset.playerHandling = HANDLING::SPECIAL;
		preset.bulletHandling = HANDLING::DETECTBULLET;
		preset.scriptedCollisions = true;
		preset.isTarget = false;
		preset.isFreezable = false;
		preset.isBlastable = false;
		preset.triggersTNT = false;
		preset.deactivates = false;
		preset.determineCurAnim(ANIM::CUSTOM[28], 0);
		//preset.determineCurFrame();
		preset.curFrame = 2;
		preset.direction = 1;
		preset.special = 0;
		preset.counter = 0;
		preset.frameID = 2;
		preset.energy = 100;
		recharge = (jjDifficulty <= 0) ? 320 : (jjDifficulty == 1) ? 280 : 240;
		maxHP = (jjDifficulty <= 0) ? 200 : (jjDifficulty == 1) ? 250 : 325;
		currentHP = maxHP;
	}
	
    void onBehave(jjOBJ @ obj) {
	  obj.energy = 100 * currentHP / maxHP;
	  int playerID = obj.findNearestPlayer(8000000);
	  if (jjPlayers[playerID].bossActivated == false && bossDefeated == false) return;
	  if (uint(obj.special) < recharge && obj.state != STATE::EXTRA) {
		obj.special++;
	  }
	  else if (uint(obj.special) >= recharge && obj.state != STATE::KILL && obj.state != STATE::EXTRA) obj.special = 0;
      if (obj.state == STATE::START) {
		jjPlayers[playerID].boss = obj.objectID;
		obj.putOnGround();
	  }
	  if (obj.special == 140 && loriCounter < 5 && obj.state != STATE::EXTRA) {
		if (obj.counter == 0) { // preventing double Lori spawn
			switch (jjRandom()%4) {
			case 0:	jjParameterSet(151, 11, 0, 3, 0);	break;
			case 1:	jjParameterSet(151, 11, 0, 3, 1);	break;
			case 2:	jjParameterSet(151, 11, 0, 3, 2);	break;
			case 3:	jjParameterSet(151, 11, 0, 3, 5);	break;
			}
			jjObjects[jjAddObject(OBJECT::MORPH, obj.xPos - 150, obj.yPos - 20)];
			jjSample(jjPlayers[playerID].xPos, jjPlayers[playerID].yPos, SOUND::COMMON_TELPORT2);
			loriCounter++;
			obj.counter = 1;
			}
		}
		else obj.counter = 0;
		
	  if (playerID > -1) {
		if (obj.age > 0) obj.age--;
		jjPLAYER@ play;
		float dx = jjPlayers[playerID].xPos - obj.xPos + 80, dy = jjLocalPlayers[playerID].yPos + 50 - obj.yPos;
		if (dx * dx + dy * dy < 140 * 140) {
			obj.age = 35;
			jjPlayers[playerID].xSpeed = -8 * obj.direction;
			//jjPlayers[playerID].ySpeed = -8 * obj.direction;
			//jjPlayers[playerID].hurt(0, false);
		}
	  }
      if (obj.justHit == 0 && obj.state != STATE::EXTRA) {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::NORMAL, 0, 5);
      } else if (obj.state != STATE::EXTRA)
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::SINGLECOLOR, 28, 5);
	  bossStarted = true;
	  if (obj.state == STATE::KILL) {
		jjPlayers[playerID].activateBoss(false);
		jjSample(jjPlayers[playerID].xPos, jjPlayers[playerID].yPos, SOUND::INTRO_BOEM2);
		jjNxt(false, false);
		bossDefeated = true;
		obj.special = 254;
		obj.state = STATE::EXTRA;
	  }	  
	  
	  if (obj.state == STATE::EXTRA) {
		if (obj.special > 0)
			obj.special--;
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction, SPRITE::BLEND_DISSOLVE, obj.special);
	  }
	  if (obj.justHit == 1) {
		switch (jjRandom()%5) {
			case 0:	jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_METAL1);	break;
			case 1:	jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_METAL2);	break;
			case 2:	jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_METAL3);	break;
			case 3:	jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_METAL4);	break;
			case 4:	jjSample(obj.xPos, obj.yPos, SOUND::ROBOT_METAL5);	break;
			}
	  }
	  if (loriCounter < 5 && obj.state != STATE::EXTRA) {
		if (obj.special == 120 || obj.special == 200) obj.frameID = 1;
		if (obj.special == 220) obj.frameID = 2;
		if (obj.special == 140) obj.frameID = 0;
	  }
	  else obj.frameID = 2;
	  obj.curFrame = jjAnimations[jjAnimSets[ANIM::CUSTOM[28]]].firstFrame + (obj.frameID % jjAnimations[jjAnimSets[ANIM::CUSTOM[28]]].frameCount);
     }
	
	 bool onObjectHit(jjOBJ @ obj, jjOBJ @ bullet, jjPLAYER @ player, int force) {
        if (bullet !is null) {
			bullet.state = STATE::EXPLODE;
			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.
			currentHP -= bullet.animSpeed;
			if (currentHP <= 0) { //killed
				currentHP = 0;
				obj.state = STATE::KILL;
			}		
      }
      return true;
    }
  }
  
bool onCheat(string &in cheat) {
	jjPLAYER@ Player = jjLocalPlayers[0];
	if (cheat == "jjgems")
		Player.gems[GEM::RED] = Player.gems[GEM::RED] + 100;
	else if (cheat == "jjmorph" || cheat == "jjcolor")
		return true; // forcing the player to play as canon Lori
	else if ((cheat == "jjk" || cheat == "jjkill") && Player.health <= 0) { ; }
	else if (cheat == "jjlori")
		jjSample(Player.xPos, Player.yPos, SOUND::LORISOUNDS_TOUCH);
	else
		return false;
	jjAlert(cheat, false, STRING::MEDIUM);
	return true;
}