Downloads containing ab20ctf12.j2as

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Anniversary Bash 20 Levels Jazz2Online Multiple N/A Download file

File preview

#include "MLLE-Include-1.4.asc"
const bool MLLESetupSuccessful = MLLE::Setup();
#pragma require "wbrg.j2t"
#pragma require "Corrupted Sanctuary.j2t"
#pragma require "Damn1.j2t"
#pragma require "ab20ctf12-MLLE-Data-2.j2l"
#pragma require "ab20ctf12-MLLE-Data-1.j2l"
#pragma require "ab20ctf12.j2l"
#pragma require "Waz18.j2t"
#pragma require "BurningTowers3.j2t"
#pragma require "Thermal.j2t"
#pragma require "Rootalopicus.j2t"
#pragma require "Volcano.j2t"
#pragma require "lavaflow.wav"
#pragma require "Rumble1.wav"
#pragma require "Rumble3.wav"
#pragma require "lavadrop.wav"
#pragma require "Meteor.j2a"
#pragma require "expmine.wav"
#pragma require "efatt1.wav"
#pragma require "weaponMega.j2a"
#pragma require "SEfirework.asc"
#include "weaponMega.asc"
#include "SEfirework.asc"
se::DefaultWeaponHook weaponHook;
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	return weaponHook.drawAmmo(player, canvas);
}
funcdef jjPALCOLOR ColorFunction(jjPALCOLOR);

class vector2i {
	int x, y;
}
array<vector2i> oneWays;
array<bool> inside(4, false);

class Level {
	private int sample = 0;
	
	void alignObjects() {
		for (int i = 1; i < jjObjectCount; i++) {
			jjOBJ@ obj = jjObjects[i];
			if (obj.eventID == OBJECT::FULLENERGY) obj.xPos = obj.xOrg + 4;
		}
	}
	
	void ashFallout() {
		for (int i = 0; i < 1024; i++) {
			jjPARTICLE@ particle = jjParticles[i];
			if (particle.type == PARTICLE::FLOWER) {
				particle.xSpeed = 0.25;
				particle.ySpeed = 3;
				particle.type = PARTICLE::SMOKE;
			}
			if (particle.type == PARTICLE::SMOKE) {
				particle.ySpeed = jjLocalPlayers[0].ySpeed < 0? 3 : int(3 + jjLocalPlayers[0].ySpeed);
				if (jjMaskedPixel(int(particle.xPos), int(particle.yPos))) {
					particle.type = PARTICLE::INACTIVE;
				}
			}
			if (particle.type == PARTICLE::FIRE) {
				particle.fire.size = particle.fire.color == 40? 3:2;
			}
		}
	}

