Register FAQ Search Today's Posts Mark Forums Read
Go Back   JazzJackrabbit Community Forums » Open Forums » General Jazz Jackrabbit Talk

JJ & JJ2 screenshots of the whole maps?

Roobar Roobar's Avatar

JCF Member

Joined: Jul 2013

Posts: 62

Roobar is an asset to this forum

Dec 3, 2016, 10:27 PM
Roobar is offline
Reply With Quote
JJ & JJ2 screenshots of the whole maps?

Hey guys, are there any Jazz Jackrabbit 1 and 2 whole maps as a screenshots? I couldn't find any. Many other games have such. Or is there a way to make such? I really would like to examine the maps, but not in the editor.
Jelly Jam Jelly Jam's Avatar

JCF Member

Joined: Oct 2014

Posts: 775

Jelly Jam is doing well so far

Dec 4, 2016, 07:19 AM
Jelly Jam is offline
Reply With Quote
You can view .j2l files on J2O. Just go to any download and click on the level it has. Same for JJ1.
Seren Seren's Avatar

JCF Member

Joined: Feb 2010

Posts: 864

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, 09: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.
Stijn Stijn's Avatar

Administrator

Joined: Mar 2001

Posts: 6,964

Stijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to behold

Dec 5, 2016, 06:18 AM
Stijn is offline
Reply With Quote
Quote:
Originally Posted by Jelly Jam View Post
You can view .j2l files on J2O. Just go to any download and click on the level it has. Same for JJ1.
Worth noting that these are zoomed-out though. They also don't have events, etc, on them.

(which is a choice rather than a limitation, so if you really want a full-size full-detail shot of a particular level I could perhaps help you)
Simon Simon's Avatar

JCF Member

Joined: Dec 2016

Posts: 58

Simon is doing well so far

Jan 12, 2017, 01:02 AM
Simon is offline
Reply With Quote
I would like full-level images of the JJ1 levels that ship with JJ1. I haven't found these levels in any downloadable in the JJ1 section, therefore the advice so far doesn't help.

How does the site generate the full-level images? Is there a full-level image dumper for download? Should I upload the JJ1 levels in a large archive and let the site dump them for me? <_<

Why I want the levels as images: I'm documenting a speedrunning route for Stonar 2. The first part of Stonar 2 looks the same everywhere, therefore ingame screenshots are too narrow, too hard to understand. I want an atlas image of this section and then draw reference points into it.

-- Simon
cooba cooba's Avatar

JCF Veteran

Joined: Jan 2004

Posts: 7,812

cooba is a glorious beacon of lightcooba is a glorious beacon of lightcooba is a glorious beacon of lightcooba is a glorious beacon of lightcooba is a glorious beacon of lightcooba is a glorious beacon of light

Jan 12, 2017, 05:23 AM
cooba is offline
Reply With Quote
JJ1MOD can generate you full-sized level images: https://www.jazz2online.com/downloads/6040/-/
Stijn Stijn's Avatar

Administrator

Joined: Mar 2001

Posts: 6,964

Stijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to beholdStijn is a splendid one to behold

Jan 12, 2017, 05:52 AM
Stijn is offline
Reply With Quote
Quote:
Originally Posted by Simon View Post
How does the site generate the full-level images? Is there a full-level image dumper for download? Should I upload the JJ1 levels in a large archive and let the site dump them for me? <_<
It's a script I wrote that generates preview images for all kinds of stuff in the downloads. Let me know if JJ1MOD doesn't help, and I could run it on the original levels. It doesn't show events, etc, though.
Simon Simon's Avatar

JCF Member

Joined: Dec 2016

Posts: 58

Simon is doing well so far

Jan 14, 2017, 09:49 PM
Simon is offline
Reply With Quote
Quote:
JJ1MOD can generate you full-sized level images: https://www.jazz2online.com/downloads/6040/-/
Thanks! JJ1MOD did the job: Exported Stonar 2 initial section.

JJ1MOD's seems to be halfway command-line, halfway interactive, that made it hard to use in a shell loop. I resorted to running JJ1MOD in DOSBox, trying different levels one after another, giving interactive input (type "1" to select image export) for every level, until I had the desired image.

But it works!

Quote:
Let me know if JJ1MOD doesn't help, and I could run it on the original levels. It doesn't show events, etc, though.
Happy with the image, but thanks for the offer.

-- Simon
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is On

Forum Jump

All times are GMT -8. The time now is 02:58 PM.