Downloads containing TrueColor.asc

Downloads
Name Author Game Mode Rating
TSF with JJ2+ Only: Holiday Hare '17Featured Download ShadowGPW Single player 8.8 Download file
TSF with JJ2+ Only: Holiday Hare '18Featured Download SmokeNC Single player 8.9 Download file

File preview

//TrueColor.asc version 1.1 (2017/02/19)
#pragma require "TrueColor.asc"

/*
TrueColor.asc lets scriptwriters create palette-independent images to be drawn to the screen as sprites, meaning you can script an enemy, pickup, etc. that uses colors that are nowhere to be found in the level's palette. This is accomplished over a series of three steps, repeatable as many times as you like:
1) Load an external .bmp file from the player's harddrive into memory in a TrueColor::Bitmap object.
2) Call TrueColor::Bitmap::saveToAnimFrames one or more times, or simplify by using the TrueColor::AllocateSpriteSheet function, to save one or more rectangular subareas from the bitmap image into one or more series of subsequent jjANIMFRAMEs, using one or more TrueColor::Coordinates objects to specify how much of the bitmap image to save.
3) Use the various TrueColor drawing functions (NOT the normal jjDraw* functions or jjCANVAS::draw* methods) to draw the resulting images, as stored in jjANIMFRAMEs series, to the screen.

A note for when designing external .bmp files for use with this library: color 0,0,0 is FULLY TRANSPARENT. Any other color is FULLY OPAQUE. TrueColor.asc does not offer greater than 1-bit alpha at this time.

API:
	const uint TrueColor::NumberOfFramesPerImage
		Each TrueColor image, in order to be usable by drawing code, is saved to a series of (TrueColor::NumberOfFramesPerImage) subsequent jjANIMFRAME objects. You will need to be able to multiply and/or divide by this number when dealing with any animation more than one image long. For instance, if you want to draw the third(2) image from a TrueColor animation stored as the first(0) animation in ANIM::SPARK:
			TrueColor::DrawSprite(100, 100, ANIM::SPARK, 0, 2 * TrueColor::NumberOfFramesPerImage);
	
	
	ANIM::Set TrueColor::FindCustomAnim()
		A convenience function not used directly by any part of the TrueColor library, TrueColor::FindCustomAnim simply finds you an ANIM::Set constant such that allocating a new animset to it would not interfere with any existing code running in the level.
	
	void TrueColor::ProcessPalette()
		This function must be called at the beginning of the level and again any time jjPAL::apply is called. This is because the TrueColor images, while visually palette-independent, must still use the contents of jjPalette in order to set themselves up as jjANIMFRAME images, and the details of this process can change slightly every time the palette is edited. As a result, constant jjPalette changes are an even worse idea in levels with TrueColor images than they are the rest of the time.
		
	void TrueColor::EnableCaching(bool enable = true)
		Enabling caching has two separate effects. First, every time you save a (subarea of a) bitmap image to some jjANIMFRAMEs (either directly through TrueColor::Bitmap::saveToAnimFrames or indirectly through TrueColor::AllocateSpriteSheet), TrueColor.asc will cache what image data was saved to which frames. Second, every time TrueColor::ProcessPalette is called, all cached image data will be saved to their respective animframes again, using the new palette. If caching is NOT enabled when ProcessPalette() is called, all previously saved TrueColor images must be MANUALLY saved to their respective jjANIMFRAMEs all over again, though the actual animset allocation need not be repeated.
		
		Note that the resaving of the cached images is done in response to TrueColor::ProcessPalette, not in response to jjPAL::apply, which may present difficulties if jjPAL::apply is called by a script module other than the one including TrueColor.asc. At worst you can try using jjPAL::operator==.
		
		Disabling caching (i.e. passing "false") will not empty the existing cache, it will only prevent the cache from being added to or used until such time as caching is enabled again. As a general rule, you should enable caching iff your script includes one or more palette changes, so that you don't have to do that work manually, but it will serve no purpose (and only take up unneeded memory) if your palette never changes.
	
	void TrueColor::AllocateSpriteSheet(const ANIM::Set setID, const Bitmap& bitmap, const array<array<TrueColor::Coordinates>>& setCoordinates = array<array<TrueColor::Coordinates>>(1, array<TrueColor::Coordinates>(1)))
		An all-in-one function that 1) allocates an entire new animset (at jjAnimSets[setID]) with its length and the lengths of each of its animations determined by the sizes of setCoordinates, then 2) saves all bitmap's rectangular subareas (defined by setCoordinates' various TrueColor::Coordinates objects) into the jjANIMFRAMEs of that animset. This is almost the same thing as jjANIMSET::load, with the caveats that a) all the sprite properties are defined in the script instead of in a .j2a file, and b) you must remember to use TrueColor::NumberOfFramesPerImage when browsing frames within any animations so allocated.
		
		See example script for example.
	
	void TrueColor::void DrawSprite(float xPixel, float yPixel, int setID, uint8 animation, uint frame, int direction = 0, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)
	void TrueColor::DrawCanvasSprite(jjCANVAS@ canvas, int xPixel, int yPixel, int setID, uint8 animation, uint frame, int8 direction = 0)
	...
		All three main sprite drawing functions--Sprite, ResizedSprite, and RotatedSprite, both with and without FromCurFrame--are recreated in TrueColor. The argument pattern is always exactly the same as the normal JJ2+ version's, except for the spriteMode and spriteParam arguments, which are omitted entirely. To call the TrueColor equivalent of a jjCANVAS method, add "Canvas" after "Draw" and pass the jjCANVAS@ as the first argument.
		
		The sprite will be drawn as semitransparent iff jjANIMFRAME::transparent is true for the first jjAnimFrames entry in the sequence of (TrueColor::NumberOfFramesPerImage) entries.
		
		There are no TrueColor versions of Pixel, Rectangle, SwingingVineSprite, or String (at this time).
	
	void TrueColor::DrawObject(jjOBJ@)
		A TrueColor version of jjOBJ::draw(), for convenience purposes. Frozen objects will be drawn as frozen (SPRITE::FROZEN), recently hit objects will be drawn all white (SPRITE::SINGLECOLOR), and all other objects will be drawn using a call to TrueColor::DrawSpriteFromCurFrame. No equivalent to SPRITE::GEM is currently provided.
	
	
	class TrueColor::Bitmap
		A Bitmap is roughly analogous to a jjPIXELMAP, in that it contains an array of pixel colors--here represented by jjPALCOLORs, rather than uint8s--and can be saved to jjANIMFRAMEs. However, its most important constructors are from a string or jjSTREAM, loading a bitmap image (traditionally accessed from an external .bmp file) into memory, and it cannot be saved to tiles or textured backgrounds.
		
		Properties:
			const uint width
			const uint height
			array<array<jjPALCOLOR>> pixels
				All the 24-bit colors in the image, accessed pixels[x][y]
		Methods:
			Bitmap()
				Default constructor is not helpful but required by AS: width and height are 0 and pixels is length 0
			Bitmap(uint w, uint h)
				Constructor from dimensions: sets width and height to w and h and resizes pixels accordingly, filling the arrays with transparent jjPALCOLOR objects (red==0, green==0, blue==0)
			Bitmap(const string &in fileroot)
				Appends ".bmp" to fileroot (e.g. "foo" becomes "foo.bmp") if fileroot does not end with ".bmp" already, and tries to load that file as a 24-bit bitmap file with a width that is a multiple of four pixels. Any failure results in width/height both equalling 0, pixels remaining at length 0, and a variously helpful error message printed to the chatlog; otherwise, the pixels array is filled with the contents of the .bmp file.
			Bitmap(jjSTREAM &in file)
				Tries to load this jjSTREAM as a 24-bit bitmap file with the exact same internal formatting as the file loaded by the previous constructor. There should be no difference between writing "TrueColor::Bitmap bar('foo.bmp');" and writing "TrueColor::Bitmap bar(jjSTREAM('foo.bmp'));" This constructor, however, is more useful in case the image does not come directly from the user's harddrive, e.g. if it is sent from the server to a client in a multiplayer game.
			Bitmap(const TrueColor::Bitmap &in source, uint left, uint top, uint width, uint height)
				First constricts left/top/width/height as needed if they do not actually fit into source's dimensions, then resizes the pixels array to width,height and fills it with the contents of the defined subarea of source.
			void saveToAnimFrames(uint frameID, const TrueColor::Coordinates@ coordinates = null) const
				Saves this image, or a subarea within it, to a series of subsequent jjANIMFRAMEs beginning at jjAnimFrames[frameID] and continuing until jjAnimFrames[frameID + TrueColor::NumberOfFramesPerImage], and sets all those jjANIMFRAMEs' hotspot, gunspot, coldspot, and transparency properties according to the equivalent properties on the coordinates argument. If the coordinates argument is left null (default), the _entire_ image will be saved to the jjANIMFRAMEs, not a subarea of it, and their hotspots/gunspots/coldspots will all be left at 0,0.
			void swizzle(COLOR::Component red, COLOR::Component green, COLOR::Component blue)
				A shortcut method to apply jjPALCOLOR::swizzle to every pixel in the image.
	
	class TrueColor::Coordinates
		Roughly analogous to jjANIMFRAME, in that it provides (a series of) jjANIMFRAMEs with their dimensions and hotspot/gunspot/coldspot positions, but can primarily be thought of as specifying a rectangular subarea of a TrueColor::Bitmap to be saved to jjANIMFRAMEs. For example, a TrueColor::Bitmap with width==128 and height==64, saved to jjANIMFRAMEs using a TrueColor::Coordinates with left=64,top=32,width=64,height=32 would save only the bottom right quadrant of its entire 128x64 image.
		
		Properties:
			int hotSpotX, hotSpotY, gunSpotX, gunSpotY, coldSpotX, coldSpotY
			bool transparent
		Methods:
			Coordinates()
				Default constructor sets all properties, including dimensions, to 0
			Coordinates(uint l, uint t, uint w, uint h, int hX = 0, int hY = 0, int gX = 0, int gY = 0, int cX = 0, int cY = 0, bool tr = false)
				Sets dimensions according to the first four arguments (left, top, width, height), then optionally allows you to set all the public properties at the same time as constructing the object
			
WARNING: All internal code details are subject to change, and any properties, methods, or functions NOT described above should not be used by external scripts for risk of failing to work in later updates to this library.
*/


