	void createCustomObjects() {
		generateCustomSpringSprites(jjAnimSets[ANIM::CUSTOM[2]], array<uint> = {88});
		turnIntoCustomSpring(jjObjectPresets[OBJECT::FROZENSPRING], 0, 30.f, false);
		
		for (int i = 1; i < 255; i++) {
			if (jjObjectPresets[i].playerHandling == HANDLING::PICKUP || jjObjects[i].eventID == OBJECT::SEEKERAMMO3) {
				jjObjectPresets[i].behavior = CannotBeShotDown(jjObjectPresets[i].behavior);
			}
		}
		jjAnimSets[ANIM::CUSTOM[22]].load(0, "Meteor.j2a");

		jjAnimations[jjAnimSets[ANIM::AMMO] + 24] = jjAnimations[jjAnimSets[ANIM::CUSTOM[22]] + 1];
		jjAnimations[jjAnimSets[ANIM::AMMO] + 25] = jjAnimations[jjAnimSets[ANIM::CUSTOM[22]] + 2];
		
		jjObjectPresets[OBJECT::BOUNCERBULLET].behavior = jjObjectPresets[OBJECT::BOUNCERBULLETPU].behavior = Meteor();
		jjObjectPresets[OBJECT::BOUNCERBULLET].special = jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurAnim(ANIM::CUSTOM[22], 1);
		jjObjectPresets[OBJECT::BOUNCERBULLETPU].special = jjObjectPresets[OBJECT::BOUNCERBULLETPU].determineCurAnim(ANIM::CUSTOM[22], 0);
		jjObjectPresets[OBJECT::BOUNCERBULLET].ySpeed = jjObjectPresets[OBJECT::BOUNCERBULLETPU].ySpeed = jjObjectPresets[OBJECT::BLASTERBULLET].ySpeed;
		jjObjectPresets[OBJECT::BOUNCERBULLETPU].killAnim = jjObjectPresets[OBJECT::SEEKERBULLET].killAnim;
		jjObjectPresets[OBJECT::BOUNCERBULLET].lightType = LIGHT::POINT;
		jjObjectPresets[OBJECT::BOUNCERBULLETPU].lightType = LIGHT::BRIGHT;
		jjObjectPresets[OBJECT::BOUNCERBULLET].light = jjObjectPresets[OBJECT::BOUNCERBULLETPU].light = 10;
		
		jjObjectPresets[OBJECT::BOUNCERAMMO15].determineCurAnim(ANIM::CUSTOM[22], 3);
		jjObjectPresets[OBJECT::BOUNCERAMMO15].determineCurFrame();
		
		jjObjectPresets[OBJECT::BOUNCERPOWERUP].determineCurAnim(ANIM::CUSTOM[22], 4);
		jjObjectPresets[OBJECT::BOUNCERPOWERUP].determineCurFrame();
		
		jjWeapons[WEAPON::BOUNCER].defaultSample = false;
		jjWeapons[WEAPON::BOUNCER].style = WEAPON::MISSILE;
		
		jjObjectPresets[OBJECT::TOASTERBULLET].behavior =
		jjObjectPresets[OBJECT::TOASTERBULLETPU].behavior = SuperToaster();
		jjObjectPresets[OBJECT::TOASTERBULLET].lightType =
		jjObjectPresets[OBJECT::TOASTERBULLETPU].lightType = LIGHT::FLICKER;
		jjObjectPresets[OBJECT::TOASTERBULLET].light =
		jjObjectPresets[OBJECT::TOASTERBULLETPU].light = 12;
		
		jjWeapons[WEAPON::TOASTER].multiplier = 12;
		jjWeapons[WEAPON::TOASTER].gradualAim = true;
		
		jjObjectPresets[OBJECT::FIREBALLBULLET].behavior =
		jjObjectPresets[OBJECT::FIREBALLBULLETPU].behavior = SuperFireball();
		jjObjectPresets[OBJECT::FIREBALLBULLET].killAnim =
		jjObjectPresets[OBJECT::FIREBALLBULLETPU].killAnim = jjObjectPresets[OBJECT::SEEKERBULLET].killAnim;
		jjObjectPresets[OBJECT::FIREBALLBULLET].lightType =
		jjObjectPresets[OBJECT::FIREBALLBULLETPU].lightType = LIGHT::FLICKER;
		jjObjectPresets[OBJECT::FIREBALLBULLET].light =
		jjObjectPresets[OBJECT::FIREBALLBULLETPU].light = 12;
		jjObjectPresets[OBJECT::FIREBALLBULLET].special = jjObjectPresets[OBJECT::FIREBALLBULLET].determineCurAnim(ANIM::BILSBOSS, 3);
		jjObjectPresets[OBJECT::FIREBALLBULLET].xSpeed = 6;
		jjObjectPresets[OBJECT::FIREBALLBULLETPU].xSpeed = 7.5;
		
		jjWeapons[WEAPON::GUN8].spread = SPREAD::NORMAL;
		
		
		se::firework.loadAnims(jjAnimSets[ANIM::CUSTOM[1]]);
		se::firework.loadSamples(array<SOUND::Sample> = {SOUND::P2_PINCH1, SOUND::P2_PINCH2});
		se::firework.setAsWeapon(4, weaponHook);
		
		jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::COMMON] + 3];
		for (uint i = 0; i < anim.frameCount; ++i) {
			jjANIMFRAME@ frame = jjAnimFrames[anim + i];
			jjPIXELMAP sprite(frame);
			for (uint x = 0; x < sprite.width; ++x)
				for (uint y = 0; y < sprite.height; ++y)
				if (sprite[x,y] != 0) sprite[x,y] -= 32;
			sprite.save(frame);
		}
		
		jjDelayGeneratedCrateOrigins = true;
	}
	
	void darkenLayer(jjLAYER@ layer, array<uint8>& freePaletteIndices, ColorFunction@ func) {
		array<int> tileIDs, uniqueTileIDs;
		for (int i = 0; i < layer.height; i++) {
			for (int j = 0; j < layer.width; j++) {
				int tileID = layer.tileGet(j, i);
				if (tileID != 0)
					tileIDs.insertLast(tileID);
			}
		}
		int prev = 0;
		tileIDs.sortAsc();
		for (uint i = 0; i < tileIDs.length(); i++) {
			if (tileIDs[i] != prev)
				uniqueTileIDs.insertLast(prev = tileIDs[i]);
		}
		uint firstNewTile = jjTileCount;
		jjTilesFromTileset(jjTilesetFileName, 1, uniqueTileIDs.length());
		array<uint8> mapping(256);
		for (uint i = 0; i < uniqueTileIDs.length(); i++) {
			jjPIXELMAP tile(uniqueTileIDs[i]);
			for (int j = 0; j < 32; j++) {
				for (int k = 0; k < 32; k++) {
					uint8 pixel = tile[k, j];
					if (pixel != 0) {
						if (mapping[pixel] == 0) {
							jjPALCOLOR color = func(jjPalette.color[pixel]);
							uint8 bestMatch = jjPalette.findNearestColor(color);
							if (!freePaletteIndices.isEmpty()) {
								jjPALCOLOR other = jjPalette.color[bestMatch];
								int red = other.red - color.red;
								int green = other.green - color.green;
								int blue = other.blue - color.blue;
								int distance = red * red + green * green + blue * blue;
								if (distance > 16) {
									bestMatch = freePaletteIndices[freePaletteIndices.length() - 1];
									jjPalette.color[bestMatch] = color;
									freePaletteIndices.removeLast();
								}
							}
							mapping[pixel] = bestMatch;
						}
						tile[k, j] = mapping[pixel];
					}
				}
			}
			tile.save(firstNewTile + i);
		}
		layer.generateSettableTileArea();
		for (int i = 0; i < layer.height; i++) {
			for (int j = 0; j < layer.widthReal; j++) {
				int tileID = layer.tileGet(j, i);
				if (tileID != 0)
					layer.tileSet(j, i, firstNewTile + uniqueTileIDs.find(tileID));
			}
		}
	}
	
	void drawFlyCarrotTimer(jjPLAYER@ play, jjCANVAS@ canvas) {
		if (play.fly == FLIGHT::FLYCARROT) {
      		canvas.drawString(
			jjSubscreenWidth - 55,
	      		jjSubscreenHeight - 284,
	      		"" + (play.timerTime + 70) / 70,
	      		STRING::LARGE,
	      		STRING::PALSHIFT,
	      		play.timerTime > 3*70?
	      			0 :
	      			jjGameTicks % 28 > 14?
	      				-40 :
	      				-24
		);
		
		canvas.drawSprite(
			jjSubscreenWidth - 72,
			jjSubscreenHeight - 270,
			ANIM::PICKUPS,
			40, 
			jjGameTicks >> 2
		);
		}
	}
	
	void handleLevelBoundaries(jjPLAYER@ play) {
		const int bounds = 16;
	
		if (play.xPos > ((jjLayerWidth[4]*32) - bounds) || play.xPos < bounds) {
			play.xPos = play.xPos < (bounds+1)? bounds : (jjLayerWidth[4]*32) - bounds;
			play.xSpeed = 0;
			play.specialMove = 0;
		}
	}
	
	void handleLevelEffects(jjPLAYER@ play) {
		play.lightType = LIGHT::NONE;
		if (play.health == 0) inside[play.localPlayerID] = false;
		
		if (!jjLowDetail) {
			if (inside[play.localPlayerID]) {
				jjIsSnowing = false;
				play.lighting = 80;
			} else {
				jjIsSnowing = true;
				play.lighting = 100;
			}
		} else {
			jjIsSnowing = false;
			play.lighting = 100;
		}
		
		if (jjEventGet(int(play.xPos/32), int(play.yPos/32)) == AREA::WATERBLOCK) {
			play.fly = FLIGHT::NONE;
		}
		
		array<jjLAYER@> layers = jjLayerOrderGet();
		layers[0].xOffset -= 1;
		if (layers[0].xOffset % (layers[0].widthReal*32) == 0) layers[0].xOffset = 0;
		for (int i = 0; i < 5; i++) {
			layers[12 + i].xOffset -= 0.25f * (2 + i);
			if (layers[12 + i].xOffset % (layers[12 + i].widthReal*32) == 0) layers[12 + i].xOffset = 0;
		}
		
		if (!jjLowDetail) {
			if (jjGameTicks > 7) {
				sample = jjSampleLooped(play.xPos, play.yPos, SOUND::WIND_WIND2A, sample, inside[play.localPlayerID]? 60:40, 0);
				if (jjGameTicks % 1200 == 0) jjSample(play.xPos, play.yPos, SOUND::BILSBOSS_THUNDER, inside[play.localPlayerID]? 60:40, 0);
				if (jjGameTicks % 3600 == 0 && inside[play.playerID]) jjSample(play.xPos, play.yPos, SOUND::BILSBOSS_FIRE, 50, 0);
			}
		}
	}
	
	void handleKickingOnOneWaySlopes(jjPLAYER@ play) {
		for (uint i = 0; i < oneWays.length(); i++) {
			jjEventSet(oneWays[i].x, oneWays[i].y, AREA::ONEWAY);
		}
		oneWays.resize(0);
		int px = int(play.xPos), py = int(play.yPos);
		bool masked;
		for (int i = -11 + int(play.ySpeed); i <= 14; i++) {
			if (masked = jjMaskedHLine(px - 14, 28, py + i))
				break;
		}
		if (!masked) {
			for (int i = 8; i <= 16; i += 8) {
				for (int j = 12; j <= 20; j += 8) {
					int x = (px + play.direction * j) >>> 5, y = (py + i) >>> 5;
					if (x >= 0 && y >= 0 && x < jjLayerWidth[4] && y < jjLayerHeight[4] && jjEventGet(x, y) == AREA::ONEWAY) {
						vector2i point;
						jjEventSet(point.x = x, point.y = y, 0);
						oneWays.insertLast(point);
					}
				}
			}
		}
	}
	
	void hideBackgroundLayers(jjPLAYER@ play) {
		array<jjLAYER@> layers = jjLayerOrderGet();
		if (play.cameraX >= 204*32 && play.cameraX <= 262*32 && play.cameraY >= 70*32) {
			for (int i = 12; i <= 21; i++) {
				layers[i].hasTiles = false;
			}
		} else {
			for (int i = 12; i <= 21; i++) {
				layers[i].hasTiles = true;
			}
		}
	}
	
	void loadSamples() {
		jjSampleLoad(SOUND::WIND_WIND2A, "lavaflow.wav");
		jjSampleLoad(SOUND::BILSBOSS_THUNDER, "Rumble1.wav");
		jjSampleLoad(SOUND::BILSBOSS_FIRE, "Rumble3.wav");
		jjSampleLoad(SOUND::ORANGE_BOEMR, "expmine.wav");
		jjSampleLoad(SOUND::COMMON_WATER, "lavadrop.wav");
		jjSampleLoad(SOUND::COMMON_RINGGUN, "efatt1.wav");
	}

	void setSkyProperties() {
		jjTexturedBGTexture = TEXTURE::MEDIVO;
		jjTexturedBGFadePositionY = 0.45;
		jjUseLayer8Speeds = true;
	}
	
	void timedFlyCarrot(jjPLAYER@ play) {
		const uint flyCMaxTime = 700;
		
		if (play.fly == FLIGHT::FLYCARROT && play.timerState == TIMER::STOPPED) play.timerStart(flyCMaxTime);
		if (play.fly == FLIGHT::NONE) play.timerStop();
		if (play.timerState == TIMER::STARTED && play.timerTime <= 3*70 && play.timerTime > 0 && play.timerTime % 70 == 0) jjSamplePriority(SOUND::COMMON_NOCOIN);
		if (play.timerTime == 0) play.fly = FLIGHT::NONE;
		
		if (jjGameState == GAME::PAUSED) play.timerPause();
		else play.timerResume();
	}
}

