Copy the following code, paste it into a text editor, save as SEscreenshot.mut, enable it in JJ2+ by typing " /mutators sescreenshot on ", cycle to the level you want to take a screenshot of, type " !screenshot ", wait between several seconds to several minutes depending on level size (yes, it can take several minutes, and the game will act as if frozen for that time, don't attempt to interact with it or Windows will assume it stopped responding), a file named "SEscreenshot.ppm.asdat" will appear in your JJ2 directory, remove the ".asdat" extension, use an image editor to convert the resulting PPM to a PNG, perhaps shrink the image if its file size is still too big for your needs.
Optionally: instead of " !screenshot " you can type " !screenshot file_name " or " !screenshot "file name" " (replacing file_name with a selected string of text, the latter syntax allowing whitespace) to save a picture with a custom file name.
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<int> 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<uint> 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<array<jjPALCOLOR>>& image, const array<int> &in layers) {
int width = image.isEmpty() ? 0 : image[0].length(), height = image.length();
int layerCount = layers.length();
array<jjPALCOLOR> translucentPixels;
for (int i = 0, j = 0; j < height; (i++) == (j += 32)) {
for (int k = 0, l = 0; l < width; (k++) == (l += 32)) {
array<int> tiles(layerCount, -1);
for (int m = 0; m < 32; m++) {
array<jjPALCOLOR>@ 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(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 + two.red >> 1, one.green + two.green >> 1, one.blue + two.blue >> 1);
translucentPixels.removeLast();
}
}
}
}
}
}
void drawFrame(array<array<jjPALCOLOR>>& 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<jjPALCOLOR>@ 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<int> 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<array<jjPALCOLOR>> image(height, array<jjPALCOLOR>(width));
drawLayers(image, bgLayers);
for (int i = 1; i < jjObjectCount; i++) {
const jjOBJ@ obj = jjObjects[i];
if (obj.isActive &&
obj.curFrame != 0 &&
obj.eventID != OBJECT::STOMPSCENERY &&
obj.eventID != OBJECT::COLLAPSESCENERY &&
obj.eventID != OBJECT::DESTRUCTSCENERY &&
obj.eventID != OBJECT::DESTRUCTSCENERYBOMB
) {
if (obj.eventID == 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, SPRITE::NORMAL, 0);
@anim = jjAnimations[animSet + 2];
drawFrame(image, jjAnimFrames[anim + t % anim.frameCount], x + 23 * d, y - 36, d, SPRITE::NORMAL, 0);
if (obj.var[4] == 0) {
@anim = jjAnimations[animSet + 5];
drawFrame(image, jjAnimFrames[anim + t % anim.frameCount], x - 64 * d, y - 4, -d, SPRITE::NORMAL, 0);
} else {
@anim = jjAnimations[animSet + 6];
drawFrame(image, jjAnimFrames[anim + obj.counter / 4 % anim.frameCount], x - 64 * d, y - 4, -d, SPRITE::NORMAL, 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;
}
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<string> 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.
|