namespace TrueColor {
	const uint NumberOfFramesPerImage = 3;
	
	ANIM::Set FindCustomAnim() { //convenience
		uint customAnimID = 0;
		while (jjAnimSets[ANIM::CUSTOM[customAnimID]].firstAnim != 0) { //A loaded anim set will have a firstAnim value of 1 or higher.
			customAnimID += 1; //Keep searching...
			if (customAnimID == 256) {
				jjDebug("No free animation sets found!");
				return ANIM::SPAZ;
			}
		}
		
		return ANIM::CUSTOM[customAnimID];
	}
	
	bool _caching = false;
	dictionary savedBitmapsCache;
	void EnableCaching(bool enable = true) {
		_caching = enable;
	}
	
	array<uint8> __NearestColors(256);
	void ProcessPalette() {
		array<uint8> brightnesses(256);
		for (uint i = 1; i < 256; ++i) { //recreate JJ2's internal brightness values, which are what it uses for SPRITE::NEONGLOW
			const jjPALCOLOR color = jjPalette.color[i];
			brightnesses[i] = (7499 * jjPalette.color[i-1].blue + color.red + 2 * (color.red + 2 * (color.red + 288 * 17 * color.red)) + 38446 * color.green) >> 16;
		}
		
		for (int i = 0; i < 256; ++i) {
			int closestDistance = 1000;
			uint8 closestDistanceID = 0;
			for (int j = 10; j <= 245; ++j) {
				const int distance = int(abs(i - brightnesses[j]));
				if (distance < closestDistance) {
					closestDistance = distance;
					closestDistanceID = j;
				}
			}
			__NearestColors[i] = closestDistanceID;
		}
		
		if (_caching) {
			const auto keys = savedBitmapsCache.getKeys();
			for (uint i = 0; i < keys.length; ++i)
				cast<Bitmap>(savedBitmapsCache[keys[i]]).saveDirectlyToFrames(parseUInt(keys[i]));
		}
	}

	
	class Bitmap {
		private uint _width = 0, _height = 0;
		uint width {
			get const { return _width; }
		};
		uint height {
			get const { return _height; }
		};
		array<array<jjPALCOLOR>> pixels;
		
