/*
	This example script seeks to demonstrate some of the basic ways to use jjPIXELMAP (and its
	cousin jjMASKMAP). Since jjPIXELMAP lets you completely edit the graphics of any tile or
	sprite in the game, it's impossible to give an exhaustive list of possibilities, but here
	are some basic ideas to get you started without too much external code to support it.
*/


void onLevelLoad() { //Just some basic setup here.
	jjTexturedBGUsed = true;
	jjTexturedBGTexture = TEXTURE::MEDIVO;
	jjSetFadeColors(80, 0, 40);
	
	jjObjectPresets[OBJECT::TRIGGERCRATE].behavior = MonitorTriggerCrates;
	
	jjObjectPresets[OBJECT::STEADYLIGHT].behavior = CircleBehavior;
	
	jjSampleLoad(SOUND::COMMON_WOOD1, "ATTACKGR.WAV"); //Load a custom sound effect for destroying trigger crates
}

void onFunction0(jjPLAYER@ playerTouchingTheTextSign) { //This serves essentially the same purpose as an ordinary Text event, but the string is so long that AngelScript needs to be used instead.
	playerTouchingTheTextSign.showText("@@@Welcome to the #j~jPIXELMAP~ example level!@This level will use a series of trigger crates@to call various functions in the script. Normally@such functions would all be called in onLevelLoad,@but here you will trigger them individually@in order to better see their effects@on the sprites around you.");
}

//This pair of arrays is used to identify to the player which function is being called when any given trigger crate is hit, so they may check in this file to see for themselves how an effect worked. Like you're doing right now!
funcdef void BASICFUNCTION();
const array<string> FunctionNames =	{"RecolorRain", "ReplaceBeeAnimation", "ReplacePlatform", "ReplacePole", "MakeSlopes", "CircleSprite", "SwingChain"};
const array<BASICFUNCTION@> Functions =	{ RecolorRain ,  ReplaceBeeAnimation ,  ReplacePlatform ,  ReplacePole ,  MakeSlopes ,  CircleSprite ,  SwingChain};

//This is just a wrapper around the basic BEHAVIOR::CRATE function for trigger crates, in place to detect when a crate has been stomped, so it can call a function from the Functions array above.
void MonitorTriggerCrates(jjOBJ@ obj) {
	obj.behave(BEHAVIOR::CRATE);
	if (obj.state == STATE::KILL) {
		obj.behavior = BEHAVIOR::CRATE; //no more need for this wrapper function
		uint triggerID = jjParameterGet(uint16(obj.xOrg / 32), uint16(obj.yOrg / 32), 0, 5);
		jjLocalPlayers[0].showText("@@@@@@" + FunctionNames[triggerID], STRING::LARGE);
		Functions[triggerID]();
	}
}

//Here's a simple function that gets every sprite frame (jjANIMFRAME) used by the Rain ambient scenery effect, constructs a pixel map from it, edits the colors of the pixel map, then saves the pixel map back to the frame. The result is simply that the rain appears red instead of blue.
void RecolorRain() {
	for (uint animID = 2; animID <= 3; ++animID) { //ANIM::COMMON, which houses the rain animations, actually has four different animations, but only the last two are of interest here
		jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::COMMON].firstAnim + animID];
		for (uint frameID = 0; frameID < anim.frameCount; ++frameID) {
			jjANIMFRAME@ frame = jjAnimFrames[anim.firstFrame + frameID];
			jjPIXELMAP image(frame); //Pass a jjANIMFRAME@ to a jjPIXELMAP constructor, and the resulting pixel map will use the width, height, and image of that jjANIMFRAME.
			for (uint x = 0; x < image.width; ++x) //Boilerplate looping code for accessing every pixel in the map.
				for (uint y = 0; y < image.height; ++y)
					if (image[x,y] != 0) //For pixel maps, color 0 is transparent.
						image[x,y] = 162 + (image[x,y] & 7); //162 is one of the brightest colors in the Medivo palette's wall tile gradient. Most sprites use the sprite color section of the palette, which organizes colors in groups of 8, so you can use &7 on a color to get basically its brightness without its particular gradient.
			image.save(frame); //An essential last step, or the changes will only have been made to the jjPIXELMAP, not the jjANIMFRAME, and the player won't end up seeing anything change.
		}
	}
}

//Pixel maps can be created from single tiles (jjPIXELMAP(uint16)), but it's more interesting to construct one from a whole section of a specified layer. Here we take a 96*128 area of layer 3 and save it to the first frame of the Bee enemy's animation.
void ReplaceBeeAnimation() {
	jjANIMATION@ anim = jjAnimations[jjObjectPresets[OBJECT::BEE].curAnim];
	anim.frameCount = 1; //The Bee enemy's animation has several frames, but the tileset only has material for one. Editing jjANIMATION::frameCount to 1 will ensure that the animation never extends beyond the one frame we're providing.
	jjANIMFRAME@ frame = jjAnimFrames[anim.firstFrame];
	jjPIXELMAP gargoyle(0, 9*32, 3*32, 4*32, 3); //Arguments in order: Leftmost pixel of image in layer; topmost pixel; area width; area height; layer ID (1-indexed). You can check the level for yourself in JCS to see how this matches up with the gargoyle's position in layer 3, although note that JCS uses 1-indexed tile positions, not 0-indexed like AngelScript does.
	gargoyle.save(frame);
	frame.hotSpotX = -frame.width / 2;	//Most hotspot values are negative, and the most common position for a hotspot is in the (negative) center of the frame. That's what this code does.
	frame.hotSpotY = -frame.height / 2;	//Of course, you're welcome to experiment with other hotspot positions for your own specialized needs, but this is fine for a simple Bee enemy.
}