jjANIMSET@ customSpringSprite;
array<int> fastCustomSpringSpeeds(jjLocalPlayerCount);
bool generateCustomSpringSprites(jjANIMSET@ anim, const array<uint> &in colors) {
	int length = colors.length();
	bool success = (@customSpringSprite = anim).allocate(array<uint>(length * 3, 5)) !is null;
	if (success) {
		uint srcSet = jjAnimSets[ANIM::SPRING];
		for (int i = 0; i < length; i++) {
			uint color = colors[i];
			uint destAnimOffset = anim + i * 3;
			for (int j = 0; j < 3; j++) {
				uint srcAnim = jjAnimations[srcSet + j];
				uint destAnim = jjAnimations[destAnimOffset + j];
				for (int k = 0; k < 5; k++) {
					jjPIXELMAP image(jjAnimFrames[destAnim + k] = jjAnimFrames[srcAnim + k]);
					int width = image.width;
					int height = image.height;
					for (int l = 0; l < height; l++) {
						for (int m = 0; m < width; m++) {
							int pixel = image[m, l];
							if (pixel >= 32 && pixel < 40)
								image[m, l] = color + (pixel & 7);
						}
					}
					if (!image.save(jjAnimFrames[destAnim + k]))
						return false;
				}
			}
		}
	}
	return success;
}

