Downloads containing ab20ctf03.j2as

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

File preview

#pragma require "SEroller.asc"
#pragma require "SEminimirv.asc"
#pragma require "SEse.dat"
#pragma require "SEse.j2a"
#pragma require "SEse.j2t"
#include "SEroller.asc"
#include "SEminimirv.asc"
funcdef int LanternFrameGetter(int, int, int);
enum CustomAnimation {
	animBanner,
	animLantern,
	animGarland,
	animGrass,
	animRoller,
	animMiniMirv
}
class Catenary {
	private double xt, yt, a;
	Catenary(double s, double x1, double y1, double x2, double y2) {
		double h = x2 - x1;
		double v = y1 - y2;
		double g = sqrt(sqrt(s * s - v * v) / h - 1.0);
		double b = 0.204124145232 / g;
		double p = 0.0;
		while (abs(p - b) > 1e-5) {
			p = b;
			double c = b * 2.0;
			double r = 1.0 / c;
			double t = sinh(r);
			double d = c * t - 1.0;
			b -= d * (1.0 - sqrt(d) / g) / (r * cosh(r) - t);
		}
		a = b * h;
		double e = 2.718281828459 ** (1.0 / b);
		double m = e - 1.0;
		double ve = v * e;
		double am = a * m;
		double ame = am * e;
		double shift = a * log((ve + sqrt(am * ame + ve * ve)) / ame);
		xt = x1 - shift;
		yt = y1 + a * cosh(shift / a);
	}
	double getX() const {
		return xt;
	}
	double getY() const {
		return yt;
	}
	double getA() const {
		return a;
	}
	double getLength(double x1, double x2) const {
		return a * (sinh((x2 - xt) / a) - sinh((x1 - xt) / a));
	}
	double getStep(double x, double s) const {
		double z = s / a + sinh((x - xt) / a);
		return xt + log(z + sqrt(z * z + 1)) * a;
	}
	double opCall(double x) const {
		return yt - a * cosh((x - xt) / a);
	}
}
class Banner {
	private int x, y, frame;
	private float angle, speed;
	private WindPollingPoint@ wind;
	Banner(int xPos, int yPos, int anim, WindPollingPoint@ windPoint) {
		x = xPos;
		y = yPos;
		frame = jjAnimations[anim];
		angle = speed = 0.f;
		@wind = windPoint;
	}
	void draw(jjCANVAS@ canvas) {
		canvas.drawRotatedSpriteFromCurFrame(x, y, frame, int(angle));
	}
	void process() {
		float xWind, yWind;
		wind.poll(xWind, yWind);
		yWind += 3.f;
		float xPoint = jjSin(int(angle)), yPoint = jjCos(int(angle));
		float torque = (yPoint * xWind - xPoint * yWind) * 0.2f;
		speed += torque;
		speed *= 0.99f;
		angle += speed;
	}
}
class Lantern {
	private uint8 palshift;
	private int x, y, type;
	private float angle, speed, dist;
	private jjANIMATION@ anim, lightAnim;
	private WindPollingPoint@ wind;
	Lantern(double xPos, double yPos, uint sprite, uint lightSprite, float lightDistance, int color, WindPollingPoint@ windPoint) {
		const array<uint8> palshifts = {24, 8, 24, 72, 0};
		x = int(xPos);
		y = int(yPos + 1.5);
		angle = speed = 0.f;
		@anim = jjAnimations[sprite];
		@lightAnim = jjAnimations[lightSprite];
		type = color % palshifts.length();
		palshift = palshifts[type];
		@wind = windPoint;
		dist = lightDistance;
	}
	void draw(jjCANVAS@ canvas, int order, int row, LanternFrameGetter@ getFrame) const {
		int frame = getFrame(type, order, row);
		int intAngle = int(angle);
		if (!jjLowDetail && jjColorDepth != 8 && frame != 0) {
			int s = int(jjSin(intAngle) * dist);
			int c = int(jjCos(intAngle) * dist);
			canvas.drawSpriteFromCurFrame(x + s, y + c, lightAnim + frame, 0, SPRITE::ALPHAMAP, palshift + 16);
		}
		canvas.drawRotatedSpriteFromCurFrame(x, y, anim + frame, intAngle, 1.f, 1.f, SPRITE::PALSHIFT, palshift);
	}
	void process() {
		float xWind, yWind;
		wind.poll(xWind, yWind);
		yWind += 0.5f;
		float xPoint = jjSin(int(angle)), yPoint = jjCos(int(angle));
		float torque = (yPoint * xWind - xPoint * yWind) * 0.8f;
		speed += torque;
		speed *= 0.98f;
		angle += speed;
		angle %= 1024.f;
	}
}
class Garland {
	private Catenary@ arc;
	private double left, right, top, bottom;
	private uint sprite;
	private array<Lantern@> lanterns;
	Garland(int x1, int y1, int x2, int y2, double lengthMultiplier, double lanternSpacing, uint lanternSprite, WindManager@ wind) {
		double xl = x1 << 5 | 16;
		double xr = x2 << 5 | 15;
		double yl = y1 << 5 | 16;
		double yr = y2 << 5 | 16;
		double width = xr - xl;
		double height = yr - yl;
		double length = sqrt(width * width + height * height) * lengthMultiplier;
		@arc = Catenary(length, xl, yl, xr, yr);
		left = xl + 8.0;
		right = xr - 8.0;
		top = yl < yr ? yl : yr;
		double x = arc.getX();
		bottom = x < left ? left : x > right ? right : arc(x);
		int segments = int(length / lanternSpacing);
		double segment = length / segments;
		double lanternX = xl;
		for (int i = 1; i < segments; i++) {
			lanternX = arc.getStep(lanternX, segment);
			int m = i - 1;
			if (segments - i - 1 < m)
				m = segments - i - 1;
			double lanternY = arc(lanternX);
			int anim = getLanternSprite(i, segments, lanternX);
			float dist = 14.f + anim * 4;
			lanterns.insertLast(Lantern(lanternX, lanternY, lanternSprite + anim, lanternSprite + 2, dist, m % 9, wind.getPollingPoint(lanternX, lanternY)));
		}
	}
	private int getLanternSprite(int id, int count, double x) const {
		if (count & 1 != 0 && x > jjLayers[4].width << 4)
			id = ~id;
		return id & 1;
	}
	void draw(const jjPLAYER@ player, jjCANVAS@ canvas, int order, int row, LanternFrameGetter@ getFrame) const {
		const double tolerance = 48.0;
		if (sprite != 0 && player.cameraX < right + tolerance && player.cameraY < bottom + tolerance && player.cameraX + jjSubscreenWidth > left - tolerance && player.cameraY + jjSubscreenHeight > top - tolerance) {
			canvas.drawSpriteFromCurFrame(int(left), int(top), sprite, 0, SPRITE::ALPHAMAP, 127);
			for (int i = lanterns.length(); i-- != 0;) {
				lanterns[i].draw(canvas, order, row, getFrame);
			}
		}
	}
	void process() {
		for (int i = lanterns.length(); i-- != 0;) {
			lanterns[i].process();
		}
	}
	void generateSprite(uint frame) {
		const int iterationCount = 20;
		const double iterationCountD = iterationCount;
		int64 l = int64(left + 0.5);
		int64 r = int64(right + 0.5);
		int64 t = int64(top + 0.5);
		int64 b = int64(bottom + 0.5);
		int width = r - l + 1;
		int height = b - t + 2;
		jjPIXELMAP image(width, height);
		array<double> opacity(height);
		double verticalShift = 0.5 - t;
		for (int i = 0; i < width; i++) {
			int highest = height, lowest = 0;
			for (int j = 0; j < iterationCount; j++) {
				double value = arc(l + i + j / iterationCountD) + verticalShift;
				int cur = int64(value);
				double frac = value % 1.0;
				opacity[cur] += 1.0 - frac;
				opacity[cur + 1] += frac;
				if (cur < highest)
					highest = cur;
				if (cur > lowest)
					lowest = cur;
			}
			lowest += 2;
			double mul = (lowest - highest) * (127.5 / iterationCountD);
			for (int j = highest; j < lowest; j++) {
				int color = int(opacity[j] * mul);
				image[i, j] = color < 256 ? color : 255;
				opacity[j] = 0.0;
			}
		}
		if (image.save(jjAnimFrames[frame]))
			sprite = frame;
	}
}
class Grass {
	private int x, y, angle;
	private uint frame;
	private WindPollingPoint@ wind;
	Grass(int xPos, int yPos, uint sprite, int a, WindPollingPoint@ windPoint) {
		x = xPos;
		y = yPos;
		frame = sprite;
		angle = a;
		@wind = windPoint;
	}
	void draw(jjCANVAS@ canvas, uint frameShift) const {
		float xWind, yWind;
		wind.poll(xWind, yWind);
		int mod = int(abs(xWind) * 4.f);
		canvas.drawRotatedSpriteFromCurFrame(x, y, frame + frameShift, angle - int(xWind * 128.f) + jjRandom() % (mod * 2 + 1) - mod);
	}
}
class GrassChunk {
	private array<Grass@> grass;
	void draw(jjCANVAS@ canvas, uint frameShift) const {
		for (int i = grass.length(); i-- != 0;) {
			grass[i].draw(canvas, frameShift);
		}
	}
	void insert(int x, int y, uint sprite, int angle, const WindManager@ windManager) {
		grass.insertLast(Grass(x, y, sprite, angle, windManager.getPollingPoint(x, y)));
	}
	void shuffle(jjRNG& random) {
		for (uint i = grass.length(); i > 1;) {
			uint j = random() % i;
			i--;
			Grass@ temp = grass[i];
			@grass[i] = grass[j];
			@grass[j] = temp;
		}
	}
}
class WindChunk {
	float xPrev, yPrev, x, y, xNext, yNext;
}
class WindDataPoint {
	private const WindChunk@ chunk;
	private float proximity;
	WindDataPoint(const WindChunk@ src, float srcProximity) {
		@chunk = src;
		proximity = srcProximity;
	}
	float getX() const {
		return chunk.x * proximity;
	}
	float getXPrev() const {
		return chunk.xPrev * proximity;
	}
	float getY() const {
		return chunk.y * proximity;
	}
	float getYPrev() const {
		return chunk.yPrev * proximity;
	}
}
class WindPollingPoint {
	private array<const WindDataPoint@> dataPoints;
	private const WindManager@ manager;
	private uint periodNumber;
	private float xPrev, x, yPrev, y;
	WindPollingPoint(const WindManager@ windManager, const array<const WindDataPoint@> &in points) {
		dataPoints = points;
		@manager = windManager;
		periodNumber = ~1;
	}
	private void cachePoll(float &out xOut, float &out yOut) const {
		float progress = manager.getProgress();
		xOut = xPrev + (x - xPrev) * progress;
		yOut = yPrev + (y - yPrev) * progress;
	}
	void poll(float &out xOut, float &out yOut) {
		uint curPeriod = manager.getPeriodNumber();
		if (curPeriod != periodNumber) {
			if (curPeriod == periodNumber + 1) {
				xPrev = x;
				yPrev = y;
				x = y = 0.f;
				for (int i = dataPoints.length(); i-- != 0;) {
					const WindDataPoint@ point = dataPoints[i];
					x += point.getX();
					y += point.getY();
				}
			} else {
				xPrev = x = yPrev = y = 0.f;
				for (int i = dataPoints.length(); i-- != 0;) {
					const WindDataPoint@ point = dataPoints[i];
					xPrev += point.getXPrev();
					x += point.getX();
					yPrev += point.getYPrev();
					y += point.getY();
				}
			}
			periodNumber = curPeriod;
		}
		cachePoll(xOut, yOut);
	}
}
class WindManager {
	private jjRNG random;
	private array<array<WindChunk>> winds;
	private uint chunkSize, period, periodNumber, counter;
	private int chunkRows, chunkColumns, chunkCount;
	private float chunkSizeInPixels, persistence, volatility, influence, explosiveness, progress, sqrtHalf = sqrt(0.5f);
	private double randMul = 2.0 ** -63;
	WindManager(uint64 seed, uint windChunkSize, uint windPeriod, float windPersistence, float windVolatility, float windInfluence, float windExplosiveness) {
		random.seed(seed);
		chunkSize = windChunkSize;
		chunkSizeInPixels = chunkSize << 5;
		period = windPeriod;
		counter = periodNumber = 0;
		progress = 0.f;
		persistence = windPersistence;
		volatility = windVolatility;
		influence = windInfluence;
		explosiveness = windExplosiveness;
		winds.resize(jjLayers[4].height / chunkSize + 2);
		for (int i = winds.length(); i-- != 0;) {
			winds[i].resize(jjLayers[4].width / chunkSize + 2);
		}
		chunkRows = winds.length();
		chunkColumns = winds[0].length();
		chunkCount = chunkRows * chunkColumns;
	}
	void process() {
		int left = counter * chunkCount / period;
		counter++;
		int right = counter * chunkCount / period;
		for (int i = left; i < right; i++) {
			int y = i % chunkRows;
			int x = i / chunkRows;
			WindChunk@ chunk = winds[y][x];
			chunk.xNext = chunk.x * persistence + float(int64(random()) * randMul) * volatility;
			chunk.yNext = chunk.y * persistence + float(int64(random()) * randMul) * volatility;
			for (int j = -1; j <= 1; j++) {
				int yOther = y + j;
				if (yOther >= 0 && yOther < chunkRows) {
					for (int k = -1; k <= 1; k++) {
						if (j != 0 || k != 0) {
							int xOther = x + k;
							if (xOther >= 0 && xOther < chunkColumns) {
								const WindChunk@ other = winds[yOther][xOther];
								float dot = other.x * k + other.y * j;
								dot *= dot > 0.f ? explosiveness : influence;
								if (j != 0 && k != 0)
									dot *= sqrtHalf;
								chunk.xNext -= other.x * dot;
								chunk.yNext -= other.y * dot;
							}
						}
					}
				}
			}
			float sum = chunk.xNext * chunk.xNext + chunk.yNext * chunk.yNext;
			if (sum > 1.f) {
				sum = sqrt(sum);
				chunk.xNext /= sum;
				chunk.yNext /= sum;
			}
		}
		if (counter == period) {
			counter = 0;
			periodNumber++;
			for (int i = chunkRows; i-- != 0;) {
				for (int j = chunkColumns; j-- != 0;) {
					WindChunk@ chunk = winds[i][j];
					chunk.xPrev = chunk.x;
					chunk.yPrev = chunk.y;
					chunk.x = chunk.xNext;
					chunk.y = chunk.yNext;
				}
			}
		}
		progress = float(counter) / float(period);
	}
	WindPollingPoint@ getPollingPoint(float x, float y) const {
		float leftF = x / chunkSizeInPixels + 0.5f;
		float topF = y / chunkSizeInPixels + 0.5f;
		int left = int(leftF);
		int top = int(topF);
		array<const WindDataPoint@> dataPoints(4);
		for (int i = 0; i < 2; i++) {
			int row = top + i;
			float rowDist = 1.f - abs(row - topF);
			for (int j = 0; j < 2; j++) {
				int col = left + j;
				float colDist = 1.f - abs(col - leftF);
				@dataPoints[i << 1 | j] = WindDataPoint(winds[row][col], rowDist * colDist);
			}
		}
		return WindPollingPoint(this, dataPoints);
	}
	float getProgress() const {
		return progress;
	}
	uint getPeriodNumber() const {
		return periodNumber;
	}
}
class Decorator {
	private array<Banner@> banners;
	private array<Garland@> garlands;
	private uint lanternSprite;
	private array<array<GrassChunk>> grassChunks;
	private WindManager@ windManager;
	void draw(const jjPLAYER@ player, jjCANVAS@ canvas, int order, int row) const {
		{
			int left = (int(player.cameraX) - 32) >>> 8;
			int top = (int(player.cameraY) - 32) >>> 8;
			int right = (int(player.cameraX) + jjSubscreenWidth + 32) >>> 8;
			int bottom = (int(player.cameraY) + jjSubscreenHeight + 32) >>> 8;
			if (left < 0)
				left = 0;
			if (top < 0)
				top = 0;
			if (right >= int(grassChunks[0].length()))
				right = grassChunks[0].length() - 1;
			if (bottom >= int(grassChunks.length()))
				bottom = grassChunks.length() - 1;
			for (int k = jjLowDetail ? 1 : 2; k-- != 0;) {
				for (int i = top; i <= bottom; i++) {
					for (int j = left; j <= right; j++) {
						grassChunks[i][j].draw(canvas, k);
					}
				}
			}
		}
		LanternFrameGetter@ getFrame = lanternPatternDigitalRitual;
		string music = lowercase(jjMusicFileName);
		if (music == "yukatan.it")
			@getFrame = lanternPatternYukatan;
		else if (music == "digital ritual.mo3")
			@getFrame = lanternPatternDigitalRitual;
		for (int i = garlands.length(); i-- != 0;) {
			garlands[i].draw(player, canvas, order, row, getFrame);
		}
	}
	void drawForeground(jjCANVAS@ canvas) const {
		for (int i = banners.length(); i-- != 0;) {
			banners[i].draw(canvas);
		}
	}
	void createLanternSprites() {
		const array<int> tiles = {566, 570};
		const int frameCount = 6;
		lanternSprite = jjAnimSets[ANIM::CUSTOM[animLantern]].allocate(array<uint>(3, frameCount));
		for (int i = 0; i < 2; i++) {
			jjPIXELMAP src(tiles[i]), dest;
			int dark = 255, bright = 0;
			for (int j = 0; j < 32; j++) {
				for (int k = 0; k < 32; k++) {
					uint8 pixel = src[k, j];
					if (pixel != 0) {
						int light = jjPalette.color[pixel].getLight();
						if (light > bright)
							bright = light;
						if (light < dark)
							dark = light;
					}
				}
			}
			int range = bright - dark + 1;
			uint anim = jjAnimations[lanternSprite + i];
			for (int l = 0; l < frameCount; l++) {
				for (int j = 0; j < 32; j++) {
					for (int k = 0; k < 32; k++) {
						uint8 pixel = src[k, j];
						if (pixel != 0)
							dest[k, j] = 7 - (jjPalette.color[pixel].getLight() - dark) * (l + (9 - frameCount)) / range | 16;
					}
				}
				dest.save(jjAnimFrames[anim + l]);
				jjAnimFrames[anim + l].hotSpotX = -15;
				jjAnimFrames[anim + l].hotSpotY = 0;
			}
		}
		{
			const int size = 40;
			const float halfSizeF = (size - 1) * 0.5f;
			jjPIXELMAP src(size, size), dest(size, size);
			for (int j = 0; j < size; j++) {
				float dy = (halfSizeF - j) / halfSizeF;
				float dy2 = dy * dy;
				for (int k = 0; k < size; k++) {
					float dx = (halfSizeF - k) / halfSizeF;
					float dx2 = dx * dx;
					float value = 1.f - sqrt(dx2 + dy2);
					if (value > 0.f) {
						src[k, j] = int(value * 255.f);
					}
				}
			}
			uint anim = jjAnimations[lanternSprite + 2];
			for (int l = 0; l < frameCount; l++) {
				for (int j = 0; j < size; j++) {
					for (int k = 0; k < size; k++) {
						dest[k, j] = src[k, j] * l / frameCount;
					}
				}
				dest.save(jjAnimFrames[anim + l]);
				jjAnimFrames[anim + l].hotSpotX = 1 - size / 2;
				jjAnimFrames[anim + l].hotSpotY = -size / 2;
			}
		}
	}
	void hangGarlands() {
		array<int> x(64, -1), y(64, -1);
		for (int i = 0; i < jjLayers[4].width; i++) {
			for (int j = 0; j < jjLayers[4].height; j++) {
				if (jjEventGet(i, j) == AREA::PATH) {
					int id = jjParameterGet(i, j, 0, 6);
					if (x[id] != -1 && x[id] != i)
						garlands.insertLast(Garland(x[id], y[id], i, j, 1.05, 50.0, lanternSprite, windManager));
					x[id] = i;
					y[id] = j;
					jjEventSet(i, j, 0);
				}
			}
		}
		int count = garlands.length();
		jjANIMSET@ animSet = jjAnimSets[ANIM::CUSTOM[animGarland]];
		animSet.allocate(array<uint> = {count});
		jjANIMATION@ anim = jjAnimations[animSet];
		for (int i = 0; i < count; i++) {
			garlands[i].generateSprite(anim + i);
		}
	}
	void loadGrassSprites() {
		jjAnimSets[ANIM::CUSTOM[animGrass]].load(0, "SEse.j2a");
	}
	void plantGrass() {
		const uint totalGrassAnims = 3;
		int height = ((jjLayers[4].height - 1) >> 3) + 1;
		int width = ((jjLayers[4].width - 1) >> 3) + 1;
		grassChunks.resize(height);
		for (int i = height; i-- != 0;) {
			grassChunks[i].resize(width);
		}
		array<uint16> grassTiles = {
			1, 2, 3, 6, 7, 16, 17, 19, 26, 27, 28, 29, 30, 36, 37, 38, 39,
			70, 71, 74, 79, 94, 105, 106, 110, 111, 114, 138, 141, 143,
			360, 362, 365, 366, 385, 386, 387, 388, 482, 489, 510, 517
		};
		uint8 grassColor = jjPIXELMAP(2)[16, 2];
		uint animSet = jjAnimSets[ANIM::CUSTOM[animGrass]];
		jjRNG random;
		for (int i = 0; i < jjLayers[4].height; i++) {
			int top = i << 5;
			for (int j = 0; j < jjLayers[4].width; j++) {
				uint16 tile = jjLayers[4].tileGet(j, i);
				if (grassTiles.find(tile) >= 0 && (i == 0 || jjLayers[3].tileGet(j, i) == 0)) {
					int left = j << 5;
					jjPIXELMAP src(tile);
					for (int k = j % 3; k < 32; k += 3) {
						for (int l = 0; l < 32; l++) {
							if (src[k, l] == grassColor) {
								int x = left + k;
								int y = top + l;
								int anim = random() % totalGrassAnims;
								grassChunks[y >>> 8][x >>> 8].insert(x, y, jjAnimations[animSet + anim], random() % 65 - 32, windManager);
								break;
							}
						}
					}
				}
			}
		}
		for (int i = height; i-- != 0;) {
			for (int j = width; j-- != 0;) {
				grassChunks[i][j].shuffle(random);
			}
		}
	}
	void createBannerSprites() {
		const array<int> tiles = {0, 10, 10, 20};
		jjANIMSET@ animSet = jjAnimSets[ANIM::CUSTOM[animBanner]];
		animSet.allocate(array<uint> = {1, 1});
		for (int i = 2; i-- != 0;) {
			jjPIXELMAP dest(32, 128);
			for (int j = 4; j-- != 0;) {
				jjPIXELMAP src(538 + i + tiles[j]);
				int offset = j << 5;
				for (int k = 32; k-- != 0;) {
					for (int l = 32; l-- != 0;) {
						dest[l, k | offset] = src[l, k];
					}
				}
			}
			jjANIMFRAME@ frame = jjAnimFrames[jjAnimations[animSet + i]];
			dest.save(frame);
			frame.hotSpotX = -16;
			frame.hotSpotY = -2;
		}
	}
	void hangBanners() {
		uint animSet = jjAnimSets[ANIM::CUSTOM[animBanner]];
		jjLAYER@ layer = jjLayers[3];
		for (int i = layer.height; i-- != 0;) {
			for (int j = layer.width; j-- != 0;) {
				int tile = layer.tileGet(j, i);
				if (tile | 1 == 539) {
					layer.generateSettableTileArea(j, i, 1, 4);
					for (int k = 4; k-- != 0;) {
						layer.tileSet(j, i + k, 0);
					}
					int x = j << 5 | 16, y = i << 5 | 5;
					banners.insertLast(Banner(x, y, animSet + (tile & 1), windManager.getPollingPoint(x, y)));
				}
			}
		}
	}
	void prepareWind(uint64 seed, uint chunkSize, uint period, float persistence, float volatility, float influence, float explosiveness) {
		@windManager = WindManager(seed, chunkSize, period, persistence, volatility, influence, explosiveness);
	}
	void processWind() const {
		windManager.process();
		for (int i = garlands.length(); i-- != 0;) {
			garlands[i].process();
		}
		for (int i = banners.length(); i-- != 0;) {
			banners[i].process();
		}
	}
}
Decorator decorator;
se::DefaultWeaponHook weaponHook;
string lowercase(string &in s) {
	for (int i = s.length(); i-- != 0;) {
		if (s[i] > 64 && s[i] < 91)
			s[i] ^= 32;
	}
	return s;
}
int lanternPatternDigitalRitual(int type, int order, int row) {
	switch (type) {
		case 0:
			if (order >= 14 && order < 30 || order >= 32 && order < 36 || order >= 50 && order < 66) {
				if (row < 4)
					return 5 - row;
				if (row >= 4 && row < 9)
					return 9 - row;
				if (row >= 16 && row < 21)
					return 21 - row;
				if (order & 1 != 0) {
					if (order == 25 || order == 35 || order == 61 || order == 65) {
						if (row >= 32 && row < 36)
							return 37 - row;
						if (row >= 36 && row < 41)
							return 41 - row;
						if (row >= 48 && row < 53)
							return 53 - row;
					} else if (order < 29 || order >= 50) {
						if (row >= 48 && row < 52)
							return 53 - row;
						if (row >= 52 && row < 57)
							return 57 - row;
					}
				} else if (order >= 22 && order < 28 || order == 34 || order >= 58) {
					if (row >= 40 && row < 45)
						return 45 - row;
					if (row >= 48 && row < 53)
						return 53 - row;
				}
			} else if (order == 66 && row < 5) {
				return 5 - row;
			}
			break;
		case 1:
			if (order >= 14 && order < 30 || order >= 32 && order < 36 || order >= 50 && order < 66) {
				if (row & ~3 != 20 && row & ~3 != 32 && (row < 52 || order != 25 && order != 29 && order != 35 && order != 61 && order != 65))
					return 5 - (row & 3);
			} else if (order == 30 || order == 31) {
				if (row < 16)
					return 5 - (row & 3);
			} else if (order >= 36 && order < 40) {
				if (row < 5)
					return 5 - row;
				if (row >= 8 && row < 13)
					return 13 - row;
				if (row >= 24 && row < 29)
					return 29 - row;
				if (row >= 28 && row < 33)
					return 33 - row;
				if (row >= 52)
					return 5 - (row & 3);
			} else if (order >= 40 && order < 48) {
				if (row < 5)
					return 5 - row;
				if (row >= 8 && row < 13)
					return 13 - row;
				if (row >= 28 && row < 33)
					return 33 - row;
				if (row >= 32 && row < 37)
					return 37 - row;
				if (row >= 40 && row < 45)
					return 45 - row;
				if (order & 1 != 0 && row >= 56 || order == 47 && row >= 44)
					return 5 - (row & 3);
			} else if (order >= 66) {
				return 0;
			} else if (row < 32) {
				if ((row >> 2) % 3 != 2)
					return 5 - (row & 3);
			} else if (order == 3 || order == 7 || order == 9 || order == 11 || order == 13 || order == 49) {
				if (row >= 48 && row & ~3 != 52)
					return 5 - (row & 3);
			} else if (order == 5) {
				if (row >= 48 && row < 53)
					return 53 - row;
			} else if (order == 10 || order == 12) {
				if (row >= 36 && row < 41)
					return 41 - row;
				if (row >= 44 && row < 48)
					return 49 - row;
				if (row >= 48 && row < 53)
					return 53 - row;
				if (row >= 56 && row < 61)
					return 61 - row;
			} else if (order == 48) {
				if (row & ~3 != 32 && row & ~3 != 56)
					return 5 - (row & 3);
			}
			break;
		case 2:
			if (order >= 14 && order < 30 || order >= 32 && order < 36 || order >= 50 && order < 66) {
				if (row >= 8 && row < 13)
					return 13 - row;
				if (row >= 12 && row < 17)
					return 17 - row;
				if (order & 1 != 0) {
					if (order == 25 || order == 35 || order == 61 || order == 65) {
						if (row >= 24 && row < 28)
							return 29 - row;
						if (row >= 28 && row < 33)
							return 33 - row;
						if (row >= 40 && row < 45)
							return 45 - row;
					} else if (order < 29 || order >= 50) {
						if (row >= 36 && row < 40)
							return 41 - row;
						if (row >= 40 && row < 44)
							return 45 - row;
						if (row >= 44 && row < 49)
							return 49 - row;
						if (row >= 56 && row < 60)
							return 61 - row;
						if (row >= 60 && row < 65)
							return 65 - row;
					}
				} else if (order >= 22 && order < 28 || order == 34 || order >= 58) {
					if (row >= 36 && row < 41)
						return 41 - row;
					if (row >= 44 && row < 49)
						return 49 - row;
					if (row >= 52 && row < 57)
						return 57 - row;
				}
			} else if (order == 66 && row < 5) {
				return 5 - row;
			}
			break;
		case 3:
			if (order >= 2 && order < 14 || order == 48 || order == 49) {
				if (order >= 6) {
					if (row >= 20 && row < 25)
						return 25 - row;
					if (row >= 56 && row < 60)
						return 61 - row;
				}
				if (row >= 44 && row < 48)
					return 49 - row;
				if (row >= 48 && row < 53)
					return 53 - row;
				if (order >= 5 && row >= 60 && row < 65)
					return 65 - row;
			} else if (order >= 18 && order < 36) {
				if (order >= 26) {
					if (row >= 48 && row < 50)
						return 53 - row;
					if (row >= 50 && row < 54)
						return 55 - row;
					if (row >= 54 && row < 59)
						return 59 - row;
					if (row >= 60 && row < 65)
						return 65 - row;
				}
				if (order >= 22 && row >= 20 && row < 25)
					return 25 - row;
				if (row >= 44 && row < 49)
					return 49 - row;
			} else if (order >= 40 && order < 48) {
				if (row >= 16 && row < 21)
					return 21 - row;
				if (row >= 32 && row < 37)
					return 37 - row;
				if (row >= 44 && row < 49)
					return 49 - row;
				if (row >= 60 && row < 65)
					return 65 - row;
			} else if (order >= 54 && order != 57 && order < 66) {
				if (row >= 44 && row < 48)
					return 49 - row;
			}
			break;
		case 4:
			if (order == 1 || order == 4) {
				if (row >= 22 && row < 32)
					return (row - 20) >> 1;
				if (row >= 32 && row < 42)
					return (43 - row) >> 1;
				if (row >= 44 && row < 50)
					return (51 - row) >> 1;
				if (row >= 52 && row < 60)
					return (61 - row) >> 1;
			} else if (order == 5 || order == 13 || order == 49) {
				if (row >= 48 && row < 53)
					return 53 - row;
			} else if (order == 32) {
				if (row < 4)
					return 4 - row;
			} else if (order == 66) {
				if (row >= 14 && row < 22)
					return (25 - row) >> 1;
				if (row >= 22 && row < 32)
					return (33 - row) >> 1;
			}
			break;
	}
	return 0;
}
int lanternPatternYukatan(int type, int order, int row) {
	switch (type) {
		case 0:
			if (order < 26 || order >= 34) {
				if (row >= 4 && row < 9)
					return 9 - row;
				if (row >= 12 && row < 17)
					return 17 - row;
				if (row >= 24 && row < 29)
					return 29 - row;
				if (row >= 32 && row < 37)
					return 37 - row;
				if (row >= 40 && row < 45)
					return 45 - row;
			}
			break;
		case 1:
			if (order < 52) {
				int beat;
				if (order < 4)
					beat = row & 15;
				else if (order == 5 && row >= 52)
					beat = row - 52;
				else if (order == 13 && row >= 36)
					beat = row & 3 + (row == 40 || row == 52 ? 4 : 0);
				else if (order == 33 && row >= 38)
					beat = row >= 46 && row < 54 ? row - 46 : row >= 62 ? row - 62 : row & 1;
				else if (order >= 34 && order < 46)
					beat = row + 4 & 7;
				else
					beat = row & 7;
				return beat < 5 ? 5 - beat : 0;
			}
			break;
		case 2:
			if (order < 26 || order >= 34) {
				if (row >= 20 && row < 25)
					return 25 - row;
				if (row >= 28 && row < 33)
					return 33 - row;
				if (row >= 48 && row < 53)
					return 53 - row;
				if (row >= 56 && row < 61)
					return 61 - row;
			}
			break;
		case 3:
			if (order >= 6 && order < 42) {
				if (row >= 32 && row < 37)
					return 37 - row;
				if (order >= 7 && order < 20 || order == 25) {
					int power = order < 14 ? 4 : 2;
					if (row < power)
						return power - row;
					if (row >= 8 && row < 8 + power)
						return 8 + power - row;
				}
			}
			break;
		case 4:
			if (order & 3 == 1 && row >= 36 && order != 1 && order != 25) {
				int beat = row >= 52 ? row - 52 : row + 4 & 7;
				return beat < 5 ? 5 - beat : 0;
			}
			if (order & 3 == 2 && row >= 4 && row < 20 && (order < 34 || order >= 46)) {
				int beat = row + 4 & 7;
				return beat < 5 ? 5 - beat : 0;
			}
			break;
	}
	return 0;
}
void fillWaterPools() {
	const array<int> poolFloorTiles = {37, 2, 38};
	const jjLAYER@ foreground = jjLayers[3];
	int width = foreground.width;
	int height = foreground.height;
	jjLAYER waterLayer(width, height);
	waterLayer.xSpeed = foreground.xSpeed;
	waterLayer.ySpeed = foreground.ySpeed;
	waterLayer.spriteMode = SPRITE::TRANSLUCENT;
	int minYForNextGenerate = 0;
	for (int i = 2; i < height; i++) {
		for (int j = 0; j < width; j++) {
			int tile = foreground.tileGet(j, i);
			if (tile != 0) {
				int index = poolFloorTiles.find(tile);
				if (index >= 0) {
					if (i >= minYForNextGenerate) {
						waterLayer.generateSettableTileArea(j, i - 2, width - j, 2);
						minYForNextGenerate = i + 2;
					}
					waterLayer.tileSet(j, i - 2, 243 + index);
					waterLayer.tileSet(j, i - 1, 263 + index);
				}
			}
		}
	}
	array<jjLAYER@>@ order = jjLayerOrderGet();
	order.insertAt(order.findByRef(foreground), waterLayer);
	jjLayerOrderSet(order);
}
void removeSpritePaletteReferences() {
	array<int> mapping(256);
	for (int i = 1; i < 96; i++) {
		jjPALCOLOR color = jjPalette.color[i];
		int best = 0x40000;
		for (int j = 96; j < 245; j++) {
			jjPALCOLOR match = jjPalette.color[j];
			int red = int(match.red) - color.red;
			int green = int(match.green) - color.green;
			int blue = int(match.blue) - color.blue;
			int dist = red * red + green * green + blue * blue;
			if (dist < best) {
				best = dist;
				mapping[i] = j;
			}
		}
	}
	for (int i = 96; i < 256; i++) {
		mapping[i] = i;
	}
	for (uint i = 1; i < jjTileCount; i++) {
		jjPIXELMAP tile(i);
		for (int j = 0; j < 32; j++) {
			for (int k = 0; k < 32; k++) {
				tile[k, j] = mapping[tile[k, j]];
			}		
		}
		tile.save(i, true);
	}
}
void setLevelPalette() {
	jjPAL@ pal = jjPalette;
	pal.reset();
	for (int i = 96; i < 256; i++) {
		pal.color[i].red = pal.color[i].red * 2 / 3;
		pal.color[i].green = pal.color[i].green * 2 / 3;
		if (pal.color[i].red / 2 > pal.color[i].blue)
			pal.color[i].blue = pal.color[i].red / 2;
		if (pal.color[i].green / 2 > pal.color[i].blue)
			pal.color[i].blue = pal.color[i].green / 2;
	}
	pal.color[96] = jjPALCOLOR(228, 226, 244);
	pal.color[120] = jjPALCOLOR(170, 165, 219);
	pal.color[123] = jjPALCOLOR(132, 126, 204);
	pal.color[126] = jjPALCOLOR(109, 102, 194);
	pal.color[138] = jjPALCOLOR(74, 66, 167);
	pal.color[144] = jjPALCOLOR(88, 80, 185);
	pal.color[167] = jjPALCOLOR(65, 57, 147);
	pal.color[200] = jjPALCOLOR(40, 35, 91);
	pal.color[206] = jjPALCOLOR(54, 47, 121);
	pal.color[219] = jjPALCOLOR(26, 23, 59);
	pal.color[242] = jjPALCOLOR(17, 15, 38);
	pal.apply();
}
void adjustMask() {
	const array<int> emptyMasks = {85, 86, 87, 107, 430, 431, 432, 454, 567, 569};
	jjMASKMAP mask;
	for (int i = emptyMasks.length(); i-- != 0;) {
		mask.save(emptyMasks[i], true);
	}
	jjMASKMAP(21).save(380, true);
	jjMASKMAP(23).save(382, true);
}
void addColumnShadow() {
	const array<int> tiles = {606, 758};
	for (int k = tiles.length(); k-- != 0;) {
		jjPIXELMAP tile(tiles[k]);
		for (int i = 32; i-- != 0;) {
			int y = 41 - i;
			if (y > 0) {
				for (int j = 32; j-- != 0;) {
					int x = (14 - j) << 2;
					int d = x * x + y * y;
					if (tile[j, i] != 0 && d > 1040) {
						float alpha = (52.f - sqrt(d)) * 0.05f;
						if (alpha < 0.25f)
							alpha = 0.25f;
						jjPALCOLOR color = jjPalette.color[tile[j, i]];
						color.red = int(color.red * alpha);
						color.green = int(color.green * alpha) >> 1;
						color.blue = int(color.blue * alpha);
						uint8 index = jjPalette.findNearestColor(color);
						if (index == 246)
							index = 47;
						else if (index | 1 == 91)
							index = 112;
						tile[j, i] = index;
					}
				}
			}
		}
		tile.save(tiles[k]);
	}
}
void loadBackground() {
	int start = jjTileCount;
	const string originalTileset = jjTilesetFileName;
	if (jjTilesFromTileset("SEse.j2t", 10, 570)) {
		jjLAYER@ layer = jjLayers[8];
		layer.generateSettableTileArea();
		for (int i = 0; i < layer.height; i++) {
			for (int j = 0; j < layer.width; j++) {
				int tileID = start + i * 10 + j % 10 + j / 10 * 190;
				layer.tileSet(j, i, tileID);
			}
		}
	}
}
void loadCaveLighting() {
	jjSTREAM data("SEse.dat");
	uint16 tileCount;
	if (data.pop(tileCount) && jjTilesFromTileset("SEse.j2t", 580, tileCount)) {
		uint16 left, top, width, height;
		if (data.pop(left) && data.pop(top) && data.pop(width) && data.pop(height)) {
			if (data.getSize() == width * height * 2) {
				jjLAYER@ layer = jjLayers[5];
				layer.generateSettableTileArea(left, top, width, height);
				for (uint i = 0; i < height; i++) {
					for (uint j = 0; j < width; j++) {
						uint16 tileID;
						data.pop(tileID);
						if (tileID != 0)
							layer.tileSet(left + j, top + i, tileID);
					}
				}
			}
		}
	}
}
bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas) {
	return weaponHook.drawAmmo(player, canvas);
}
void onDrawLayer3(jjPLAYER@, jjCANVAS@ canvas) {
	decorator.drawForeground(canvas);
}
void onDrawLayer4(jjPLAYER@ player, jjCANVAS@ canvas) {
	decorator.draw(player, canvas, jjGetModOrder(), jjGetModRow());
}
void onLevelLoad() {
	//beginLevelLoad();
	decorator.prepareWind(jjUnixTimeMs(), 10, 20, 0.8f, 0.2f, 0.25f, 0.05f);
	decorator.createLanternSprites();
	fillWaterPools();
	removeSpritePaletteReferences();
	setLevelPalette();
	adjustMask();
	addColumnShadow();
	loadBackground();
	loadCaveLighting();
	//generateCaveLighting();
	decorator.createBannerSprites();
	decorator.hangBanners();
	decorator.hangGarlands();
	decorator.loadGrassSprites();
	decorator.plantGrass();
	se::roller.loadAnims(jjAnimSets[ANIM::CUSTOM[animRoller]]);
	se::roller.loadSamples(array<SOUND::Sample> = {SOUND::INTRO_BLOW});
	se::roller.setAsWeapon(3, weaponHook);
	se::miniMirv.loadAnims(jjAnimSets[ANIM::CUSTOM[animMiniMirv]]);
	se::miniMirv.loadSamples(array<SOUND::Sample> = {SOUND::INTRO_BOEM1, SOUND::INTRO_BOEM2});
	se::miniMirv.setAsWeapon(9, weaponHook);
	//endLevelLoad();
}
void onLevelReload() {
	setLevelPalette();
}
void onMain() {
	decorator.processWind();
}
void onPlayer(jjPLAYER@ player) {
	if (player.yPos < 712.f)
		player.yPos = 712.f;
}
void onReceive(jjSTREAM &in packet, int clientID) {
	weaponHook.processPacket(packet, clientID);
}