//This is pretty much the same code as ReplaceBeeAnimation, but simpler because we don't need to edit jjANIMATION::frameCount (swinging platform animations are only one frame long to begin with), yet more complicated because we're grabbing only a portion of the tiles, not the whole things. Thus the top and left arguments to the constructor are not multiples of 32, and the width and height arguments are not necessary either. For this kind of editing, you may want to take a screenshot of the level from JCS, open it up in a graphics program, and zoom in to find the exact pixels you want.
//One thing this script doesn't demonstrate is editing the contents of a pixel map once you've generated it from an area in a layer--for example, rounding off the platform's corners with transparent pixels--but that's perfectly simple. Use another pair of x<platform.width and y<platform.height loops to access every pixel in the map, and make your edits accordingly from there.
void ReplacePlatform() {
	jjPIXELMAP platform(38*32 + 17, 13*32 + 1, 64, 11, 4);
	jjANIMFRAME@ frame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::BOLLPLAT].firstAnim].firstFrame];
	platform.save(frame);
	frame.hotSpotX = -frame.width/2;
	frame.hotSpotY = -frame.height/2;
}

//Even simpler, but it makes the important point that the sprites you edit can be used for _any_ purpose, including rotating and/or resizing.
void ReplacePole() {
	jjPIXELMAP pole(33*32 + 1, 6*32 + 4, 31, 102);
	jjANIMFRAME@ frame = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::CARROTPOLE].firstAnim].firstFrame];
	pole.save(frame);
	frame.hotSpotX = -frame.width/2;
	frame.hotSpotY = -frame.height;
}

//Not all uses of jjPIXELMAP involve saving to sprites--you can also overwrite the images of individual tiles, so long as the pixel map has dimensions 32x32! Medivo already has workable slope tiles, but some other tilesets do not, and this is an example of how you might generate slopes in a tileset based on its existing graphics. The loops are simultaneously complicated and formulaic enough that they will not be commented in any detail--you can figure them out if you stare at them long enough--but the point is that if a tileset is missing some tiles that you need, you can add them in using AngelScript!
void MakeSlopes() {
	//A basic floor area, from which all the slopes will be drawn
	jjPIXELMAP slopeBase(14*32, 10*32, 96, 96, 4);
	
	//Construct a bunch of arrays of pixel maps and mask maps to draw the new slope tiles to
	array<jjMASKMAP@> singleWidthSlopeMask(3); //jjMASKMAP is much like jjPIXELMAP, but is only used for the masks of tiles. As such, it uses bool instead of uint8, where "true" for any given pixel means "masked" and "false" means "unmasked". Mask maps are always 32x32 in size.
	array<array<jjMASKMAP@>> doubleWidthSlopeMask(2, array<jjMASKMAP@>(3));
	array<jjPIXELMAP@> singleWidthSlopeImageLeft(3);
	array<jjPIXELMAP@> singleWidthSlopeImageRight(3);
	array<array<jjPIXELMAP@>> doubleWidthSlopeImageLeft(2, array<jjPIXELMAP@>(3));
	array<array<jjPIXELMAP@>> doubleWidthSlopeImageRight(2, array<jjPIXELMAP@>(3));
	
	//Initialize the new pixel maps
	for (uint yTile = 0; yTile < 3; ++yTile) {
		@singleWidthSlopeMask[yTile] = @jjMASKMAP(false); //an initialization value of "false" for a mask map means it will be completely unmasked until you start editing it
		@singleWidthSlopeImageLeft[yTile] = @jjPIXELMAP((yTile < 2) ? 0 : 45);
		@singleWidthSlopeImageRight[yTile] = @jjPIXELMAP((yTile < 2) ? 0 : 45);
		for (uint xTile = 0; xTile < 2; ++xTile) {
			@doubleWidthSlopeMask[xTile][yTile] = @jjMASKMAP(false);
			@doubleWidthSlopeImageLeft[xTile][yTile] = @jjPIXELMAP((yTile < 2) ? 0 : 45);
			@doubleWidthSlopeImageRight[xTile][yTile] = @jjPIXELMAP((yTile < 2) ? 0 : 45);
		}
	}
	
	//Draw (and mask) the new slopes to those maps
	for (uint y = 0; y < 64; ++y)
		for (uint x = 0; x < 64; ++x) {
			if (x < 32) {
				singleWidthSlopeMask[y / 32][x, y & 31] = (y + x) >= 43;
				if (y + x >= 32 && y + x < 96)
					singleWidthSlopeImageLeft[y / 32][x, y & 31] = slopeBase[x,y+x];
				if (y - x >= 0 && y - x < 64)
					singleWidthSlopeImageRight[y / 32][x, y & 31] = slopeBase[x, y - x + 32];
			}
			uint xh = x/2;
			doubleWidthSlopeMask[x / 32][y / 32][x & 31, y & 31] = (y + xh) >= 43;
			if (y + xh >= 32 && y + xh < 96)
				doubleWidthSlopeImageLeft[x / 32][y / 32][x, y & 31] = slopeBase[32+x,y+xh];
			if (y - xh >= 0 && y - xh < 64)
				doubleWidthSlopeImageRight[x / 32][y / 32][x, y & 31] = slopeBase[32+x, y - xh + 32];
		}
		
	//Save the maps to the actual tiles
	for (uint yTile = 0; yTile < 2; ++yTile) {
		singleWidthSlopeMask[yTile].save(572 + yTile*10); //Saves the /-shaped slope
		singleWidthSlopeMask[yTile].save(577 + yTile*10 + TILE::HFLIPPED, true); //Reuses the masks of the /-shaped slope by saving it to the horizontally flipped versions of the tileIDs. The "true" parameter then flips that mask, resulting in a \-shaped slope being saved to the non-horizontally-flipped version of those tileIDs. Play around with jjPIXELMAP::save and jjMASKMAP::save with flipped and unflipped tiles until you get how this works.
		singleWidthSlopeImageLeft[yTile].save(572 + yTile*10);
		singleWidthSlopeImageRight[yTile].save(577 + yTile*10);
		for (uint xTile = 0; xTile < 2; ++xTile) {
			doubleWidthSlopeMask[xTile][yTile].save(573 + xTile + yTile*10);
			doubleWidthSlopeMask[xTile][yTile].save(576 - xTile + yTile*10 + TILE::HFLIPPED, true);
			doubleWidthSlopeImageLeft[xTile][yTile].save(573 + xTile + yTile*10);
			doubleWidthSlopeImageRight[xTile][yTile].save(575 + xTile + yTile*10);
		}
	}
}