void initializeCustomSpring(jjOBJ@ obj) {
	int anim = obj.curAnim;
	obj.behave(obj.behavior = BEHAVIOR::SPRING, false);
	if (obj.curAnim != anim) {
		obj.curAnim = anim + 2;
		obj.determineCurFrame();
	}
	obj.draw();
}

void turnIntoCustomSpring(jjOBJ@ obj, uint color, float power, bool horizontal) {
	if (horizontal) {
		obj.xSpeed = power;
		obj.ySpeed = 0.f;
	} else {
		obj.xSpeed = 0.f;
		obj.ySpeed = -power;
		if (obj.state == STATE::START && obj.creatorType == CREATOR::LEVEL) {
			int x = int(obj.xPos) >> 5;
			int y = int(obj.yPos) >> 5;
			if (jjParameterGet(x, y, 0, 1) != 0) {
				jjParameterSet(x, y, 0, 1, 0);
				obj.yPos -= 4.f;
				obj.ySpeed = power;
			}
		}
	}
	obj.behavior = initializeCustomSpring;
	obj.curAnim = customSpringSprite + color * 3 + (horizontal ? 1 : 0);
	obj.energy = obj.frameID = obj.freeze = obj.justHit = obj.light = obj.points = 0;
	obj.isBlastable = obj.isTarget = obj.scriptedCollisions = obj.triggersTNT = false;
	obj.deactivates = obj.isFreezable = true;
	obj.bulletHandling = HANDLING::IGNOREBULLET;
	obj.playerHandling = HANDLING::SPECIAL;
	obj.lightType = LIGHT::NORMAL;
	obj.determineCurFrame();
}

