View Single Post
Seren Seren's Avatar

JCF Member

Joined: Feb 2010

Posts: 866

Seren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to allSeren is a name known to all

Dec 4, 2016, 08:34 AM
Seren is offline
Reply With Quote
For JJ2 levels, as long as you have the time it takes, I'm not aware of any better tools than my script (see here for usage details, although use the version in this post for more accurate results):

Code:
#pragma name "Full Level Screenshot"
jjPALCOLOR getColor(jjPALCOLOR dest, int index, SPRITE::Mode mode, int param) {
	jjPALCOLOR source = jjPalette.color[index];
	switch (mode) {
		case SPRITE::BRIGHTNESS:
			return param < 128 ?
				jjPALCOLOR(dest.red * param >> 7, dest.green * param >> 7, dest.blue * param >> 7) :
				jjPALCOLOR(~(~dest.red * param >> 7), ~(~dest.green * param >> 7), ~(~dest.blue * param >> 7));
		case SPRITE::FROZEN:
			{
				jjPALCOLOR color(dest.red + source.red * 3 >> 2, dest.green + source.green * 3 >> 2, dest.blue + source.blue * 3 >> 2);
				int light = color.getLight();
				return jjPALCOLOR(light >> 3, light * 5 >> 3, light);
			}
		case SPRITE::GEM:
			{
				const array hue = {245, 106, 136, 201};
				jjPALCOLOR color;
				color.setHSL(hue[param & 3], 255, index << 1);
				return jjPALCOLOR(dest.red + color.red * 3 >> 2, dest.green + color.green * 3 >> 2, dest.blue + color.blue * 3 >> 2);
			}
		case SPRITE::INVISIBLE:
			return dest;
		case SPRITE::PALSHIFT:
			return jjPalette.color[(index + param) & 255];
		case SPRITE::PLAYER:
			{
				jjPLAYER@ player = jjPlayers[param];
				CHAR::Char chara = player.charCurr;
				bool inRange0 = index >= (jjIsTSF && chara == CHAR::LORI ? 24 : 16) && index < 48;
				bool inRange1 = index >= 88 && index < 96 && jjIsTSF && chara == CHAR::LORI;
				bool inRange2 = index >= 80 && index < 88 && (chara == CHAR::BIRD || chara == CHAR::BIRD2 || chara == CHAR::FROG);
				bool inRange3 = index >= 59 && index < 64 && chara != CHAR::BIRD && chara != CHAR::BIRD2;
				if (inRange0 || inRange1 || inRange2 || inRange3) {
					array fur(4);
					for (int i = 0; i < 4; i++) {
						fur[i] = player.fur >> (i << 3);
					}
					if (chara == CHAR::BIRD) {
						for (int i = 0; i < 2; i++) {
							uint temp = fur[i];
							fur[i] = fur[i | 2];
							fur[i | 2] = temp;
						}
					}
					if (inRange0)
						index -= 16;
					else if (inRange1)
						index -= 88;
					else if (inRange2)
						index -= 80;
					else if (fur[3] != 40)
						index -= index < 61 ? 34 : 33;
					else
						return source;
					return jjPalette.color[(fur[index >> 3] + (index & 7)) & 255];
				}
			}
			return source;
		case SPRITE::SINGLECOLOR:
			return jjPalette.color[param];
		case SPRITE::TRANSLUCENT:
			return jjPALCOLOR(dest.red + source.red >> 1, dest.green + source.green >> 1, dest.blue + source.blue >> 1);
	}
	return source;
}
void drawLayers(array>& image, const array &in layers) {
	int width = image.isEmpty() ? 0 : image[0].length(), height = image.length();
	int layerCount = layers.length();
	array translucentPixels;
	for (int i = 0, j = 0; j < height; (i++) == (j += 32)) {
		for (int k = 0, l = 0; l < width; (k++) == (l += 32)) {
			array tiles(layerCount, -1);
			for (int m = 0; m < 32; m++) {
				array@ row = image[j | m];
				for (int n = 0; n < 32; n++) {
					int column = l | n;
					for (int o = 0; o < layerCount; o++) {
						int tile = tiles[o];
						if (tile < 0) {
							int layer = layers[o];
							int layerWidth = jjLayerWidth[layer], layerHeight = jjLayerHeight[layer];
							int x = k, y = i;
							if (x >= layerWidth) {
								if (jjLayerTileWidth[layer])
									x %= layerWidth;
								else
									x = -1;
							}
							if (y >= layerHeight) {
								if (jjLayerTileHeight[layer])
									y %= layerHeight;
								else
									y = -1;
							}
							tile = tiles[o] = x >= 0 && y >= 0 ? jjTileGet(layer, x, y) : 0;
						}
						int index = jjPIXELMAP(jjGetStaticTile(tile))[n, m];
						if (index != 0) {
							jjPALCOLOR color = jjPalette.color[index];
							if (tile & TILE::ANIMATED == 0 && jjTileType[tile & TILE::RAWRANGE] == 1) {
								translucentPixels.insertLast(color);
							} else {
								row[column] = color;
								break;
							}
						}
					}
					while (!translucentPixels.isEmpty()) {
						jjPALCOLOR one = translucentPixels[translucentPixels.length() - 1], two = row[column];
						row[column] = jjPALCOLOR(one.red * 3 + two.red >> 2, one.green * 3 + two.green >> 2, one.blue * 3 + two.blue >> 2);
						translucentPixels.removeLast();
					}
				}
			}
		}
	}
}
void drawFrame(array>& image, jjANIMFRAME@ frame, int x, int y, int d, SPRITE::Mode mode, int param) {
	const jjPIXELMAP source(frame);
	int width = frame.width, height = frame.height;
	int xMax = image.isEmpty() ? 0 : image[0].length(), yMax = image.length();
	bool hFlip = d < 0, vFlip = (d & 0xC0 != 0) && (~d & 0xC0 != 0);
	x += hFlip ? 1 - (width + frame.hotSpotX) : frame.hotSpotX;
	y += vFlip ? 1 - (height + frame.hotSpotY) : frame.hotSpotY;
	int xDir = hFlip ? -1 : 1;
	int yDir = vFlip ? -1 : 1;
	int xStart = hFlip ? width - 1 : 0;
	int yStart = vFlip ? height - 1 : 0;
	for (int i = 0, j = y, k = yStart; i < height; (i++) ^ (j++) ^ (k += yDir)) {
		if (j >= 0 && j < yMax) {
			array@ row = image[j];
			for (int l = 0, m = x, n = xStart; l < width; (l++) ^ (m++) ^ (n += xDir)) {
				if (m >= 0 && m < xMax) {
					int index = source[n, k];
					if (index != 0)
						row[m] = getColor(row[m], index, mode, param);
				}
			}
		}
	}
}
void takeScreenshot(const string &in filename) {
	array fgLayers, bgLayers;
	for (int i = 1; i < 8; i++) {
		if (
			jjLayerHasTiles[i] &&
			jjLayerXSpeed[i] == 1.f &&
			jjLayerYSpeed[i] == 1.f &&
			jjLayerXAutoSpeed[i] == 0.f &&
			jjLayerYAutoSpeed[i] == 0.f &&
			jjLayerXOffset[i] == 0.f &&
			jjLayerYOffset[i] == 0.f
		)
			(i < 4 ? fgLayers : bgLayers).insertLast(i);
	}
	bgLayers.insertLast(8);
	int width = jjLayerWidth[4] << 5, height = jjLayerHeight[4] << 5;
	array> image(height, array(width));
	drawLayers(image, bgLayers);
	for (int i = 1; i < jjObjectCount; i++) {
		const jjOBJ@ obj = jjObjects[i];
		if (obj.isActive && obj.curFrame != 0) {
			int pickupAnim = obj.curAnim - jjAnimSets[ANIM::PICKUPS];
			jjANIMFRAME@ frame = jjAnimFrames[obj.curFrame];
			SPRITE::Mode mode;
			int param = 0;
			if (obj.freeze != 0) {
				mode = SPRITE::FROZEN;
			} else if (obj.justHit != 0) {
				mode = SPRITE::SINGLECOLOR;
				param = 15;
			} else if (pickupAnim == 22 || pickupAnim == 34 || pickupAnim == 35) {
				mode = SPRITE::GEM;
				param = obj.var[0] - 1;
			} else if (frame.transparent) {
				mode = SPRITE::TRANSLUCENT;
			} else {
				mode = SPRITE::NORMAL;
			}
			switch (obj.eventID) {
				case OBJECT::STOMPSCENERY:
				case OBJECT::COLLAPSESCENERY:
				case OBJECT::DESTRUCTSCENERY:
				case OBJECT::DESTRUCTSCENERYBOMB:
				case OBJECT::BUBBLER:
				case OBJECT::GEMSTOMP:
					break;
				case OBJECT::FLOATLIZARD:
					{
						int x = int(obj.xPos), y = int(obj.yPos);
						const jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::LIZARD] + 3];
						drawFrame(image, jjAnimFrames[anim + jjGameTicks / 3  % anim.frameCount], x, y + 4, obj.direction, mode, param);
						drawFrame(image, frame, x, y, obj.direction, mode, param);
					}
					break;
				case OBJECT::WARP:
					{
						const array palShift = {248, 48, 0, 24, 8, 32, 40};
						int x = int(obj.xPos), y = int(obj.yPos);
						const jjANIMATION@ anim;
						@anim = jjAnimations[obj.special];
						drawFrame(image, jjAnimFrames[anim + (obj.counter >> 3) % anim.frameCount], x - 1, y + 3, obj.direction, mode, param);
						@anim = jjAnimations[obj.curAnim];
						drawFrame(image, jjAnimFrames[obj.curFrame + anim.frameCount], x, y, obj.direction, mode, param);
						drawFrame(image, frame, x, y, obj.direction, SPRITE::PALSHIFT, palShift[obj.var[0] % 7]);
					}
					break;
				case OBJECT::BRIDGE:
					{
						int length = 0, x = int(obj.xPos), y = int(obj.yPos);
						uint frameID = 0;
						const jjANIMATION@ anim = jjAnimations[obj.curAnim];
						do {
							@frame = jjAnimFrames[anim + frameID];
							drawFrame(image, frame, x + length - frame.hotSpotX, y, obj.direction, mode, param);
							length += frame.width;
							if (++frameID >= anim.frameCount)
								frameID = 0;
						} while (length < obj.var[0]);
					}
					break;
				case OBJECT::BOLLPLATFORM:
				case OBJECT::FRUITPLATFORM:
				case OBJECT::GRASSPLATFORM:
				case OBJECT::PINKPLATFORM:
				case OBJECT::SONICPLATFORM:
				case OBJECT::SPIKEPLATFORM:
				case OBJECT::SPIKEBOLL:
					{
						jjANIMFRAME@ subFrame = jjAnimFrames[jjAnimations[obj.killAnim]];
						int distPlus = subFrame.width - 1, dist = 0, anglePlus = -obj.var[5], angle = obj.var[4] - anglePlus * obj.var[2];
						for (int j = obj.var[2]; j > 0; j--) {
							drawFrame(image, subFrame, int(obj.xOrg + dist * jjSin(angle)), int(obj.yOrg + dist * jjCos(angle)), 0, mode, param);
							angle += anglePlus;
							dist += distPlus;
						}
					}
					drawFrame(image, frame, int(obj.xPos), int(obj.yPos), obj.direction, mode, param);
					break;
				case OBJECT::CTFBASE:
					{
						int animSet = jjAnimSets[ANIM::FLAG], x = int(obj.xOrg), y = int(obj.yOrg), d = obj.var[2] != 0 ? 1 : -1, t = jjGameTicks / 6;
						const jjANIMATION@ anim;
						@anim = jjAnimations[animSet + 1];
						drawFrame(image, jjAnimFrames[anim + obj.var[1]], x + 5 * d, y + 17, d, mode, param);
						@anim = jjAnimations[animSet + 2];
						drawFrame(image, jjAnimFrames[anim + t % anim.frameCount], x + 23 * d, y - 36, d, mode, param);
						if (obj.var[4] == 0) {
							@anim = jjAnimations[animSet + 5];
							drawFrame(image, jjAnimFrames[anim + t % anim.frameCount], x - 64 * d, y - 4, -d, mode, param);
						} else {
							@anim = jjAnimations[animSet + 6];
							drawFrame(image, jjAnimFrames[anim + obj.counter / 4 % anim.frameCount], x - 64 * d, y - 4, -d, mode, param);
						}
					}
				default:
					drawFrame(image, frame, int(obj.xPos), int(obj.yPos), obj.direction, mode, param);
			}
		}
	}
	for (int i = 0; i < 32; i++) {
		const jjPLAYER@ player = jjPlayers[i];
		if (player.isInGame && !player.invisibility)
			drawFrame(image, jjAnimFrames[player.curFrame], int(player.xPos), int(player.yPos),
				player.direction ^ (player.antiGrav ? SPRITE::FLIPV : SPRITE::FLIPNONE),
				player.frozen == 0 ? player.spriteMode : SPRITE::FROZEN, player.spriteParam);
	}
	drawLayers(image, fgLayers);
	jjSTREAM file;
	string header = "P6 " + width + ' ' + height + " 255\n";
	int headerLength = header.length();
	for (int i = 0; i < headerLength; i++) {
		file.push(header[i]);
	}
	for (int i = 0; i < height; i++) {
		for (int j = 0; j < width; j++) {
			jjPALCOLOR color = image[i][j];
			file.push(color.red);
			file.push(color.green);
			file.push(color.blue);
		}
	}
	file.save(filename);
}
bool onLocalChat(string &in text, CHAT::Type type) {
	array results;
	if (type == CHAT::NORMAL && jjRegexMatch(text, """!screenshot(?:\s+([^\s"]+|"([^"]+)"))?\s*""", results, true)) {
		string filename;
		if (results[1].isEmpty())
			filename = "SEscreenshot";
		else if (results[2].isEmpty())
			filename = results[1];
		else
			filename = results[2];
		takeScreenshot(filename + ".ppm.asdat");
		return true;
	}
	return false;
}
__________________

I am an official JJ2+ programmer and this has been an official JJ2+ statement.