Downloads containing ab20btl09.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 "ab20btl09.j2l"
#pragma require "Meteor.j2a"
#pragma require "expmine.wav"
#pragma require "SEfirework.asc"
#include "SEfirework.asc"

class Point {
	int x, y;
	Point() {}
	Point(int X, int Y) {
		x = X;
		y = Y;
	}
}
funcdef jjPALCOLOR ColorFunction(jjPALCOLOR);
void generateAndAddStars() {
	int tileOffset = jjTileCount;
	jjTilesFromTileset(jjTilesetFileName, 1, 128);
	for (int i = 0; i < 128; i++) {
		jjPIXELMAP tile;
		for (int j = 0; j < 32; j++) {
			for (int k = 0; k < 32; k++) {
				tile[k, j] = 58;
			}
		}
		for (int j = jjRandom() & 3; j >= 0; j--) {
			int color = jjRandom() & 15 == 0 ? 48 + (jjRandom() & 127) : 144 + (jjRandom() % 11);
			if (color == 149)
				color = 156;
			else if (color == 153)
				color = 157;
			tile[jjRandom() & 31, jjRandom() & 31] = color;
		}
		tile.save(tileOffset + i);
	}
	jjLayers[7].generateSettableTileArea();
	for (int i = 0; i < jjLayers[7].height; i++) {
		for (int j = 0; j < jjLayers[7].widthReal; j++) {
			if (jjLayers[7].tileGet(j, i) != 0)
				jjLayers[7].tileSet(j, i, tileOffset + (jjRandom() & 127));
		}
	}
	layer7Row = jjLayers[7].height / 2;
	@layer7Backup = jjLAYER(jjLayers[7]);
	layer7Backup.generateSettableTileArea();
}
array<uint8>@ findFreePaletteIndices(const array<uint8>& ignored) {
	const auto@ layers = jjLayerOrderGet();
	array<bool> contained(jjTileCount);
	for (int i = layers.length(); i-- != 0;) {
		const jjLAYER@ layer = layers[i];
		if (layer.hasTileMap) {
			for (int y = layer.height; y-- != 0;) {
				for (int x = layer.width; x-- != 0;) {
					const jjTILE@ tile = jjTiles[layer.tileGet(x, y)];
					const auto@ frames = tile.getFrames();
					for (int j = frames.length(); j-- != 0;) {
						contained[frames[j] & TILE::RAWRANGE] = true;
					}
				}
			}
		}
	}
	array<bool> used(256);
	for (int i = ignored.length(); i-- != 0;) {
		used[ignored[i]] = true;
	}
	for (int i = jjTileCount; i-- != 0;) {
		if (contained[i]) {
			jjPIXELMAP image(i);
			for (int y = 32; y-- != 0;) {
				for (int x = 32; x-- != 0;) {
					used[image[x, y]] = true;
				}
			}
		}
	}
	array<uint8> result;
	for (int i = 96; i < 250; i++) {
		if (!used[i])
			result.insertLast(i);
	}
	return result;
}
void correctTiles() {
	jjMASKMAP poleMask(133);
	poleMask.save(146);
	poleMask.save(153);
	poleMask.save(156);
	for (int i = 0; i < 14; i++) {
		for (int j = 0; j < 32; j++) {
			poleMask[j, i] = false;
		}
	}
	poleMask.save(163);
	jjMASKMAP vineMask(87);
	for (int i = 0; i < 32; i++) {
		vineMask[i, 5] = true;
	}
	vineMask.save(63);
	vineMask.save(64);
	vineMask.save(87);
	vineMask.save(89);
	vineMask.save(533);
	vineMask.save(534);
	vineMask.save(535);
	jjPIXELMAP(163 | TILE::VFLIPPED).save(153);
	for (int i = 146; i <= 156; i += 10) {
		jjPIXELMAP poleTile(i);
		for (int j = 0; j < 32; j++) {
			poleTile[23, j] = poleTile[22, j];
		}
		poleTile.save(i);
	}
}
void generateAndAddFogOld() {
	jjLAYER@ src = jjLayers[6];
	int tileOffset = jjTileCount;
	jjTilesFromTileset(jjTilesetFileName, 1, src.widthReal << 3);
	array<jjLAYER@> fogLayers(2);
	array<int> fogColors = {176, 207};
	int top = -1;
	int bottom = -1;
	for (int j = 0; j < src.height; j++) {
		for (int k = 0; k < src.widthReal; k++) {
			if (src.tileGet(k, j) != 0) {
				if (top < 0)
					top = j;
				bottom = j;
			}
		}
	}
	for (int i = 0; i < 2; i++) {
		jjLAYER fog(src);
		@fogLayers[i] = fog;
		int color = fogColors[i];
		fog.generateSettableTileArea(0, top, fog.widthReal, bottom - top + 1);
		fog.spriteMode = SPRITE::ALPHAMAP;
		fog.spriteParam = color;
		for (int j = top + 2; j < bottom - 1; j++) {
			for (int k = 0; k < fog.widthReal; k++) {
				fog.tileSet(k, j, 0);
			}
		}
		for (int j = 0; j < 2; j++) {
			for (int k = 0; k < fog.widthReal; k++) {
				{
					int y = top + j;
					jjPIXELMAP image(fog.tileGet(k, y));
					for (int m = 0; m < 32; m++) {
						int alpha = j << 5 | m;
						for (int n = 0; n < 32; n++) {
							int value = image[n, m] - color;
							if (value < 0)
								value = -value;
							value ^= 31;
							image[n, m] = value * alpha * 85 / 651;
						}
					}
					image.save(tileOffset);
					fog.tileSet(k, y, tileOffset++);
				} {
					int y = bottom - j;
					jjPIXELMAP image(fog.tileGet(k, y));
					for (int m = 0; m < 32; m++) {
						int alpha = j << 5 | m ^ 31;
						for (int n = 0; n < 32; n++) {
							int value = image[n, m] - color;
							if (value < 0)
								value = -value;
							value ^= 31;
							image[n, m] = value * alpha * 85 / 651;
						}
					}
					image.save(tileOffset);
					fog.tileSet(k, y, tileOffset++);
				}
			}
		}
	}
	src.generateSettableTileArea(0, top, src.widthReal, 2);
	src.generateSettableTileArea(0, bottom - 1, src.widthReal, 2);
	for (int j = 0; j < 2; j++) {
		for (int k = 0; k < src.widthReal; k++) {
			src.tileSet(k, top + j, 0);
			src.tileSet(k, bottom - j, 0);
		}
	}
	jjLayerOrderSet(array<jjLAYER@> = {
		jjLayers[2],
		jjLayers[3],
		jjLayers[4],
		jjLayers[1],
		jjLayers[5],
		jjLayers[6],
		fogLayers[0],
		fogLayers[1],
		jjLayers[7],
		jjLayers[8]
	});
}
void generateAndAddFog() {
	const int layerCount = 64;
	jjLAYER@ src = jjLayers[6];
	int tileOffset = jjTileCount;
	for (int i = 0; i < 2; i++) {
		jjTilesFromTileset(jjTilesetFileName, 1, src.widthReal * layerCount);
	}
	array<jjLAYER@> fogLayers(layerCount);
	int top = -1;
	int bottom = -1;
	for (int j = 0; j < src.height; j++) {
		for (int k = 0; k < src.widthReal; k++) {
			if (src.tileGet(k, j) != 0) {
				if (top < 0)
					top = j;
				bottom = j;
			}
		}
	}
	for (int i = 0; i < layerCount; i++) {
		if (i * i * 255 < 63 * 63)
			continue;
		jjLAYER fog(src);
		@fogLayers[i] = fog;
		fog.generateSettableTileArea(0, top, fog.widthReal, bottom - top + 1);
		fog.spriteMode = SPRITE::BLEND_NORMAL;
		fog.spriteParam = i * i * 255 / (63 * 63);
		for (int j = top; j <= bottom; j++) {
			for (int k = 0; k < fog.widthReal; k++) {
				fog.tileSet(k, j, 0);
			}
		}
		for (int k = 0; k < fog.widthReal; k++) {
			{
				int y = top + (i >> 5);
				jjPIXELMAP image(src.tileGet(k, y));
				jjPIXELMAP dest;
				for (int m = 0; m < 32; m++) {
					dest[m, i & 31] = image[m, i & 31];
				}
				dest.save(tileOffset);
				fog.tileSet(k, y, tileOffset++);
			} {
				int y = bottom - (i >> 5);
				jjPIXELMAP image(src.tileGet(k, y));
				jjPIXELMAP dest;
				for (int m = 0; m < 32; m++) {
					dest[m, ~i & 31] = image[m, ~i & 31];
				}
				dest.save(tileOffset);
				fog.tileSet(k, y, tileOffset++);
			}
		}
	}
	src.generateSettableTileArea(0, top, src.widthReal, 2);
	src.generateSettableTileArea(0, bottom - 1, src.widthReal, 2);
	for (int j = 0; j < 2; j++) {
		for (int k = 0; k < src.widthReal; k++) {
			src.tileSet(k, top + j, 0);
			src.tileSet(k, bottom - j, 0);
		}
	}
	array<jjLAYER@> order = {
		jjLayers[2],
		jjLayers[3],
		jjLayers[4],
		jjLayers[1],
		jjLayers[5],
		jjLayers[6],
		jjLayers[7],
		jjLayers[8]
	};
	for (int i = layerCount; i-- != 0;) {
		if (i * i * 255 < 63 * 63)
			break;
		order.insertAt(6, fogLayers[i]);
	}
	jjLayerOrderSet(order);
}
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[0];
								jjPalette.color[bestMatch] = color;
							}
						}
						int index = freePaletteIndices.find(bestMatch);
						if (index >= 0)
							freePaletteIndices.removeAt(index);
						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));
		}
	}
}
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.causesRicochet = 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();
}
jjANIMSET@ customSpringSprite;
jjLAYER@ layer7Backup;
int layer7Row = 0;
array<Point> oneWays;
array<bool> specialMove(jjLocalPlayerCount);
se::DefaultWeaponHook weaponHook;
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	return weaponHook.drawAmmo(player, canvas);
}
void onLevelLoad() {
	jjDelayGeneratedCrateOrigins = true;
	jjPalette.gradient(69, 88, 143, 0, 0, 0);
	array<uint8> ignoredIndices;
	for (int i = 0; i < 96; i++) {
		ignoredIndices.insertLast(i);
	}
	for (int i = 176; i < 208; i++) {
		ignoredIndices.insertLast(i);
	}
	array<uint8> freePaletteIndices = findFreePaletteIndices(ignoredIndices);
	generateAndAddStars();
	correctTiles();
	generateAndAddFog();
	if (jjLayers[8].hasTileMap)
		darkenLayer(jjLayers[8], freePaletteIndices, function(color) {
			color.setHSL(color.getHue(), color.getSat() / 2, color.getLight());
			return color;
		});
	if (jjLayers[1].hasTileMap)
		darkenLayer(jjLayers[1], freePaletteIndices, function(color) {
			color.setHSL(color.getHue(), color.getSat() * 4 / 5, color.getLight() * 2 / 5);
			return color;
		});
	for (int i = 0; i < jjLayers[4].height; i++) {
		for (int j = 0; j < jjLayers[4].width; j++) {
			if (jjEventGet(j, i) == AREA::ONEWAY)
				oneWays.insertLast(Point(j, i));
		}
	}
	int tileOffset = jjTileCount;
	jjTilesFromTileset(jjTilesetFileName, 1, 10);
	jjLayers[3].generateSettableTileArea();
	jjLayers[4].generateSettableTileArea();
	jjPIXELMAP slope(19);
	jjPIXELMAP tilebug1(8), tilebug1src(29);
	jjPIXELMAP tilebug2(29), tilebug2src(8);
	jjPIXELMAP tilebug3(9), tilebug3src(28);
	jjPIXELMAP tilebug4(28), tilebug4src(9);
	jjPIXELMAP tilebug5(9), tilebug5src(15);
	jjPIXELMAP tilebug6(3), tilebug6src(29);
	jjPIXELMAP slope2(28), slope2src(18);
	jjPIXELMAP slope3(8);
	jjPIXELMAP slope4(18);
	for (int i = 1; i < 32; i++) {
		for (int j = 0; j < i; j++) {
			slope[j, i] = 0;
		}
	}
	for (int i = 0; i < 32; i++) {
		for (int j = 0; i + j < 32; j++) {
			slope2[j, i] = slope2src[j, i];
		}
	}
	for (int i = 16; i < 32; i++) {
		for (int j = 47 - i; j < 32; j++) {
			slope3[j, i] = 0;
		}
	}
	for (int i = 0; i < 32; i++) {
		for (int j = i < 15 ? 15 - i : 0; j < 32; j++) {
			slope4[j, i] = 0;
		}
	}
	for (int i = 16; i < 32; i++) {
		for (int j = 31 - i; j < i - 16; j++) {
			bool less = jjPalette.color[tilebug1[j, i]].getLight() < jjPalette.color[tilebug1src[j, i]].getLight();
			if (less ^^ j < 8)
				tilebug1[j, i] = tilebug1src[j, i];
			less = jjPalette.color[tilebug2[j, i]].getLight() < jjPalette.color[tilebug2src[j, i]].getLight();
			if (less ^^ j >= 8)
				tilebug2[j, i] = tilebug2src[j, i];
		}
		for (int j = 47 - i ; j <= i; j++) {
			bool less = jjPalette.color[tilebug3[j, i]].getLight() < jjPalette.color[tilebug3src[j, i]].getLight();
			if (less ^^ j >= 24)
				tilebug3[j, i] = tilebug3src[j, i];
			less = jjPalette.color[tilebug4[j, i]].getLight() < jjPalette.color[tilebug4src[j, i]].getLight();
			if (less ^^ j < 24)
				tilebug4[j, i] = tilebug4src[j, i];
		}
		for (int j = 16; j <= i; j++) {
			bool less = jjPalette.color[tilebug5[j, i]].getLight() < jjPalette.color[tilebug5src[j, i]].getLight();
			if (less ^^ j >= i / 2 + 9)
				tilebug5[j, i] = tilebug5src[j, i];
		}
		for (int j = 0; j < i - 16; j++) {
			bool less = jjPalette.color[tilebug6[j, i]].getLight() < jjPalette.color[tilebug6src[j, i]].getLight();
			if (less ^^ j < i / 2 - 8)
				tilebug6[j, i] = tilebug6src[j, i];
		}
	}
	slope.save(tileOffset + 0);
	tilebug1.save(tileOffset + 1);
	tilebug2.save(tileOffset + 2);
	tilebug3.save(tileOffset + 3);
	tilebug4.save(tileOffset + 4);
	tilebug5.save(tileOffset + 5);
	tilebug6.save(tileOffset + 6);
	slope2.save(tileOffset + 7);
	slope3.save(tileOffset + 8);
	slope4.save(tileOffset + 9);
	jjMASKMAP(19).save(tileOffset);
	jjMASKMAP(8).save(tileOffset + 1);
	jjMASKMAP(29).save(tileOffset + 2);
	jjMASKMAP(9).save(tileOffset + 3);
	jjMASKMAP(28).save(tileOffset + 4);
	jjMASKMAP(9).save(tileOffset + 5);
	jjMASKMAP(3).save(tileOffset + 6);
	jjMASKMAP(28).save(tileOffset + 7);
	jjMASKMAP(8).save(tileOffset + 8);
	jjMASKMAP(18).save(tileOffset + 9);
	for (int i = 87; i < 94; i++) {
		for (int j = 23; j < 30; j++) {
			if (jjLayers[3].tileGet(j, i) == 151 && jjLayers[4].tileGet(j, i) == 19)
				jjLayers[4].tileSet(j, i, tileOffset);
		}
	}
	for (int i = 99; i < 112; i++) {
		for (int j = 0; j < 13; j++) {
			if (jjLayers[4].tileGet(j, i) == 28)
				jjLayers[4].tileSet(j, i, tileOffset + 7);
		}
	}
	for (int i = 23; i < 27; i++) {
		jjLayers[4].tileSet(i, 135 - i, tileOffset + 8);
		jjLayers[4].tileSet(i, 136 - i, tileOffset + 9);
	}
	for (int layer = 3; layer < 5; layer++) {
		for (int i = 0; i < jjLayers[layer].height - 1; i++) {
			for (int j = 0; j < jjLayers[layer].width; j++) {
				if (jjLayers[layer].tileGet(j, i) == 8 && jjLayers[layer].tileGet(j, i + 1) == 39)
					jjLayers[layer].tileSet(j, i, tileOffset + 1);
				else if (jjLayers[layer].tileGet(j, i) == 29 && jjLayers[layer].tileGet(j, i + 1) == 18)
					jjLayers[layer].tileSet(j, i, tileOffset + 2);
				else if (jjLayers[layer].tileGet(j, i) == 9 && jjLayers[layer].tileGet(j, i + 1) == 38)
					jjLayers[layer].tileSet(j, i, tileOffset + 3);
				else if (jjLayers[layer].tileGet(j, i) == 28 && jjLayers[layer].tileGet(j, i + 1) == 19)
					jjLayers[layer].tileSet(j, i, tileOffset + 4);
				else if (jjLayers[layer].tileGet(j, i) == 9 && jjLayers[layer].tileGet(j, i + 1) == 15)
					jjLayers[layer].tileSet(j, i, tileOffset + 5);
				else if (jjLayers[layer].tileGet(j, i) == 3 && jjLayers[layer].tileGet(j, i + 1) == 79)
					jjLayers[layer].tileSet(j, i, tileOffset + 6);
			}
		}
	}
	jjLayers[4].tileSet(93, 83, tileOffset + 6);
	/*array<uint8> mapping(256);
	for (uint i = 0; i < tilesToTurnOrange.length(); i++) {
		jjPIXELMAP tile(tilesToTurnOrange[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 = jjPalette.color[pixel];
						int hue = color.getHue();
						int sat = color.getSat();
						int light = color.getLight();
						if (i > 2 && k <= 16) color.setHSL(hue + 100, sat, light + 10);
						else if (i < 2) color.setHSL(hue + 100, sat, light + 10);
						mapping[pixel] = jjPalette.findNearestColor(color);
					}
					tile[k, j] = mapping[pixel];
				}
			}
		}
		tile.save(tilesToReplace[i]);
		jjMASKMAP(tilesToTurnOrange[i]).save(tilesToReplace[i], true);
	}*/
	generateCustomSpringSprites(jjAnimSets[ANIM::CUSTOM[3]], array<uint> = {40});
	turnIntoCustomSpring(jjObjectPresets[OBJECT::FROZENSPRING], 0, 21.f, false);
	
	se::firework.loadAnims(jjAnimSets[ANIM::CUSTOM[2]]);
	se::firework.loadSamples(array<SOUND::Sample> = {SOUND::INTRO_BOEM1, SOUND::INTRO_BOEM2});
	se::firework.setAsWeapon(3, weaponHook);
	
	jjAnimSets[ANIM::CUSTOM[1]].load(0, "Meteor.j2a");

	jjAnimations[jjAnimSets[ANIM::AMMO] + 24] = jjAnimations[jjAnimSets[ANIM::CUSTOM[1]] + 1];
	jjAnimations[jjAnimSets[ANIM::AMMO] + 25] = jjAnimations[jjAnimSets[ANIM::CUSTOM[1]] + 2];
	
	jjObjectPresets[OBJECT::BOUNCERBULLET].behavior = jjObjectPresets[OBJECT::BOUNCERBULLETPU].behavior = Meteor();
	jjObjectPresets[OBJECT::BOUNCERBULLET].special = jjObjectPresets[OBJECT::BOUNCERBULLET].determineCurAnim(ANIM::CUSTOM[1], 1);
	jjObjectPresets[OBJECT::BOUNCERBULLETPU].special = jjObjectPresets[OBJECT::BOUNCERBULLETPU].determineCurAnim(ANIM::CUSTOM[1], 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[1], 3);
	jjObjectPresets[OBJECT::BOUNCERAMMO15].determineCurFrame();
	
	jjObjectPresets[OBJECT::BOUNCERPOWERUP].determineCurAnim(ANIM::CUSTOM[1], 4);
	jjObjectPresets[OBJECT::BOUNCERPOWERUP].determineCurFrame();
	
	jjWeapons[WEAPON::BOUNCER].defaultSample = false;
	jjWeapons[WEAPON::BOUNCER].style = WEAPON::MISSILE;
	
	jjSampleLoad(SOUND::ORANGE_BOEMR, "expmine.wav");
}
void onLevelBegin() {
	for (int i = 0; i < jjObjectCount; i++) {
		jjOBJ@ obj = jjObjects[i];
		if (obj.eventID == OBJECT::GENERATOR) {
			int x = int(obj.xOrg) >> 5;
			int y = (int(obj.yOrg) >> 5) - 1;
			if (x >= 0 && y >= 0 && x < jjLayers[4].width && y < jjLayers[4].height && jjEventGet(x, y) == AREA::PATH)
				obj.xPos += 14.f;
		}
	}
}

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[1], 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[1], 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[1] : 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[1], 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;
			}
			
		}
		
	}
}