class CannotBeShotDown : jjBEHAVIORINTERFACE {
	jjBEHAVIOR originalBehavior;
	CannotBeShotDown(jjBEHAVIOR behavior) {
		originalBehavior = behavior;
	}
	void onBehave(jjOBJ@ obj) override {
		obj.behave(originalBehavior);
		if (obj.state == STATE::FLOATFALL)
			obj.state = STATE::FLOAT;
		if (obj.eventID == OBJECT::FULLENERGY)
			obj.xPos = obj.xOrg - 16;
	}
	bool onObjectHit(jjOBJ@ obj, jjOBJ@ bullet, jjPLAYER@ player, int force) {
		obj.behavior = originalBehavior;
		if (bullet is null)
			player.objectHit(obj, force, obj.playerHandling);
		else
			bullet.objectHit(obj, obj.playerHandling);
		obj.behavior = CannotBeShotDown(obj.behavior);
		return true;
	}
}

class Meteor : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET, obj.state == STATE::EXPLODE? true:false);
		jjPLAYER@ creator = jjPlayers[obj.creatorID];
		
		if (obj.state != STATE::EXPLODE) {
			if (obj.counter == 1 && creator.isLocal) {
				jjSample(creator.xPos, creator.yPos, SOUND::ORANGE_BOEMR, 42, obj.eventID == OBJECT::BOUNCERBULLET? 22000 : 20000);
				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;
				
				for (int i = -1; i <= 1; i+= 2) {
					Rock temp;
						jjOBJ@ rock = jjObjects[jjAddObject(OBJECT::SHARD, int(obj.xPos + (i * 12)), int(obj.yPos - 8), obj.creatorID, CREATOR::PLAYER, jjVOIDFUNCOBJ(temp.onBehave))];
						rock.determineCurAnim(obj.eventID == OBJECT::BOUNCERBULLETPU? ANIM::CUSTOM[22] : ANIM::FONT, obj.eventID == OBJECT::BOUNCERBULLETPU? 0:14);
						rock.playerHandling = HANDLING::PLAYERBULLET;
						rock.var[3] = 2;
						rock.var[4] = obj.var[4];
						rock.var[5] = obj.eventID == OBJECT::BOUNCERBULLETPU? 0:1;
						rock.var[6] = obj.eventID == OBJECT::BOUNCERBULLETPU? 8:0;
						rock.special = obj.eventID == OBJECT::BOUNCERBULLETPU? 40:72;
						rock.animSpeed = 2;
						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;
				}
			}
		}
	}
}