		Bitmap() { pixels.resize(0); }
		private void resize(uint w, uint h) {
			_width = w;
			_height = h;
			pixels = array<array<jjPALCOLOR>>(width, array<jjPALCOLOR>(height));
		}
		Bitmap(uint w, uint h) {
			resize(w,h);
		}
		Bitmap(const string &in fileroot) {
			string filename = fileroot;
			if (!jjRegexMatch(filename.substr(filename.length - 4), "\\.bmp", true)) //case-insensitive match
				filename += ".bmp";
			jjSTREAM file(filename);
			if (file.isEmpty()) {
				jjDebug("File " + filename + " not found!");
				return;
			}
			_loadFromStream(file);
		}
		Bitmap(jjSTREAM &in file) {
			if (file.isEmpty()) {
				jjDebug("Cannot create a Bitmap from an empty stream.");
				return;
			}
			_loadFromStream(file);
		}
		private void _loadFromStream(jjSTREAM &in file) {
			uint32 imageWidth, imageHeight;
			{ //Bitmap file header
				uint16 headerField; file.pop(headerField);
				if (headerField != 0x4D42) {
					jjDebug('Invalid file header (should be "BM")');
					return;
				}
				uint32 fileSize; file.pop(fileSize);
				if (fileSize != file.getSize() + 6) { //6 for bytes already read
					jjDebug("Invalid internal filesize");
					return;
				}
				file.discard(8);
			}
			{ //DIB header
				uint32 headerType; file.pop(headerType);
				if (headerType != 40) { //"BITMAPINFOHEADER"
					jjDebug("Unsupported BMP format");
					return;
				}
				file.pop(imageWidth);
				file.pop(imageHeight);
				uint16 colorPlanes; file.pop(colorPlanes);
				if (colorPlanes != 1) {
					jjDebug("Invalid file");
					return;
				}
				uint16 bitDepth; file.pop(bitDepth);
				if (bitDepth != 24) {
					jjDebug("Unsupported BMP format");
					return;
				}
				uint32 compressionMethod; file.pop(compressionMethod);
				if (compressionMethod != 0) {
					jjDebug("Unsupported BMP format");
					return;
				}
				uint32 imageSize; file.pop(imageSize);
				if (imageSize != 0 && imageSize != imageWidth * imageHeight * 3) {
					jjDebug("Unsupported file (width must be multiple of 4 pixels)");
					return;
				}
				file.discard(8); //resolution weirdness
				uint32 colorCount; file.pop(colorCount);
				if (colorCount != 0) {
					jjDebug("Unsupported BMP format");
					return;
				}
				uint32 importantColorCount; file.pop(importantColorCount);
				if (importantColorCount != 0) {
					jjDebug("Unsupported BMP format");
					return;
				}
			}
			resize(imageWidth, imageHeight);
			for (int y = imageHeight - 1; y >= 0; --y)
				for (uint x = 0; x < imageWidth; ++x) {
					jjPALCOLOR color;
					file.pop(color.blue); file.pop(color.green); file.pop(color.red);
					pixels[x][y] = color;
				}
		}
		Bitmap(const Bitmap &in source, uint left, uint top, uint imageWidth, uint imageHeight) {
			if (left >= source.width)
				return;
			if (top >= source.height)
				return;
			if (left + imageWidth > source.width) imageWidth = source.width - left;
			if (top + imageHeight > source.height) imageHeight = source.height - top;
			resize(imageWidth, imageHeight);
			for (uint x = 0; x < imageWidth; ++x)
				for (uint y = 0; y < imageHeight; ++y)
					pixels[x][y] = source.pixels[left + x][top + y];
		}
		