//Not all pixel map images need to be constructed from existing tiles, layer areas, or sprites! You can simply draw your own from scratch, as is done here by drawing an all-white circle in the center of a 100x100 sprite image.
void CircleSprite() {
	jjANIMFRAME@ frame = jjAnimFrames[jjObjectPresets[OBJECT::ORANGE].curFrame]; //We're going to need to replace some jjANIMFRAME's image here, and it might as well be the Orange's, since it's not in this level...
	jjPIXELMAP circle(100, 100); //A two-integer constructor for jjPIXELMAP will create a totally empty pixel map (all pixels of value 0, transparent) with those dimensions
	for (uint x = 0; x < circle.width; ++x)
		for (uint y = 0; y < circle.height; ++y)
			circle[x,y] = (sqrt(pow(50-int(x), 2) + pow(50-int(y), 2)) < 50) ? 15 : 0; //Straightforward distance-based math for drawing a circle--the point is, each pixel in the pixel map is drawn from scratch in the script.
	circle.save(frame);
	frame.hotSpotX = -frame.width/2;
	frame.hotSpotY = -frame.height/2;
	drawSteadyLight = true; //Turn on this bool to signify that the white circle sprite has been created and can be drawn to screen
}
bool drawSteadyLight = false;
void CircleBehavior(jjOBJ@ obj) {
	if (drawSteadyLight) {
		const float scale = abs(jjSin(jjGameTicks << 3));
		jjDrawResizedSpriteFromCurFrame(obj.xPos, obj.yPos, jjObjectPresets[OBJECT::ORANGE].curFrame, scale, scale, SPRITE::BLEND_DIFFERENCE, 255, 1); //This sprite was created entirely within this .j2as file, and can be drawn as a rescaled sprite! The sprite mode BLEND_DIFFERENCE with a white color produces a negative image of whatever pixels lay under the space being drawn to.
	}
}

//One special case that deserves examining is sprites used by Swinging Vine objects or the jjDrawSwingingVineSpriteFromCurFrame function, which have two special concerns: the sprite is looped along its y axis if it's not tall enough to support the full length of the vine (great!) and instead of pixel 0 for transparency, it uses pixel 128 (bleh). Here's an example of how to construct such a sprite from a single tile in the tileset:
void SwingChain() {
	jjPIXELMAP chainHorizontal(568); //tile ID
	jjPIXELMAP chainVertical(9, 32); //dimensions. this is smaller than the normal swinging vine sprite, but the code accepts that.
	for (uint x = 0; x < chainVertical.width; ++x)
		for (uint y = 0; y < chainVertical.height; ++y) {
			uint8 color = chainHorizontal[y, x + 12]; //simple math to rotate the image and use only a subsection of the original tile
			if (color == 0) color = 128;
			chainVertical[x,y] = color;
		}
	chainVertical.save(jjAnimFrames[jjAnimations[jjAnimSets[ANIM::VINE].firstAnim + 1].firstFrame]);
}