class Rock : jjBEHAVIORINTERFACE {
	void onBehave(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], obj.var[5] == 0? 0:1, 0, -obj.age, 0.5, 0.5, SPRITE::SINGLEHUE, obj.special);
			
			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 && obj.var[5] == 2) {
				smoke.xPos = obj.xPos;
				smoke.yPos = obj.yPos;
			}
			
		}
		
	}
}

class SuperToaster : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::TOASTERBULLET);
		
		if (obj.counter == 1) obj.xSpeed = 5 * obj.direction;
		if (obj.xSpeed != 0) obj.ySpeed += 0.085;
		
		jjPARTICLE@ cinders = jjAddParticle(PARTICLE::FIRE);
		if (cinders !is null && obj.counter > 10) {
			cinders.xPos = int(obj.xPos - 8) + jjRandom()%17;
			cinders.yPos = int(obj.yPos - 8) + jjRandom()%17;
			cinders.fire.color = obj.eventID == OBJECT::TOASTERBULLETPU? 32:40;
			cinders.fire.colorStop = obj.eventID == OBJECT::TOASTERBULLETPU? 40:48;
			cinders.fire.colorDelta = 2;
		}
	}
}

class SuperFireball : jjBEHAVIORINTERFACE {
	void onBehave(jjOBJ@ obj) {
		obj.behave(BEHAVIOR::BULLET, obj.state != STATE::FLY? true: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::BILSBOSS, 3, jjGameTicks >> 2, obj.var[0], 1, 1, SPRITE::SINGLEHUE, obj.eventID == OBJECT::FIREBALLBULLETPU? 88:40);
		}
	}
}

bool LoadWeaponMega3() {
	if (!SafeToReplaceWeapon(WEAPON::ICE))
		return false;
	
	jjWeapons[WEAPON::ICE].style = WEAPON::POPCORN;
	jjWeapons[WEAPON::ICE].spread = SPREAD::TOASTER;
	jjWeapons[WEAPON::ICE].multiplier = 6;
	jjWeapons[WEAPON::ICE].gradualAim = true;
	
	Preset1.behavior = ApplyIceCloud;
	Preset2.behavior = ApplyIceCloud;
	Preset1.special = Preset1.determineCurAnim(AnimSet, 0);
	Preset2.special = Preset2.determineCurAnim(AnimSet, 0);
	Preset1.determineCurFrame(); Preset2.determineCurFrame();
	Preset1.isBlastable = true; Preset2.isBlastable = true;
	//Preset1.var[6] = Preset1.var[6] | 16; //"fireball"
	//Preset2.var[6] = Preset2.var[6] | 16;
	Preset1.counterEnd = 120; //lifetime
	Preset2.counterEnd = 200;
	
	return true;
}
const bool WeaponMega3Loaded = LoadWeaponMega3();