		void saveDirectlyToFrames(uint frameID) const {
			array<jjPIXELMAP@> maps(NumberOfFramesPerImage, null);
			for (uint c = 0; c < NumberOfFramesPerImage; ++c)
				@maps[c] = @jjPIXELMAP(width, height);
			const jjPALCOLOR transparent(0, 0, 0);
			for (uint x = 0; x < width; ++x)
				for (uint y = 0; y < height; ++y) {
					const jjPALCOLOR src = pixels[x][y];
					if (src != transparent) {
						maps[0][x,y] = __NearestColors[src.red];
						maps[1][x,y] = __NearestColors[src.green];
						maps[2][x,y] = __NearestColors[src.blue];
					}
				}
			for (uint subFrameID = 0; subFrameID < NumberOfFramesPerImage; ++subFrameID)
				maps[subFrameID].save(jjAnimFrames[frameID + subFrameID]);
		}
		void saveToAnimFrames(uint frameID, const Coordinates@ coordinates = null) const {
			if (coordinates is null || coordinates.width == 0 || coordinates.height == 0)
				@coordinates = @Coordinates(0, 0, width, height);
			Bitmap source(this, coordinates.left, coordinates.top, coordinates.width, coordinates.height);
			source.saveDirectlyToFrames(frameID);
			if (_caching)
				savedBitmapsCache["" + frameID] = source;
			for (uint subFrameID = 0; subFrameID < NumberOfFramesPerImage; ++subFrameID) {
				jjANIMFRAME@ frame = jjAnimFrames[frameID + subFrameID];
				frame.hotSpotX = coordinates.hotSpotX;
				frame.hotSpotY = coordinates.hotSpotY;
				frame.gunSpotX = coordinates.gunSpotX;
				frame.gunSpotY = coordinates.gunSpotY;
				frame.coldSpotX= coordinates.coldSpotX;
				frame.coldSpotY= coordinates.coldSpotY;
			}
			jjAnimFrames[frameID].transparent = coordinates.transparent; //only need to set the first one
		}
		