void onMain() {
	for (int i = 0; i < 1024; i++) {
		jjPARTICLE@ particle = jjParticles[i];
		if (particle.type == PARTICLE::FIRE) {
			particle.fire.size = particle.fire.color == 40? 3:2;
		}
	}
}


void onPlayer(jjPLAYER@ player) {
	if (jjEventGet(int(player.xPos) >> 5, int(player.yPos) >> 5) == AREA::SUCKERTUBE)
		player.alreadyDoubleJumped = false;
	bool previousSpecialMove = specialMove[player.localPlayerID];
	bool currentSpecialMove = player.specialMove != 0 && jjCharacters[player.charCurr].groundJump != GROUND::JAZZ;
	if (currentSpecialMove ^^ previousSpecialMove) {
		for (uint i = 0; i < oneWays.length(); i++) {
			const Point@ tile = oneWays[i];
			jjEventSet(tile.x, tile.y, currentSpecialMove ? 0 : AREA::ONEWAY);
		}
		specialMove[player.localPlayerID] = currentSpecialMove;
	}
	weaponHook.processPlayer(player);
}
void onPlayerInput(jjPLAYER@ player) {
	weaponHook.processPlayerInput(player);
}
void onReceive(jjSTREAM &in packet, int clientID) {
	weaponHook.processPacket(packet, clientID);
}
void onDrawLayer8(jjPLAYER@ player, jjCANVAS@) {
	int y = 30 - int(player.cameraY * jjLayers[6].ySpeed / 32.f);
	if (layer7Row != y) {
		while (layer7Row > y) {
			layer7Row--;
			for (int i = 0; i < jjLayers[7].widthReal; i++) {
				int realRow = (layer7Row + jjLayers[7].height) % jjLayers[7].height;
				jjLayers[7].tileSet(i, realRow, 0);
			}
		}
		while (layer7Row < y) {
			for (int i = 0; i < jjLayers[7].widthReal; i++) {
				int realRow = (layer7Row + jjLayers[7].height) % jjLayers[7].height;
				jjLayers[7].tileSet(i, realRow, layer7Backup.tileGet(i, realRow));
			}
			layer7Row++;
		}
	}
}