void ApplyIceCloud(jjOBJ@ obj) { obj.behavior = IceCloud(obj); obj.behave(); }
class IceCloud : WeaponMega {
	IceCloud(jjOBJ@ objectOfAttachment) {
		@obj = @objectOfAttachment;
		obj.xSpeed += obj.var[7] / 65536.f;
		if (obj.creatorType == CREATOR::PLAYER)
			obj.ySpeed += int(jjPlayers[obj.creatorID].ySpeed) / 2;
		obj.state = STATE::FLY;
	}
	void onBehave(jjOBJ@) override {
		obj.xPos += obj.xSpeed *= 0.96;
		obj.yPos += obj.ySpeed *= 0.96;
		if (obj.frameID < 3 && ++obj.age & 15 == 15) {
			++obj.frameID; ++obj.curFrame;
		}
		if (--obj.counterEnd == 0 || obj.state == STATE::EXPLODE) {
			int playerID;
			if (isPowerup() && (playerID = getNearestEnemy(256)) >= 0) {
				obj.frameID = 0;
				obj.killAnim = (obj.curAnim += 1) + 1;
				obj.counterEnd = 100;
				obj.freeze = 0;
				const jjPLAYER@ target = jjPlayers[playerID];
				obj.xSpeed = (target.xPos - obj.xPos) / 25;
				obj.ySpeed = (target.yPos - obj.yPos) / 25;
				obj.xAcc = obj.yAcc = obj.var[7] = 0;
				obj.behavior = BEHAVIOR::BULLET;
			} else {
				obj.counter = 0;
				obj.frameID = 0;
				obj.curAnim += 2;
				obj.behavior = BEHAVIOR::EXPLOSION;
			}
			jjSample(obj.xPos, obj.yPos, SOUND::COMMON_ICECRUSH);
		} else if (jjGameTicks & 1 == 0) {
			jjPARTICLE@ part = jjAddParticle(PARTICLE::ICETRAIL);
			if (part !is null) {
				part.yPos = obj.yPos;
				part.xPos = obj.xPos + (jjRandom() & 31) - 15;
			}
		}
	}
	void onDraw(jjOBJ@) override {
		jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, 1, SPRITE::TRANSLUCENT);
		if (obj.frameID == 3 && isPowerup())
			jjDrawSpriteFromCurFrame(obj.xPos, obj.yPos, jjAnimations[obj.curAnim + 1], 0, SPRITE::TRANSLUCENTCOLOR, 15);
	}
}

Level level;

void onLevelLoad() {
	level.createCustomObjects();
	level.loadSamples();
	level.setSkyProperties();
	
	array<jjLAYER@> layers = jjLayerOrderGet();
	if (layers[6].hasTileMap) {
		level.darkenLayer(layers[6], array<uint8>(), function(color) {
			color.setHSL(color.getHue(), color.getSat(), color.getLight() * 7 / 10);
			return color;
		});
	}
	
	if (layers[7].hasTileMap) {
		level.darkenLayer(layers[7], array<uint8>(), function(color) {
			color.setHSL(color.getHue(), color.getSat(), color.getLight() * 2 / 5);
			return color;
		});
	}
	
	if (layers[9].hasTileMap) {
		level.darkenLayer(layers[9], array<uint8>(), function(color) {
			color.setHSL(color.getHue(), color.getSat(), color.getLight() * 3 / 5);
			color.red = color.red + 30;
			return color;
		});
	}
}

void onLevelReload() {
	MLLE::Palette.apply();
}

void onPlayer(jjPLAYER@ play) {
	level.handleLevelBoundaries(play);
	level.handleLevelEffects(play);
	level.handleKickingOnOneWaySlopes(play);
	if (!jjLowDetail) level.hideBackgroundLayers(play);
	level.timedFlyCarrot(play);
	weaponHook.processPlayer(play);
}

void onPlayerInput(jjPLAYER@ play) {
	weaponHook.processPlayerInput(play);
}

void onPlayerTimerEnd(jjPLAYER@ play) {
	play.fly = FLIGHT::NONE;
}

void onMain() {
	level.alignObjects();
	level.ashFallout();
	weaponHook.processMain();
}

void onReceive(jjSTREAM &in packet, int clientID) {
	weaponHook.processPacket(packet, clientID);
}

bool onDrawPlayerTimer(jjPLAYER@ play, jjCANVAS@ canvas) {
	level.drawFlyCarrotTimer(play, canvas);
	return true;
}

void onFunction0(jjPLAYER@ play) {
	inside[play.localPlayerID] = false;
}

void onFunction1(jjPLAYER@ play) {
	inside[play.localPlayerID] = true;
}