		void swizzle(COLOR::Component red, COLOR::Component green, COLOR::Component blue) {
			for (uint x = 0; x < _width; ++x)
				for (uint y = 0; y < _height; ++y)
					pixels[x][y].swizzle(red, green, blue);
		}
	}
	
	class Coordinates {
		uint left = 0, right = 0, width = 0, top = 0, bottom = 0, height = 0;
		int hotSpotX = 0, hotSpotY = 0, gunSpotX = 0, gunSpotY = 0, coldSpotX = 0, coldSpotY = 0;
		bool transparent = false;
		Coordinates(){}
		Coordinates(uint l, uint t, uint w, uint h, int hX = 0, int hY = 0, int gX = 0, int gY = 0, int cX = 0, int cY = 0, bool tr = false) {
			left = l; top = t;
			width = w; height = h;
			right = l+w; bottom = t+h;
			hotSpotX = hX; hotSpotY = hY;
			gunSpotX = gX; gunSpotY = gY;
			coldSpotX = cX; coldSpotY = cY;
			transparent = tr;
		}
	}
	
	void AllocateSpriteSheet(const ANIM::Set setID, const Bitmap& bitmap, const array<array<Coordinates>>& setCoordinates = array<array<Coordinates>>(1, array<Coordinates>(1))) {
		array<uint> animSizes(setCoordinates.length);
		for (uint i = 0; i < animSizes.length; ++i) {
			animSizes[i] = setCoordinates[i].length * NumberOfFramesPerImage;
		}
		jjAnimSets[setID].allocate(animSizes);
		for (uint animID = 0; animID < animSizes.length; ++animID) {
			const array<Coordinates>@ animCoordinates = @setCoordinates[animID];
			for (uint frameID = 0; frameID < animCoordinates.length; ++frameID)
				bitmap.saveToAnimFrames(
					jjAnimations[jjAnimSets[setID].firstAnim + animID].firstFrame + frameID * NumberOfFramesPerImage,
					animCoordinates[frameID]
				);
		}
	}
	
	uint __getCurFrame(int setID, uint animation, uint frame) { //same code JJ2+ uses for converting to *FromCurFrame functions
		jjANIMATION@ anim = jjAnimations[jjAnimSets[setID].firstAnim + animation];
		return anim.firstFrame + (frame % anim.frameCount);
	}
	void DrawSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int direction = 0, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1) {
		if (!jjAnimFrames[sprite].transparent) {
			jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::SINGLECOLOR, 0, layerZ, layerXY, playerID);
			jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::NEONGLOW, 0, layerZ, layerXY, playerID);
			jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite+1, direction, SPRITE::NEONGLOW, 1, layerZ, layerXY, playerID);
			jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite+2, direction, SPRITE::NEONGLOW, 2, layerZ, layerXY, playerID);
		} else
			jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::SHADOW, 0, layerZ, layerXY, playerID);
		jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::NEONGLOW, 0, layerZ, layerXY, playerID);
		jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite+1, direction, SPRITE::NEONGLOW, 1, layerZ, layerXY, playerID);
		jjDrawSpriteFromCurFrame(xPixel, yPixel, sprite+2, direction, SPRITE::NEONGLOW, 2, layerZ, layerXY, playerID);
	}
	void DrawSprite(float xPixel, float yPixel, int setID, uint8 animation, uint frame, int direction = 0, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1) {
		DrawSpriteFromCurFrame(xPixel, yPixel, __getCurFrame(setID, animation, frame), direction, layerZ, layerXY, playerID);
	}
	
	void DrawCanvasSpriteFromCurFrame(jjCANVAS@ canvas, int xPixel, int yPixel, uint sprite, int8 direction = 0) {
		if (!jjAnimFrames[sprite].transparent) {
			canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::SINGLECOLOR, 0);
			canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::NEONGLOW, 0);
			canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite+1, direction, SPRITE::NEONGLOW, 1);
			canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite+2, direction, SPRITE::NEONGLOW, 2);
		} else
			canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::SHADOW, 0);
		canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite, direction, SPRITE::NEONGLOW, 0);
		canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite+1, direction, SPRITE::NEONGLOW, 1);
		canvas.drawSpriteFromCurFrame(xPixel, yPixel, sprite+2, direction, SPRITE::NEONGLOW, 2);
	}
	void DrawCanvasSprite(jjCANVAS@ canvas, int xPixel, int yPixel, int setID, uint8 animation, uint frame, int8 direction = 0) {
		DrawCanvasSpriteFromCurFrame(canvas, xPixel, yPixel, __getCurFrame(setID, animation, frame), direction);
	}
	
	void DrawResizedSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, float xScale, float yScale, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1) {
		if (!jjAnimFrames[sprite].transparent) {
			jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::SINGLECOLOR, 0, layerZ, layerXY, playerID);
			jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::NEONGLOW, 0, layerZ, layerXY, playerID);
			jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+1, xScale, yScale, SPRITE::NEONGLOW, 1, layerZ, layerXY, playerID);
			jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+2, xScale, yScale, SPRITE::NEONGLOW, 2, layerZ, layerXY, playerID);
		} else
			jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::SHADOW, 0, layerZ, layerXY, playerID);
		jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::NEONGLOW, 0, layerZ, layerXY, playerID);
		jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+1, xScale, yScale, SPRITE::NEONGLOW, 1, layerZ, layerXY, playerID);
		jjDrawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+2, xScale, yScale, SPRITE::NEONGLOW, 2, layerZ, layerXY, playerID);
	}
	void DrawResizedSprite(float xPixel, float yPixel, int setID, uint8 animation, uint frame, float xScale, float yScale, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1) {
		DrawResizedSpriteFromCurFrame(xPixel, yPixel, __getCurFrame(setID, animation, frame), xScale, yScale, layerZ, layerXY, playerID);
	}
	
	void DrawCanvasResizedSpriteFromCurFrame(jjCANVAS@ canvas, int xPixel, int yPixel, uint sprite, float xScale, float yScale) {
		if (!jjAnimFrames[sprite].transparent) {
			canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::SINGLECOLOR, 0);
			canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::NEONGLOW, 0);
			canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+1, xScale, yScale, SPRITE::NEONGLOW, 1);
			canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+2, xScale, yScale, SPRITE::NEONGLOW, 2);
		} else
			canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::SHADOW, 0);
		canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite, xScale, yScale, SPRITE::NEONGLOW, 0);
		canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+1, xScale, yScale, SPRITE::NEONGLOW, 1);
		canvas.drawResizedSpriteFromCurFrame(xPixel, yPixel, sprite+2, xScale, yScale, SPRITE::NEONGLOW, 2);
	}
	void DrawCanvasResizedSprite(jjCANVAS@ canvas, int xPixel, int yPixel, int setID, uint8 animation, uint frame, float xScale, float yScale) {
		DrawCanvasResizedSpriteFromCurFrame(canvas, xPixel, yPixel, __getCurFrame(setID, animation, frame), xScale, yScale);
	}
	
	void DrawRotatedSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int angle, float xScale = 1, float yScale = 1, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1) {
		if (!jjAnimFrames[sprite].transparent) {
			jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::SINGLECOLOR, 0, layerZ, layerXY, playerID);
			jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::NEONGLOW, 0, layerZ, layerXY, playerID);
			jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+1, angle, xScale, yScale, SPRITE::NEONGLOW, 1, layerZ, layerXY, playerID);
			jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+2, angle, xScale, yScale, SPRITE::NEONGLOW, 2, layerZ, layerXY, playerID);
		} else
			jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::SHADOW, 0, layerZ, layerXY, playerID);
		jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::NEONGLOW, 0, layerZ, layerXY, playerID);
		jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+1, angle, xScale, yScale, SPRITE::NEONGLOW, 1, layerZ, layerXY, playerID);
		jjDrawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+2, angle, xScale, yScale, SPRITE::NEONGLOW, 2, layerZ, layerXY, playerID);
	}
	void DrawRotatedSprite(float xPixel, float yPixel, int setID, uint8 animation, uint frame, int angle, float xScale = 1, float yScale = 1, uint8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1) {
		DrawRotatedSpriteFromCurFrame(xPixel, yPixel, __getCurFrame(setID, animation, frame), angle, xScale, yScale, layerZ, layerXY, playerID);
	}
	
	void DrawCanvasRotatedSpriteFromCurFrame(jjCANVAS@ canvas, int xPixel, int yPixel, uint sprite, int angle, float xScale = 1, float yScale = 1) {
		if (!jjAnimFrames[sprite].transparent) {
			canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::SINGLECOLOR, 0);
			canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::NEONGLOW, 0);
			canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+1, angle, xScale, yScale, SPRITE::NEONGLOW, 1);
			canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+2, angle, xScale, yScale, SPRITE::NEONGLOW, 2);
		} else
			canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::SHADOW, 0);
		canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite, angle, xScale, yScale, SPRITE::NEONGLOW, 0);
		canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+1, angle, xScale, yScale, SPRITE::NEONGLOW, 1);
		canvas.drawRotatedSpriteFromCurFrame(xPixel, yPixel, sprite+2, angle, xScale, yScale, SPRITE::NEONGLOW, 2);
	}
	void DrawCanvasRotatedSprite(jjCANVAS@ canvas, int xPixel, int yPixel, int setID, uint8 animation, uint frame, int angle, float xScale = 1, float yScale = 1) {
		DrawCanvasRotatedSpriteFromCurFrame(canvas, xPixel, yPixel, __getCurFrame(setID, animation, frame), angle, xScale, yScale);
	}
	
	void DrawObject(jjOBJ@ obj) {
		if (obj.freeze != 0 || obj.justHit != 0)
			obj.draw();
		else
			DrawSpriteFromCurFrame(obj.xPos, obj.yPos, obj.curFrame, obj.direction);
	}
}