var modName = "mods/../a_mod_by_alice.js" //can't do "alice's mod" because the apostrophe will fuck up code, be too confusing, or both
var dependencies = ["mods/libhooktick.js", "mods/chem.js", "mods/minecraft.js", "mods/Neutronium Mod.js", "mods/CrashTestDummy.js", "mods/fey_and_more.js", "mods/velocity.js", "mods/ketchup_mod.js", "mods/moretools.js", "mods/aChefsDream.js", "mods/nousersthings.js"];  //thanks to mollthecoder, PlanetN9ne, StellarX20 (3), MelecieDiancie, R74n, Nubo318, Sightnado, sqeč, and NoUsernameFound
var dependencyExistence = dependencies.map(x => enabledMods.includes(x));
var allDependenciesExist = dependencyExistence.reduce(function(a,b) { return a && b });
//console.log(allDependenciesExist);
if(allDependenciesExist) {
try {
	//COMMON VARIABLES ##

		const whiteColor = {r: 255, g: 255, b: 255};
		const blackColor = {r: 0, g: 0, b: 0};
		canvas = document.getElementsByTagName("canvas")[0];
		ctx = canvas.getContext("2d");

	//ESSENTIAL COMMON FUNCTIONS (CODE LIBRARY) ##

		//URL

			urlParams = new URLSearchParams(window.location.search);

		//Objects

			//getKeyByValue code by SO UncleLaz: https://stackoverflow.com/questions/9907419/how-to-get-a-key-in-a-javascript-object-by-its-value
			//CC-BY-SA-4.0
			function getKeyByValue(object, value) {
			  return Object.keys(object).find(key => object[key] === value);
			};

		//RNG

			//Random integer from 0 to n
			function randomIntegerFromZeroToValue(value) {
				var absoluteValuePlusOne = Math.abs(value) + 1;
				if(value >= 0) { //Positive case
					return Math.floor(Math.random() * absoluteValuePlusOne)
				} else { //Negative case: flip sign
					return 0 - Math.floor(Math.random() * absoluteValuePlusOne)
				};
			};

			//Random thing from array
			function randomChoice(array) {
				if(!array) { throw new Error("randomChoice: Array is undefined or missing!") }
				if(array.length === 0) { throw new Error(`The array ${array} is empty`) };
				var length = array.length;
				var randomIndex = randomIntegerFromZeroToValue(length - 1);
				return array[randomIndex];
			};

			//Random 1 or -1
			function randomSign() {
				return Math.random() < 0.5 ? 1 : -1
			};

			//Random integer from m to n
			function randomIntegerBetweenTwoValues(min,max) {
				if(min > max) {
					var temp = max; //the need of a temporary space has always annoyed me
					max = min;
					min = temp;
				};
				return Math.floor(Math.random() * (max - min + 1)) + min
			};

			//cyrb128 idk where this comes from but it was in the same thread
			function cyrb128(str) {
				let h1 = 1779033703, h2 = 3144134277,
					h3 = 1013904242, h4 = 2773480762;
				for (let i = 0, k; i < str.length; i++) {
					k = str.charCodeAt(i);
					h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
					h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
					h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
					h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
				}
				h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
				h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
				h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
				h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
				return [(h1^h2^h3^h4)>>>0, (h2^h1)>>>0, (h3^h1)>>>0, (h4^h1)>>>0];
			}

			function mulberry32(a) { //Mulberry32 implemented in JS from StackOverflow, https://gist.github.com/tommyettinger/46a874533244883189143505d203312c
				return function() {
				  var t = a += 0x6D2B79F5;
				  t = Math.imul(t ^ t >>> 15, t | 1);
				  t ^= t + Math.imul(t ^ t >>> 7, t | 61);
				  return ((t ^ t >>> 14) >>> 0) / 4294967296;
				}
			} //returns random function seeded with a

			//Seeded randbetween
			function seededRandBetween(min,max,randomFunction) {
				if(min > max) {
					var temp = max;
					max = min;
					min = temp;
				};
				return Math.floor(randomFunction() * (max - min + 1)) + min
			};

		//Arrays

			//Shallow array comparer by SO Tim Down: https://stackoverflow.com/a/10260204
			//CC-BY-SA-3.0
			function arraysIdentical(arr1, arr2) {
				var i = arr1.length;
				if (i !== arr2.length) {
					return false;
				};
				while (i--) {
					if (arr1[i] !== arr2[i]) {
						return false;
					};
				};
				return true;
			};

			function indexOf(arr, val, comparer) {
				for (var i = 0, len = arr.length; i < len; ++i) {
					if ( i in arr && comparer(arr[i], val) ) {
						return i;
					};
				};
				return -1;
			};

			function averageNumericArray(array) {
				var total = array.reduce(addTwoNumbers,0)
				return total / array.length
			};

			function sumNumericArray(array) { //Sum of array numbers
				return array.reduce((partialSum, a) => partialSum + a, 0);
			};

			function pad_array(arr,len,fill) { //https://stackoverflow.com/a/38851957
				//console.log("Padding array");
				return arr.concat(Array(len).fill(fill)).slice(0,len);
			}

			//Function to check if an array includes a given array by SO Johnny Tisdale: https://stackoverflow.com/a/60922255
			//CC-BY-SA-4.0
			function includesArray(parentArray, testArray) {
				for (let i = 0; i < parentArray.length; i++) {
					if (parentArray[i].every(function(value, index) { return value === testArray[index]})) {
						return true;
					};
				};
				return false;
			};

			function addArraysInPairs(array1,array2,fill=0) { //e.g. [1,2,3] + [10,0,-1] = [11,2,2]
				//console.log("Adding in pairs: " + array1 + " and " + array2 + ".");
				if(array1.length > array2.length) { //zero-padding
					array2 = pad_array(array2,array1.length,fill); //if a1 is longer, pad a2 to a1's length
				} else if(array2.length > array1.length) {
					array1 = pad_array(array1,array2.length,fill); //if a2 is longer, pad a1 to a2's length
				};
				var tempArray = [];
				for(z = 0; z < array1.length; z++) {
					//console.log("Forming output values (" + array1[z] + " + " + array2[z] + ")");
					tempArray[z] = array1[z] + array2[z];
					//console.log("Sum" + tempArray[z]);
				};
				//console.log("Added into " + tempArray + ".");
				return tempArray;
			};

			function tryJoin(stringOrArray,joiner) {
				//console.log(`tryJoin: ${stringOrArray}`);
				if(typeof(stringOrArray) === "string") {
					//console.log("tryJoin: String");
					return stringOrArray;
				} else if(Array.isArray(stringOrArray)) {
					//console.log("tryJoin: Array");
					return stringOrArray.join(joiner);
				} else {
					throw new TypeError(`Unexpected type: ${typeof(stringOrArray)}`);
				};
			};

		//Checks

			//Element exists in the elements object
			function elementExists(elementName) {
				return typeof(elements[elementName]) === "object";
			};

			//Has a given state
			function isState(elementName,inputState) {
				if(!elementExists(elementName)) {
					throw new Error(`Element ${elementName} doesn't exist`);
				};

				var infoState = elements[elementName].state;

				if(infoState == undefined) { infoState = "undefined" };

				if(inputState == undefined) { inputState = "undefined" };
				if(inputState instanceof Array) {
					var limit = 0;
					while(inputState.includes(undefined) && limit < 3) {
						inputState[inputState.indexOf(undefined)] = "undefined"
						limit++;
					};
				};

				if(inputState instanceof Array) {
					return inputState.includes(infoState);
				};

				return infoState == inputState;
			};

			//Check if pixel of given element exists at given location
			function hasPixel(x,y,elementInput) {
				if(isEmpty(x,y,true)) { //if empty, it can't have a pixel
					return false;
				} else {
					if(elementInput.includes(",")) { //CSTA
						elementInput = elementInput.split(",");
					};
					if(Array.isArray(elementInput)) { //if element list
						for(i = 0; i < elementInput.length; i++) { if(!elementExists(elementInput[i])) { console.log(`hasPixel: Element "${elementInput[i]}" doesn't exist`) } };
						return elementInput.includes(pixelMap[x][y].element);
					} else { //if single element
						if(!elementExists(elementInput)) { console.log(`hasPixel: Element "${elementInput}" doesn't exist`) };
						return pixelMap[x][y].element === elementInput;
					};
				};		
			};

			//Is movable
			var backupCategoryWhitelist = ["land","powders","weapons","food","life","corruption","states","fey","Fantastic Creatures","dyes","energy liquids","random liquids","random gases","random rocks"];
			var backupElementWhitelist = ["mercury", "chalcopyrite_ore", "chalcopyrite_dust", "copper_concentrate", "fluxed_copper_concentrate", "unignited_pyrestone", "ignited_pyrestone", "everfire_dust", "extinguished_everfire_dust", "mistake", "polusium_oxide", "vaporized_polusium_oxide", "glowstone_dust", "redstone_dust", "soul_mud", "wet_soul_sand", "nitrogen_snow", "fusion_catalyst", "coal", "coal_coke", "blast_furnace_fuel", "molten_mythril"];

			function commonMovableCriteria(name,shallowBlacklist=null) {
				if(typeof(elements[name]) !== "object") {
					throw new Error(`Nonexistent element ${name}`);
				};
				var info = elements[name];
				//console.log(`${name} (${JSON.stringify(elements[name])})`);
				if(typeof(info.state) === "undefined") {
					var state = null;
				} else {
					var state = info.state;
				};
				if(typeof(info.category) === "undefined") {
					var category = "other";
				} else {
					var category = info.category;
				};
				if(shallowBlacklist !== null && shallowBlacklist.includes(name)) {
					return false;
				};
				if(elements[name].tool) {
					return false;
				};
				if(elements[name].behavior && elements[name].behavior.toString() == elements.wall.behavior.toString() && !elements[name].tick) {
					return false;
				};
				if(["liquid","gas"].includes(state)) {
					return true;
				};
				if(info.movable) {
					return true;
				};
				if(elements[name].behavior instanceof Array) {
					var behaviorString = elements[name].behavior.toString();
					return behaviorString.includes("M1") || behaviorString.includes("M2");
				};
				if(backupCategoryWhitelist.includes(category)) {
					return true;
				};
				if(backupElementWhitelist.includes(name)) {
					return true;
				};
				if(category.includes("mudstone")) {
					return true;
				};
				return false;
			};

			//Element name search
				window.searchQuery = {};

				function searchElements(query) {
					if(typeof(window.searchQuery) == "undefined") { window.searchQuery = "" }; //necessary because of filter's idiotic no-argument policy
					window.searchQuery = query;
					var elemNames = Object.keys(elements);
					var matches = elemNames.filter(function(name) {
						return !!(name.match(window.searchQuery))
					});
					return matches
				};

				function elementsWith(keyQuery) {
					if(typeof(window.keyQuery) == "undefined") { window.keyQuery = "" }; //necessary because of filter's idiotic no-argument policy
					window.keyQuery = keyQuery;
					var elemNames = Object.keys(elements);
					var matches = elemNames.filter(function(name) {
						return typeof(elements[name]?.[window.keyQuery]) !== "undefined"
					});
					return matches
				};

				function elementsWithout(keyInverseQuery) {
					if(typeof(window.keyInverseQuery) == "undefined") { window.keyInverseQuery = "" }; //necessary because of filter's idiotic no-argument policy
					window.keyInverseQuery = keyInverseQuery;
					var elemNames = Object.keys(elements);
					var matches = elemNames.filter(function(name) {
						return typeof(elements[name]?.[window.keyInverseQuery]) === "undefined"
					});
					return matches
				};

				function getElementsInCategory(categoryName) {
					if(["",null,undefined].includes(categoryName)) { categoryName = "other" };
					window.categoryQuery = categoryName;
					var elemNames = Object.keys(elements);
					var matches = elemNames.filter(function(name) {
						return (elements[name].category ?? "other") == window.categoryQuery
					});
					return matches
				};

				function getStateHigh(element,forceArray=false) {
					if(!(element instanceof Array)) { element = [element] };
					var existantElements = element.filter(function(name) { return elementExists(name) });
					if(existantElements.length == 0) { return undefined } else { element = existantElements };
					var results = element.map(name => elements[name].stateHigh).filter(function(nameOrUndefined) { return typeof(nameOrUndefined) !== "undefined" });
					switch(results.length) {
						case 0:
							return null;
						case 1:
							if(!forceArray) { return results[0] };
						default:
							return results
					}
				};

				function getState(element,forceArray=false) {
					if(!(element instanceof Array)) { element = [element] };
					var existantElements = element.filter(function(name) { return elementExists(name) });
					if(existantElements.length == 0) { return undefined } else { element = existantElements };
					var results = element.map(name => elements[name].state).filter(function(nameOrUndefined) { return typeof(nameOrUndefined) !== "undefined" });
					switch(results.length) {
						case 0:
							return null;
						case 1:
							if(!forceArray) { return results[0] };
						default:
							return results
					}
				};

				function getStateLow(element,forceArray=false) {
					if(!(element instanceof Array)) { element = [element] };
					var existantElements = element.filter(function(name) { return elementExists(name) });
					if(existantElements.length == 0) { return undefined } else { element = existantElements };
					var results = element.map(name => elements[name].stateLow).filter(function(nameOrUndefined) { return typeof(nameOrUndefined) !== "undefined" });
					switch(results.length) {
						case 0:
							return null;
						case 1:
							if(!forceArray) { return results[0] };
						default:
							return results
					}
				};

				function getStateAtTemp(element,temp) {
					var data = elements[element];
					var tl = data.tempLow;
					var th = data.tempHigh;
					if(typeof(tl) == "number" && temp <= tl) {
						return data.stateLow
					} else if(typeof(th) == "number" && temp >= th) {
						return data.stateHigh
					} else {
						return element
					}
				};

				function getBreakInto(element,forceArray=false) {
					if(!(element instanceof Array)) { element = [element] };
					var existantElements = element.filter(function(name) { return elementExists(name) });
					if(existantElements.length == 0) { return undefined } else { element = existantElements };
					var results = element.map(name => elements[name].breakInto).filter(function(nameOrUndefined) { return typeof(nameOrUndefined) !== "undefined" });
					switch(results.length) {
						case 0:
							return null;
						case 1:
							if(!forceArray) { return results[0] };
						default:
							return results
					}
				};

		//Math(s)

			//Base n logarithm from https://stackoverflow.com/a/3019290
			function logN(number,base) { //Vulnerable to float issues
				return Math.log(number) / Math.log(base);
			};

			//Distance between points
			function pyth(xA,yA,xB,yB) {
				var a = Math.abs(xB - xA);
				var b = Math.abs(yB - yA);
				var c = Math.sqrt(a**2 + b**2);
				return c;
			};

			//Limit number to [min, max]
			function bound(number,lowerBound,upperBound) {
				return Math.min(upperBound,Math.max(lowerBound,number));
			};

			//Emergency color wrapper
			rgbColorBound = function(color) {
				return bound(color,0,255);
			};

			function addTwoNumbers(number1,number2) { //reducer
				return number1 + number2
			}

			//Logistic curve
				//x = real number
				//L = maximum value
				//x_0 = "the x value of the sigmoid midpoint" i.e. the x center of the bendy part
				//k = steepness
			function logisticCurve(x,L,k,x0) {
				return L/(   1 + (  Math.E ** ( -k * (x - x0) )  )   );
			};

			// https://stackoverflow.com/questions/10756313/javascript-jquery-map-a-range-of-numbers-to-another-range-of-numbers
			// Function from August Miller
			//Map a range of numbers to another range of numbers
			function scale (number, inMin, inMax, outMin, outMax) {
				return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
			}

		//Color

			function rgbStringToUnvalidatedObject(string) { //turns rgb() to {r,g,b} with no bounds checking
				//console.log("Splitting string into object");
				string = string.split(",");
				var red = parseFloat(string[0].substring(4));
				var green = parseFloat(string[1]);
				var blue = parseFloat(string[2].slice(0,-1));
				//console.log("String split: outputs " + red + ", " + green + ", " + blue + ".");
				return {r: red, g: green, b: blue};
			};

			function rgbStringToObject(string,doRounding=true,doBounding=true) { //turns rgb() to {r,g,b}
				//console.log(`rgbStringToObject: ${string}`);
					//console.log("Splitting string into object");
				if( !(string.startsWith("rgb(")) || !(string.endsWith(")")) ) {
					throw new Error('Color must start with "rgb(" and end with ")"');
				};
				var red,green,blue;
				[red,green,blue] = string.match(/[0-9\-.]+/g).slice(0,3).map(x => parseFloat(x));
					//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
				//NaN checking
					var redNaN = isNaN(red);
					var greenNaN = isNaN(green);
					var blueNaN = isNaN(blue);
					var NanErrorString = "One or more colors are NaN:"
					if(redNaN) { NanErrorString += " red" };
					if(greenNaN) { NanErrorString += " green" };
					if(blueNaN) { NanErrorString += " blue" };
					if(redNaN || greenNaN || blueNaN) { throw new Error(NanErrorString) };
				if(doRounding) {
					red = Math.round(red);
					green = Math.round(green);
					blue = Math.round(blue);
						//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
				};
				if(doBounding) {
					red = bound(red,0,255)
					green = bound(green,0,255)
					blue = bound(blue,0,255)
						//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
				};
					//console.log("String split: outputs " + red + ", " + green + ", " + blue + ".");
				return {r: red, g: green, b: blue};
			};

			function hslColorStringToObject(color) {
				if(!color.startsWith("hsl(") || !color.endsWith(")")) {
					throw new Error(`The color ${color} is not a valid hsl() color`)
				};
				var colorTempArray = color.split(",")
				if(colorTempArray.length !== 3) {
					throw new Error(`The color ${color} is not a valid hsl() color`)
				};
				if(!colorTempArray[1].endsWith("%")) { console.log(`hslColorStringToObject: Saturation in color ${color} was missing a %`); colorTempArray[1] += "%"; }
				if(!colorTempArray[2].endsWith("%)")) { console.log(`hslColorStringToObject: Lightness in color ${color} was missing a %`); colorTempArray[2] = [colorTempArray[2].slice(0, colorTempArray[2].length - 1), "%", colorTempArray[2].slice(colorTempArray[2].length - 1)].join(''); }
				var hue = parseFloat(colorTempArray[0].substring(4));
				var saturation = parseFloat(colorTempArray[1].slice(0,-1))
				var lightness = parseFloat(colorTempArray[2].slice(0,-2));
				//NaN checking
					var hueNaN,saturationNaN,lightnessNaN;
					isNaN(hue) ? hueNaN = true : hueNaN = false;
					isNaN(saturation) ? saturationNaN = true : saturationNaN = false;
					isNaN(lightness) ? lightnessNaN = true : lightnessNaN = false;
					var NanErrorString = "One or more colors are NaN:"
					if(hueNaN) { NanErrorString += " hue" };
					if(saturationNaN) { NanErrorString += " saturation" };
					if(lightnessNaN) { NanErrorString += " lightness" };
					if(hueNaN || saturationNaN || lightnessNaN) { throw new Error(NanErrorString) };
				return {h: hue, s: saturation, l: lightness};
			};

			function rgbToHex(color) {
				//console.log(`rgbToHex called on ${typeof(color) === "object" ? JSON.stringify(color) : color}`);
				if(typeof(color) == "object") { //Expects object like "{r: 172, g: 11, b: 34}"
					var red = color.r;
					var green = color.g;
					var blue = color.b;
						//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
					red = Math.round(red);
					green = Math.round(green);
					blue = Math.round(blue);
						//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
					red = bound(red,0,255)
					green = bound(green,0,255)
					blue = bound(blue,0,255)
						//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
					red = red.toString(16);
					green = green.toString(16);
					blue = blue.toString(16);
						//console.log(`Colors converted to (0x${red}, 0x${green}, 0x${blue})`);
					//console.log("Padding R");
					while(red.length < 2) {
						red = "0" + red;
					};
					//console.log("Padding G");
					while(green.length < 2) {
						green = "0" + green;
					};
					//console.log("Padding B");
					while(blue.length < 2) {
						blue = "0" + blue;
					};
						//console.log(`Colors padded to (0x${red}, 0x${green}, 0x${blue}), concatenating...`);
					return "#" + red + green + blue;
				} else if(typeof(color) == "string") { //Expects string like "rgb(20,137,4)". Also doesn't round properly for some reason...
						//console.log("Splitting string")
					color = rgbStringToUnvalidatedObject(color);
					red = color.r;
					green = color.g;
					blue = color.b;
						//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
					red = Math.round(red);
					green = Math.round(green);
					blue = Math.round(blue);
						//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
					red = bound(red,0,255)
					green = bound(green,0,255)
					blue = bound(blue,0,255)
						//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
					red = red.toString(16);
					green = green.toString(16);
					blue = blue.toString(16);
						//console.log(`Colors converted to (0x${red}, 0x${green}, 0x${blue})`);
					//console.log("Padding R");
					while(red.length < 2) {
						red = "0" + red;
					};
					//console.log("Padding G");
					while(green.length < 2) {
						green = "0" + green;
					};
					//console.log("Padding B");
					while(blue.length < 2) {
						blue = "0" + blue;
					};
						//console.log(`Colors padded to (0x${red}, 0x${green}, 0x${blue}), concatenating...`);
					return "#" + red + green + blue;
					} else {
					throw new Error(`Received invalid color: ${color}`);
				};
			};

			function linearBlendTwoColorObjects(color1,color2,weight1=0.5) { /*third argument is for color1 and expects a float from 0
																		  to 1, where 0 means "all color2" and 1 means "all color1"*/
				var w1 = Math.min(Math.max(weight1,0),1);
				var red1 = color1.r;
				var green1 = color1.g;
				var blue1 = color1.b;
				var red2 = color2.r;
				var green2 = color2.g;
				var blue2 = color2.b;
				var red3 = (red1 * w1) + (red2 * (1 - w1));
				var green3 = (green1 * w1) + (green2 * (1 - w1));
				var blue3 = (blue1 * w1) + (blue2 * (1 - w1));
				return {r: red3, g: green3, b: blue3};
			};

			function lightenColor(color,offset,outputType="rgb") {
				if(typeof(color) === "string") {
					if(color.length < 10) {
					//console.log(`detected as hex: ${color}`);
						//catch missing octothorpes
						if(!color.startsWith("#")) {
							color = "#" + color;
						};
					//console.log(`octothorpe checked: ${color}`);

						offset = parseFloat(offset);
						if(isNaN(offset)) {
							throw new Error("Offset is NaN");
						};

						var oldColor = color; //for error display
						color = hexToRGB(color);
						if(color === null) {
							throw new Error(`hexToRGB(color) was null (${oldColor}, maybe it's an invalid hex triplet?)`);
						};

					//console.log("converted color: " + JSON.stringify(color));
						var red = color.r + offset;
						var green = color.g + offset;
						var blue = color.b + offset;
					//console.log(`altered color: rgb(${red},${green},${blue})`);

						//rounding and bounding
						red = Math.round(red);
						green = Math.round(green);
						blue = Math.round(blue);
					//console.log(`rounded color: rgb(${red},${green},${blue})`);
						red = bound(red,0,255)
						green = bound(green,0,255)
						blue = bound(blue,0,255)
					//console.log(`bounded color: rgb(${red},${green},${blue})`);

						color = {r: red, g: green, b: blue};

						switch(outputType.toLowerCase()) {
							case "rgb":
								return `rgb(${red},${green},${blue})`;
								break;
							case "hex":
								return rgbToHex(color);
								break;
							case "json":
								return color;
								break;
							default:
								throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
						};
					} else {
						if(color.startsWith("rgb(")) {
							color = convertColorFormats(color,"json"); //object conversion
						//console.log(`color converted to object: ${JSON.stringify(color)}`);

							offset = parseFloat(offset);
							if(isNaN(offset)) {
								throw new Error("Offset is NaN");
							};

							var red = color.r + offset;
							var green = color.g + offset;
							var blue = color.b + offset;
						//console.log(`altered color: rgb(${red},${green},${blue})`);

							//rounding and bounding
							red = Math.round(red);
							green = Math.round(green);
							blue = Math.round(blue);
						//console.log(`rounded color: rgb(${red},${green},${blue})`);
							red = bound(red,0,255)
							green = bound(green,0,255)
							blue = bound(blue,0,255)
						//console.log(`bounded color: rgb(${red},${green},${blue})`);

							color = {r: red, g: green, b: blue};

							switch(outputType.toLowerCase()) {
								case "rgb":
									return `rgb(${red},${green},${blue})`;
									break;
								case "hex":
									return rgbToHex(color);
									break;
								case "json":
									return color;
									break;
								default:
									throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
							};
						} /*else if(color.startsWith("hsl")) {
							throw new Error("HSL is not implemented yet");
						}*/ else {
							throw new Error('Color must be of the type "rgb(red,green,blue)"'/* or "hsl(hue,saturation%,luminance%)"*/);
						};
					};
				} else if(typeof(color) === "object") {
					if(typeof(color.r) === "undefined" || typeof(color.g) === "undefined" || typeof(color.b) === "undefined") {
						throw new Error("Color must be of the form {r: red, g: green, b: blue}");
					};

				//console.log("received color: " + JSON.stringify(color));
					var red = color.r + offset;
					var green = color.g + offset;
					var blue = color.b + offset;
				//console.log(`altered color: rgb(${red},${green},${blue})`);

					//rounding and bounding
					red = Math.round(red);
					green = Math.round(green);
					blue = Math.round(blue);
				//console.log(`rounded color: rgb(${red},${green},${blue})`);
					red = bound(red,0,255)
					green = bound(green,0,255)
					blue = bound(blue,0,255)
				//console.log(`bounded color: rgb(${red},${green},${blue})`);

					color = {r: red, g: green, b: blue};

					switch(outputType.toLowerCase()) {
						case "rgb":
							return `rgb(${red},${green},${blue})`;
							break;
						case "hex":
							return rgbToHex(color);
							break;
						case "json":
							return color;
							break;
						default:
							throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
					};
				};
			};

			function rgbObjectToString(color,stripAlpha=false) {
				if(typeof(color) !== "object") {
					throw new Error("Input color is not an object");
				};
				var red = color.r;
				var green = color.g;
				var blue = color.b;
					//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
				red = Math.round(red);
				green = Math.round(green);
				blue = Math.round(blue);
					//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
				red = bound(red,0,255)
				green = bound(green,0,255)
				blue = bound(blue,0,255)
					//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
				return `rgb(${red},${green},${blue})`
			};

			function convertColorFormats(color,outputType="rgb",stripAlpha=false) {
				if(typeof(color) === "undefined") {
					//console.log("Warning: An element has an undefined color. Unfortunately, due to how the code is structured, I can't say which one.");
					//color = "#FF00FF";
					throw new Error("Color is undefined!");
				};
				//console.log("Logged color for convertColorFormats: " + color);
				var oldColor = color;
				var bytes,r,g,b,a;
				if(typeof(color) === "string") {
					//Hex input case
					
					if(color.length < 10) {
						//a proper hex quadruplet is still shorter than the shortest proper rgb() string
						//console.log(`detected as hex: ${color}`);
							//catch missing octothorpes
							if(!color.startsWith("#")) {
								color = "#" + color;
							};
						//console.log(`octothorpe checked: ${color}`);

						if(oldColor.length < 6) {
							bytes = oldColor.toLowerCase().match(/[a-z0-9]/g).map(x => parseInt(x.concat(x),16));
						} else {
							bytes = oldColor.toLowerCase().match(/[a-z0-9]{2}/g).map(x => parseInt(x,16));
						};
						r = bytes[0];
						g = bytes[1];
						b = bytes[2];
						if(bytes.length > 3) {
							a = bytes[3] / 255;
						} else {
							a = null
						};
						if(stripAlpha) { a = null };
						//to JSON for ease of use
						color = {"r": r, "g": g, "b": b};
						if(typeof(a) == "number") { color["a"] = a };
					} else {					
						//otherwise assume rgb() input
						bytes = color.match(/[\d\.]+/g);
						if(typeof(bytes?.map) == "undefined") {
							console.log(bytes);
							bytes = [255,0,255]
						} else {
							bytes = bytes.map(x => Number(x));
						};
						r = bytes[0];
						g = bytes[1];
						b = bytes[2];
						if(bytes.length > 3) {
							a = bytes[3];
							if(a > 1) {
								a /= 255
							}
						} else {
							a = null
						};
						if(stripAlpha) { a = null };
						//to JSON for ease of use
						color = {"r": r, "g": g, "b": b}
						if(typeof(a) == "number") { color["a"] = a };
					};
				} else if(Array.isArray(color)) {
					bytes = color;
					r = bytes[0];
					g = bytes[1];
					b = bytes[2];
					if(bytes.length > 3) {
						a = bytes[3];
						if(a > 1) {
							a /= 255
						}						
					} else {
						a = null
					};
					if(stripAlpha) { a = null };
					//to JSON for ease of use
					color = {"r": r, "g": g, "b": b}
					if(typeof(a) == "number") { color["a"] = a };
				} else if(typeof(color) == "object") {
					//variable mappings only
					r = color.r;
					g = color.g;
					b = color.b;
					if(typeof(color.a) == "number") {
						a = color.a;
					} else {
						a = null
					};
					if(stripAlpha) { a = null }
				};
				//Colors are now objects

				switch(outputType.toLowerCase()) {
					case "rgb":
					case "rgba":
						var _r,_g,_b,_a;
						_r = r;
						_g = g;
						_b = b;
						if(typeof(a) == "number") { _a = a } else { _a = null };
						var values;
						if(stripAlpha || _a == null) {
							values = [_r,_g,_b];
						} else {
							values = [_r,_g,_b,_a];
						};
						for(var i = 0; i <= 2; i++) {
							values[i] = Math.round(values[i])
						};
						return (typeof(a) == "number" ? "rgba" : "rgb") + `(${values.join(",")})`
					case "hex":
						var _r,_g,_b,_a;
						_r = r;
						_g = g;
						_b = b;
						if(typeof(a) == "number") { _a = Math.round(a * 255) } else { _a = null };
						var bytesToBe;
						if(stripAlpha || _a == null) {
							bytesToBe = [_r,_g,_b];
						} else {
							bytesToBe = [_r,_g,_b,_a];
						};
						return "#" + bytesToBe.map(x => Math.round(x).toString(16).padStart(2,"0")).join("");
					case "json":
						return color;
					case "array":
						return Object.values(color);
						break;
					default:
						throw new Error("outputType must be \"rgb\", \"hex\", \"json\", or \"array\"");
				}
			};

			function rgbHexCatcher(color) {
				return convertColorFormats(color,"rgb");
			};

			function _rgbHexCatcher(color) {
				return convertColorFormats(color,"rgb");
			};


			function averageColorObjects(color1,color2,weight1=0.5) { //misnomer, actually a linear interpolation but it's too late to rename that
				//third argument is for color1 and expects a float from 0 to 1, where 0 means "all color2" and 1 means "all color1"
				//(backwards from how it should work)
				var w1 = Math.min(Math.max(weight1,0),1)
				var red1 = color1.r
				var green1 = color1.g
				var blue1 = color1.b
				var red2 = color2.r
				var green2 = color2.g
				var blue2 = color2.b
				var red3 = (red1 * w1) + (red2 * (1 - w1))
				var green3 = (green1 * w1) + (green2 * (1 - w1))
				var blue3 = (blue1 * w1) + (blue2 * (1 - w1))
				return {r: red3, g: green3, b: blue3}
			};

			function lerpColors(color1,color2,outputType="rgb",weight1=0.5) {
				color1 = convertColorFormats(color1,"json");
				color2 = convertColorFormats(color2,"json");
				theColor = averageColorObjects(color1,color2,weight1);
				return convertColorFormats(theColor,outputType);
			};

			function multiplyColors(color1,color2,outputType="rgb") {
				//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
				if(typeof(color1) !== "object") {
					color1 = convertColorFormats(color1,"json");
				};
				if(typeof(color2) !== "object") {
					color2 = convertColorFormats(color2,"json");
				};
				var finalR = Math.round(color1.r * (color2.r/255));
				var finalG = Math.round(color1.g * (color2.g/255));
				var finalB = Math.round(color1.b * (color2.b/255));
				var finalColor = {r: finalR, g: finalG, b: finalB};
				switch(outputType.toLowerCase()) {
					case "rgb":
						return `rgb(${finalColor.r},${finalColor.g},${finalColor.b})`;
						break;
					case "hex":
						return rgbToHex(finalColor);
						break;
					case "json":
						return finalColor;
						break;
					default:
						throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
				};
			};

			function divideColors(color1,color2,outputType="rgb") { //color2 is the divisor and color1 the dividend (base/original color)
				//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
				if(typeof(color1) !== "object") {
					color1 = convertColorFormats(color1,"json");
				};
				if(typeof(color2) !== "object") {
					color2 = convertColorFormats(color2,"json");
				};
				var finalR = bound(Math.round(255 / (color2.r / color1.r)),0,255);
				var finalG = bound(Math.round(255 / (color2.g / color1.g)),0,255);
				var finalB = bound(Math.round(255 / (color2.b / color1.b)),0,255);
				if(isNaN(finalR)) { finalR = 255 };
				if(isNaN(finalG)) { finalG = 255 };
				if(isNaN(finalB)) { finalB = 255 };
				var finalColor = {r: finalR, g: finalG, b: finalB};
				switch(outputType.toLowerCase()) {
					case "rgb":
						return `rgb(${finalColor.r},${finalColor.g},${finalColor.b})`;
						break;
					case "hex":
						return rgbToHex(finalColor);
						break;
					case "json":
						return finalColor;
						break;
					default:
						throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
				};
			};

			function addColors(color1,color2,outputType="rgb") {
				//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
				if(typeof(color1) !== "object") {
					color1 = convertColorFormats(color1,"json");
				};
				if(typeof(color2) !== "object") {
					color2 = convertColorFormats(color2,"json");
				};
				var finalR = bound(Math.round(color1.r + color2.r),0,255)
				var finalG = bound(Math.round(color1.g + color2.g),0,255)
				var finalB = bound(Math.round(color1.b + color2.b),0,255)
				var finalColor = {r: finalR, g: finalG, b: finalB};
				switch(outputType.toLowerCase()) {
					case "rgb":
						return `rgb(${finalColor.r},${finalColor.g},${finalColor.b})`;
						break;
					case "hex":
						return rgbToHex(finalColor);
						break;
					case "json":
						return finalColor;
						break;
					default:
						throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
				};
			};

			function subtractColors(color1,color2,outputType="rgb") {
				//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
				if(typeof(color1) !== "object") {
					color1 = convertColorFormats(color1,"json");
				};
				if(typeof(color2) !== "object") {
					color2 = convertColorFormats(color2,"json");
				};
				var finalR = bound(Math.round(color1.r - color2.r),0,255)
				var finalG = bound(Math.round(color1.g - color2.g),0,255)
				var finalB = bound(Math.round(color1.b - color2.b),0,255)
				var finalColor = {r: finalR, g: finalG, b: finalB};
				switch(outputType.toLowerCase()) {
					case "rgb":
						return `rgb(${finalColor.r},${finalColor.g},${finalColor.b})`;
						break;
					case "hex":
						return rgbToHex(finalColor);
						break;
					case "json":
						return finalColor;
						break;
					default:
						throw new Error("outputType must be \"rgb\", \"hex\", \"json\"");
				};
			};

			function averageRgbPrefixedColorArray(colorArray,returnObject=false) { //array of rgb()s to single rgb() of average color
				//averageRgbPrefixedColorArray(["rgb(255,0,0)", "rgb(0,0,0)", "rgb(0,0,255)"]);
				//console.log("Averaging started");
				var reds = [];
				var greens = [];
				var blues = [];
				for(k = 0; k < colorArray.length; k++) {
					//console.log("Average function: Executing catcher on " + colorArray);
					var color = convertColorFormats(colorArray[k]);
					//console.log("Logged color for aRPCA: " + color);
					color = color.split(","); 
					var red = parseFloat(color[0].substring(4));
					reds.push(red)
					var green = parseFloat(color[1]);
					greens.push(green)
					var blue = parseFloat(color[2].slice(0,-1));
					blues.push(blue)
				};
				redAverage = Math.round(averageNumericArray(reds));
				greenAverage = Math.round(averageNumericArray(greens));
				blueAverage = Math.round(averageNumericArray(blues));
				var output; 
				returnObject ? output = {r: redAverage, g: greenAverage, b: blueAverage} : output = `rgb(${redAverage},${greenAverage},${blueAverage})`;
				//console.log("Averaging finished, product: " + output);
				return output;
			};

			//https://stackoverflow.com/questions/46432335/hex-to-hsl-convert-javascript
			function rgbStringToHSL(rgb,outputType="array") { //Originally a hex-to-HSL function, edited to take RGB and spit out an array
				//console.log("HSLing some RGBs");
				var result = rgbStringToUnvalidatedObject(rgb);

				var r = result.r;
				var g = result.g;
				var b = result.b;

				r /= 255, g /= 255, b /= 255;
				var max = Math.max(r, g, b), min = Math.min(r, g, b);
				var h, s, l = (max + min) / 2;

				if(max == min){
					h = s = 0; // achromatic
				} else {
					var d = max - min;
					s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
					switch(max) {
						case r: h = (g - b) / d + (g < b ? 6 : 0); break;
						case g: h = (b - r) / d + 2; break;
						case b: h = (r - g) / d + 4; break;
					}
					h /= 6;
				};

				s = s*100;
				s = Math.round(s);
				l = l*100;
				l = Math.round(l);
				h = Math.round(360*h);

				//var colorInHSL = 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
				//Edit to return an array
				switch(outputType.toLowerCase()) {
					case "array":
						return [h,s,l];
						break;
					case "hsl":
						return `hsl(${h},${s}%,${l}%)`;
						break;
					case "json":
						return {h: h, s: s, l: l};
					default:
						throw new Error("outputType must be \"array\", \"hsl\", or \"json\"");
						break;				
				};
				//console.log("HSL output "+ colorInHSL + ".");

			};

			function normalizeColorToHslObject(color,arrayType=null) {
				var ambiguousArrayError = "changeSaturation can't tell if the array input is supposed to be RGB or HSL. Please use an \"arrayType\" argument of \"rgb\" or \"hsl\".";
				var isHsl = false;
				if(Array.isArray(color)) {
					if(arrayType === null) {
						throw new Error(ambiguousArrayError);
					} else if(arrayType === "rgb") {
						color = `rgb(${color[0]},${color[1]},${color[2]})`;
						color = rgbStringToHSL(color,"json"); //rgb arr to hsl obj
					} else if(arrayType === "hsl") {
						color = {h: color[0], s: color[1], l: color[2]}; //hsl arr to hsl obj
					} else {
						throw new Error(ambiguousArrayError);
					};
				} else {
					//by this point, any array cases would have been handled, leaving just hex (rgb), json rgb, json hsl, string rgb, and string hsl 
					if(typeof(color) === "string") {
						if(color.length < 10) { //detect hex: assume hex triplet if too short to be a well-formed rgb()
							if(!color.startsWith("#")) {
								color = "#" + color; //catch missing #
							};
							isHsl = false;
						};
						if(color.startsWith("rgb(")) { //detect rgb(): self-explanatory
							isHsl = false;
						};
						if(color.startsWith("hsl(")) { //detect hsl(): self-explanatory
							isHsl = true;
						};
					} else if(typeof(color) === "object") {
						if(typeof(color.r) !== "undefined") { //detect {r,g,b}: check for r key
							isHsl = false;
						};
						if(typeof(color.h) !== "undefined") { //detect {h,s,l}: check for h key
							isHsl = true;
						};
					};
					if(!isHsl) {
						color = convertColorFormats(color,"rgb"); //make any RGBs rgb()
						color = rgbStringToHSL(color,"json"); //make that rgb() an {h,s,l}
					} else { //by this point, it would have to either be a string or an object
						if(typeof(color) === "string") { //if it's a string
							color = hslColorStringToObject(color) //now it's an object
						};
					};
				};
				return color;		
			};

			function convertHslObjects(color,outputType="rgb") {
				if(color == null) { console.error("convertHslObjects: Color is null"); color = {h: 300, s: 100, l: 50} };
				switch(outputType.toLowerCase()) {
					//RGB cases
					case "rgb":
						color = convertColorFormats(hslToHex(...Object.values(color)),"json"); //hsl to hex, hex to rgb_json, and rgb_json to rgb()
						return `rgb(${color.r},${color.g},${color.b})`;
						break;
					case "hex":
						color = hslToHex(...Object.values(color)); //hsl to hex
						return color;
						break;
					case "rgbjson":
					case "rgb-json":
					case "rgb_json":
						color = hexToRGB(hslToHex(...Object.values(color))); //hsl to hex and hex to rgb_json
						return color; 
						break;
					case "rgbarray":
					case "rgb-array":
					case "rgb_array":
						color = hexToRGB(hslToHex(...Object.values(color))); //hsl to hex, hex to rgb_json, and rgb_json to rgb_array
						return [color.r, color.g, color.b]; 
						break;
					//HSL cases
					case "hsl":
						//note: color was previously converted to {h, s, l}
						return `hsl(${color.h},${color.s}%,${color.l}%)`;
						break;
					case "hsljson":
					case "hsl-json":
					case "hsl_json":
						return color; 
						break;
					case "hslarray":
					case "hsl-array":
					case "hsl_array":
						return [color.h, color.s, color.l]; 
						break;
					default:
						throw new Error("outputType must be \"rgb\", \"hex\", \"rgb_json\", \"rgb_array\", \"hsl\", \"hsl_json\", or \"hsl_array\"");
				}
			};

			function changeSaturation(color,saturationChange,operationType="add",outputType="rgb",arrayType=null,doRounding=true) {
				color = normalizeColorToHslObject(color,arrayType);
				//only {h,s,l} should exist now

				//Math
				switch(operationType.toLowerCase()) {
					case "+":
					case "add":
						color.s += saturationChange;
						break;
					case "-":
					case "subtract":
						color.s -= saturationChange;
						break;
					case "*":
					case "x":
					case "×":
					case "multiply":
						color.s *= saturationChange;
						break;
					case "/":
					case "÷":
					case "divide":
						color.s /= saturationChange;
						break;
					case "=":
					case "set":
						color.s = saturationChange;
						break;
					case ">":
					case ">=":
					case "min": //lower-bounds the color
						color.s = Math.max(color.s,saturationChange); //math.max to bound it to the higher of the input number or the existing color
						break;
					case "<":
					case "<=":
					case "max": //upper-bounds the color
						color.s = Math.min(color.s,saturationChange); //math.min to bound it to the lower of the input number or the existing color
						break;
					default:
						throw new Error("Operation must be \"add\", \"subtract\", \"multiply\", \"divide\", \"set\", \"min\", or \"max\"");
				};

				color.h = doRounding ? Math.round(color.h % 360) : color.h % 360;
				color.s = doRounding ? Math.round(bound(color.s,0,100)) : color.s,0,100;
				color.l = doRounding ? Math.round(bound(color.l,0,100)) : color.l,0,100;

				return convertHslObjects(color,outputType)
			};

			function changeLuminance(color,luminanceChange,operationType="add",outputType="rgb",arrayType=null,doRounding=true) {
				color = normalizeColorToHslObject(color,arrayType);
				//only {h,s,l} should exist now

				//Math
				switch(operationType.toLowerCase()) {
					case "+":
					case "add":
						color.l += luminanceChange;
						break;
					case "-":
					case "subtract":
						color.l -= luminanceChange;
						break;
					case "*":
					case "x":
					case "×":
					case "multiply":
						color.l *= luminanceChange;
						break;
					case "/":
					case "÷":
					case "divide":
						color.l /= luminanceChange;
						break;
					case "=":
					case "set":
						color.l = luminanceChange;
						break;
					case ">":
					case ">=":
					case "min":
						color.l = Math.max(color.l,luminanceChange);
						break;
					case "<":
					case "<=":
					case "max":
						color.l = Math.min(color.l,luminanceChange);
						break;
					default:
						throw new Error("Operation must be \"add\", \"subtract\", \"multiply\", \"divide\", \"set\", \"min\", or \"max\"");
				};

				color.h = doRounding ? Math.round(color.h % 360) : color.h % 360;
				color.s = doRounding ? Math.round(bound(color.s,0,100)) : color.s,0,100;
				color.l = doRounding ? Math.round(bound(color.l,0,100)) : color.l,0,100;

				return convertHslObjects(color,outputType);
			};

			function changeHue(color,hueChange,operationType="add",outputType="rgb",arrayType=null,doRounding=true) {
				color = normalizeColorToHslObject(color,arrayType);
				//only {h,s,l} should exist now

				//Math
				switch(operationType.toLowerCase()) {
					case "+":
					case "add":
						color.h += hueChange;
						break;
					case "-":
					case "subtract":
						color.h -= hueChange;
						break;
					case "*":
					case "x":
					case "×":
					case "multiply":
						color.h *= hueChange;
						break;
					case "/":
					case "÷":
					case "divide":
						color.h /= hueChange;
						break;
					case "=":
					case "set":
						color.h = hueChange;
						break;
					case ">":
					case ">=":
					case "min":
						color.h = Math.max(color.h,hueChange);
						break;
					case "<":
					case "<=":
					case "max":
						color.h = Math.min(color.h,hueChange);
						break;
					default:
						throw new Error("Operation must be \"add\", \"subtract\", \"multiply\", \"divide\", \"set\", \"min\", or \"max\"");
				};

				color.h = doRounding ? Math.round(color.h % 360) : color.h % 360;
				color.s = doRounding ? Math.round(bound(color.s,0,100)) : color.s,0,100;
				color.l = doRounding ? Math.round(bound(color.l,0,100)) : color.l,0,100;

				return convertHslObjects(color,outputType);
			};

			function colorToHsl(color,outputType="json") {
				if(typeof(color.h) == "number" && typeof(color.s) == "number" && typeof(color.l) == "number") {
					return color
				};
				color = convertColorFormats(color,"rgb");
				color = rgbStringToHSL(color,outputType);
				return color;
			};

			//https://stackoverflow.com/questions/36721830/convert-hsl-to-rgb-and-hex
			function hslToHex(h, s, l) { //h, s, l params to hex triplet
			  //console.log(`Hexing some HSLs (the HSLs are ${h},${s},${l})`)
			  s = bound(s,0,100); //limit to 0-100
			  l = bound(l,0,100);
			  l /= 100;
			  var a = s * Math.min(l, 1 - l) / 100;
			  var f = n => {
				var k = (n + h / 30) % 12;
				var color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
				return Math.round(255 * color).toString(16).padStart(2, '0');   // convert to Hex and prefix "0" if needed
			  };
			  //console.log(`Hexed to #${f(0)}${f(8)}${f(4)}`)
			  return `#${f(0)}${f(8)}${f(4)}`;
			};

		//Pixels
		
			function tryMoveAndReturnBlockingPixel(pixel,nx,ny,leaveBehind,force) {
				if(outOfBounds(nx,ny)) { return false };
				if(isEmpty(nx,ny,false)) { return tryMove(pixel,nx,ny,leaveBehind,force) };
				return pixelMap[nx][ny]
			};

			function exposedToAir(pixel) {	
				return (isEmpty(pixel.x+1,pixel.y) || isEmpty(pixel.x-1,pixel.y) || isEmpty(pixel.x,pixel.y+1) || isEmpty(pixel.x,pixel.y-1));
			};

			function tryTarnish(pixel,element,chance) {
				if(exposedToAir(pixel)) {
					if(Array.isArray(element)) {
						if(Math.random() < chance) {
							changePixel(pixel,randomChoice(element));
						};
					} else {
						if(Math.random() < chance) {
							changePixel(pixel,element);
						};
					};
				};
			};

			//Try to create a pixel, return true if it could be created and false if it couldn't
			function tryCreatePixel(elementInput,x,y) {
				//array handling
				if(elementInput.includes(",")) { //CSTA
					elementInput = elementInput.split(",");
				};
				if(Array.isArray(elementInput)) { //if element list
					elementInput = elementInput.filter(function(e) {
						return elementExists(e);
					});
					if(elementInput.length === 0) { throw new Error("elementInput has no existing elements") };
					elementInput = randomChoice(elementInput);
				};

				//existence check
				if(!elementExists(elementInput)) {
					throw new Error("Element " + elementInput + " doesn't exist!");
				};

				//actual creation check
				if(isEmpty(x,y)) {
					createPixel(elementInput,x,y);
					return true;
				} else {
					return false;
				};
			};

			function tryCreatePixelReturn(elementInput,x,y) {
				//array handling
				if(elementInput.includes(",")) { //CSTA
					elementInput = elementInput.split(",");
				};
				if(Array.isArray(elementInput)) { //if element list
					elementInput = elementInput.filter(function(e) {
						return elementExists(e);
					});
					if(elementInput.length === 0) { throw new Error("elementInput has no existing elements") };
					elementInput = randomChoice(elementInput);
				};

				//existence check
				if(!elementExists(elementInput)) {
					throw new Error("Element " + elementInput + " doesn't exist!");
				};

				//actual creation check
				if(isEmpty(x,y)) {
					return createPixelReturn(elementInput,x,y);
				} else {
					return false;
				};
			};

			function createPixelReturn(elementIn,x,y) { //sugar
				var element = elementIn; while(element instanceof Array) { element = randomChoice(element) };
				var newPixel = new Pixel(x, y, element);
				currentPixels.push(newPixel);
				checkUnlock(element);
				return newPixel;
			};

			function changePixelReturn(pixel,element,changetemp=true) {
				if(typeof(elements[element]) == "undefined") {
					if(doLog) { console.error(`Something tried to change a pixel of ${pixel.element} at (${pixel.x},${pixel.y}) to nonexistent element "${element}"`) };
					return false;
				};
				pixel.element = element;
				pixel.color = pixelColorPick(pixel);
				pixel.start = pixelTicks;
				var elementInfo = elements[element];
				if (elementInfo.burning == true) {
					pixel.burning = true;
					pixel.burnStart = pixelTicks;
				}
				else if (pixel.burning && !elementInfo.burn) {
					delete pixel.burning;
					delete pixel.burnStart;
				}
				delete pixel.origColor; // remove stain
				if (pixel.r && !elementInfo.rotatable) {
					delete pixel.r;
				}
				if (pixel.flipX && !elementInfo.flippableX) {
					delete pixel.flipX;
				}
				if (pixel.flipY && !elementInfo.flippableY) {
					delete pixel.flipY;
				}
				// If elementInfo.flippableX, set it to true or false randomly
				if (elementInfo.flipX !== undefined) { pixel.flipX = elementInfo.flipX }
				else if (elementInfo.flippableX) {
					pixel.flipX = Math.random() >= 0.5;
				}
				// If elementInfo.flippableY, set it to true or false randomly
				if (elementInfo.flipY !== undefined) { pixel.flipY = elementInfo.flipY }
				else if (elementInfo.flippableY) {
					pixel.flipY = Math.random() >= 0.5;
				}
				if (elementInfo.temp != undefined && changetemp) {
					pixel.temp = elementInfo.temp;
					pixelTempCheck(pixel)
				}
				// If elementInfo.properties, set each key to its value
				if (elementInfo.properties !== undefined) {
					for (var key in elementInfo.properties) {
						// If it is an array or object, make a copy of it
						if (typeof elementInfo.properties[key] == "object") {
							pixel[key] = JSON.parse(JSON.stringify(elementInfo.properties[key]));
						}
						else {
							pixel[key] = elementInfo.properties[key];
						}
					}
				}
				checkUnlock(element);
				return pixel;
			};

			function storeFirstTouchingElement(pixel,propertyName,copyTemp=true,spread=true) {
				var info = elements[pixel.element];
				if(pixel[propertyName]) {
					return false;
				};
				for(i = 0; i < adjacentCoords.length; i++) {
					var newCoords = {x: pixel.x+adjacentCoords[i][0], y: pixel.y+adjacentCoords[i][1]};

					if (!isEmpty(newCoords.x,newCoords.y,true)) {
						newPixel = pixelMap[newCoords.x][newCoords.y];
						if (info.ignore && info.ignore.indexOf(newPixel.element) !== -1) {
							continue;
						};
						if (newPixel.element != pixel.element && newPixel.element != "wire") {
							pixel[propertyName] = newPixel.element;
							if(copyTemp) { pixel.temp = newPixel.temp };
							return newPixel.element;
						}
						else if (newPixel[propertyName] && spread) {
							pixel[propertyName] = newPixel[propertyName];
							pixel.temp = newPixel.temp;
							return newPixel[propertyName];
						}
					}
				};
			};

			function breakPixel(pixel,changeTemp=false,defaultBreakIntoDust=false) {
				var result = elements[pixel.element].breakInto;
				if (result === undefined) {if(defaultBreakIntoDust) { result = "dust" } else { return }};
				// if it is an array, choose a random item, else just use the value
				while (Array.isArray(result)) {
					result = randomChoice(result);
				};
				// change the pixel to the result
				if (result === null) {
					deletePixel(pixel.x,pixel.y);
					return
				};
				if (elements[pixel.element].breakIntoColor) {
					var oldelement = pixel.element;
					changePixel(pixel,result);
					pixel.color = pixelColorPick(pixel, elements[oldelement].breakIntoColor);
				}
				else {
					changePixel(pixel,result);
				}
			}

			defaultHardness = 0.3;
			function tryBreak(pixel,changetemp=false,defaultBreakIntoDust=false) {
				var info = elements[pixel.element];
				var hardness = defaultHardness;
				if(typeof(info.hardness) === "number") {
					hardness = info.hardness;
				};
				hardness = 1 - hardness; //invert hardness, so a hardness of 0 becomes a 100% chance and a hardness of 1 becomes a 0% chance
				if(Math.random() < hardness) {
					return breakPixel(pixel,changetemp=false,defaultBreakIntoDust=false);
				} else {
					return false;
				};
			};

			function reactionStealer(pixel,newPixel,reactionTarget) {
				if(!elements[reactionTarget]) {
					throw new Error(`No such element ${reactionTarget}!`);
				};
				if(typeof(newPixel) === "undefined") { //timing issue?
					return false;
				};
				var newElement = newPixel.element;
				var newInfo = elements[newElement];
				if(typeof(newInfo.reactions) === "undefined") {
					return false;
				};
				if(typeof(newInfo.reactions[reactionTarget]) === "undefined") {
					return false;
				};
				var pixel2 = pixel;
				var pixel1 = newPixel;
				var r = newInfo.reactions[reactionTarget];

				if (r.setting && settings[r.setting]===0) {
					return false;
				}
				// r has the attribute "y" which is a range between two y values
				// r.y example: [10,30]
				// return false if y is defined and pixel1's y is not in the range
				if (r.tempMin !== undefined && pixel1.temp < r.tempMin) {
					return false;
				}
				if (r.tempMax !== undefined && pixel1.temp > r.tempMax) {
					return false;
				}
				if (r.charged && !pixel.charge) {
					return false;
				}
				if (r.chance !== undefined && Math.random() > r.chance) {
					return false;
				}
				if (r.y !== undefined && (pixel1.y < r.y[0] || pixel1.y > r.y[1])) {
					return false;
				}
				if (r.elem1 !== undefined) {
					// if r.elem1 is an array, set elem1 to a random element from the array, otherwise set it to r.elem1
					if (Array.isArray(r.elem1)) {
						var elem1 = r.elem1[Math.floor(Math.random() * r.elem1.length)];
					} else { var elem1 = r.elem1; }

					if (elem1 == null) {
						deletePixel(pixel1.x,pixel1.y);
					}
					else {
						changePixel(pixel1,elem1);
					}
				}
				if (r.charge1) { pixel1.charge = r.charge1; }
				if (r.temp1) { pixel1.temp += r.temp1; pixelTempCheck(pixel1); }
				if (r.color1) { // if it's a list, use a random color from the list, else use the color1 attribute
					pixel1.color = pixelColorPick(pixel1, Array.isArray(r.color1) ? r.color1[Math.floor(Math.random() * r.color1.length)] : r.color1);
				}
				if (r.attr1) { // add each attribute to pixel1
					for (var key in r.attr1) {
						pixel1[key] = r.attr1[key];
					}
				}
				if (r.elem2 !== undefined) {
					// if r.elem2 is an array, set elem2 to a random element from the array, otherwise set it to r.elem2
					if (Array.isArray(r.elem2)) {
						var elem2 = r.elem2[Math.floor(Math.random() * r.elem2.length)];
					} else { var elem2 = r.elem2; }

					if (elem2 == null) {
						deletePixel(pixel2.x,pixel2.y);
					}
					else {
						changePixel(pixel2,elem2);
					}
				}
				if (r.charge2) { pixel2.charge = r.charge2; }
				if (r.temp2) { pixel2.temp += r.temp2; pixelTempCheck(pixel2); }
				if (r.color2) { // if it's a list, use a random color from the list, else use the color2 attribute
					pixel2.color = pixelColorPick(pixel2, Array.isArray(r.color2) ? r.color2[Math.floor(Math.random() * r.color2.length)] : r.color2);
				}
				if (r.attr2) { // add each attribute to pixel2
					for (var key in r.attr2) {
						pixel2[key] = r.attr2[key];
					}
				}
				if (r.func) { r.func(pixel1,pixel2); }
				return r.elem1!==undefined || r.elem2!==undefined;
			};

			function spreadingProperty(pixel,propertyName,whitelist=null) {
				if(isNaN(pixel[propertyName])) {
					pixel[propertyName] = 0;
				};
				var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
				var rX = randomNeighborOffset[0];
				var rY = randomNeighborOffset[1];
				var rfX = pixel.x+rX;
				var rfY = pixel.y+rY;
				if(!isEmpty(rfX,rfY,true)) {
					if(!pixelMap[rfX]) {
						return false;
					};
					var rOtherPixel = pixelMap[rfX][rfY];
					var rOtherElement = rOtherPixel.element;
					if(whitelist === null || (whitelist !== null && whitelist.includes(rOtherElement))) {
						if(typeof(rOtherPixel) === "undefined" || isEmpty(rfX,rfY,true)) {
							return false;
						};
						if(isNaN(pixel[propertyName])) { //should include undefined
							pixel[propertyName] = 0;
						};
						var averageValue = (pixel[propertyName] + rOtherPixel[propertyName]) / 2;
						pixel[propertyName] = averageValue;
						rOtherPixel[propertyName] = averageValue;
					};
				};
				return true;
			};

			function spreadingPropertyReturn(pixel,propertyName,whitelist=null) {
				if(isNaN(pixel[propertyName])) {
					pixel[propertyName] = 0;
				};
				var recipients = []; //will never be more than one but done with [] for forEach
				var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
				var rX = randomNeighborOffset[0];
				var rY = randomNeighborOffset[1];
				var rfX = pixel.x+rX;
				var rfY = pixel.y+rY;
				if(!isEmpty(rfX,rfY,true)) {
					if(!pixelMap[rfX]) {
						return [];
					};
					var rOtherPixel = pixelMap[rfX][rfY];
					var rOtherElement = rOtherPixel.element;
					if(whitelist === null || (whitelist !== null && whitelist.includes(rOtherElement))) {
						if(typeof(rOtherPixel) === "undefined" || isEmpty(rfX,rfY,true)) {
							return [];
						};
						if(isNaN(pixel[propertyName])) { //should include undefined
							pixel[propertyName] = 0;
						};
						var averageValue = (pixel[propertyName] + rOtherPixel[propertyName]) / 2;
						pixel[propertyName] = averageValue;
						rOtherPixel[propertyName] = averageValue;
						recipients.push(rOtherPixel);
					};
				};
				return recipients;
			};

			function swapNumericPropertyValues(pixel1,pixel2,propertyName,whitelist=null) {
				if(!pixel1 || !pixel2) {
					return false;
				};
				if(isNaN(pixel1[propertyName])) {
					pixel1[propertyName] = 0;
				};
				if(isNaN(pixel2[propertyName])) {
					pixel2[propertyName] = 0;
				};
				if(whitelist === null || (whitelist !== null && whitelist.includes(pixel1.element) && whitelist.includes(pixel2.element))) {
					var temp1 = pixel1[propertyName];
					pixel1[propertyName] = pixel2[propertyName];
					pixel2[propertyName] = temp1;
				};
				return true;
			};

		//World

			function getCirclePixels(x,y,radius) {
				return circleCoords(x,y,radius).map(coordinates => pixelMap[coordinates.x]?.[coordinates.y]).filter(function(pixelOrUndefined) { return typeof(pixelOrUndefined) == "object" })
			};

			function getPixelMooreNeighbors(pixel) {
				var coordsToCheck = mooreDonutCoords.map(function(offsets) { return {x: offsets[0]+pixel.x, y: offsets[1]+pixel.y} } );
				var neighbors = [];
				for(var i = 0; i < coordsToCheck.length; i++) {
					var coords = coordsToCheck[i];
					if(outOfBounds(coords.x,coords.y)) {
						continue
					};
					if(isEmpty(coords.x,coords.y,true)) {
						continue
					};
					if(!pixelMap[coords.x]?.[coords.y]) {
						continue
					};
					neighbors.push(pixelMap[coords.x][coords.y])
				};
				return neighbors
			};

			function clonePixel(pixel,newX,newY,replaceExistingPixel=false,returnPixel=false) {
				if(!pixel) { return false };
				if(outOfBounds(newX,newY)) { return false };
				if(isEmpty(newX,newY)) {
					//Do nothing
				} else {
					if(replaceExistingPixel) {
						deletePixel(newX,newY) 
					} else {
						return false
					}
				};
				var newPixel = structuredClone ? structuredClone(pixel) : JSON.parse(JSON.stringify(pixel));
				newPixel.x = newX; newPixel.y = newY;
				pixelMap[newX][newY] = newPixel;
				currentPixels.push(newPixel);
				return returnPixel ? newPixel : true
			};

			function cloneArea(topLeftX,topLeftY,bottomRightX,bottomRightY,newTopLeftX,newTopLeftY,oldPixelHandling_PreClear1_OnlyReplace2_Ignore3=1,errorOnOutOfBounds=false) {
				var results = {"created": 0, "replaced": 0, "deleted": 0, "skipped": 0, "skipped_OOB": 0};
				for(var x = topLeftX; x <= bottomRightX; x++) {
					for(var y = topLeftY; y <= bottomRightY; y++) {
						var relativeOffsetX = x - topLeftX;
						var relativeOffsetY = y - topLeftY;
						var newCoords = {"x": newTopLeftX+relativeOffsetX, "y": newTopLeftY+relativeOffsetY};
						var oldCoords = {"x": x, "y": y};
						var oldCoordsOOB = outOfBounds(oldCoords.x,oldCoords.y);
						var newCoordsOOB = outOfBounds(newCoords.x,newCoords.y);
						if(oldCoordsOOB || newCoordsOOB) {
							if(errorOnOutOfBounds) {
								var message;
								if(oldCoordsOOB && !newCoordsOOB) {
									message = "cloneArea: Source is or extends outside of the canvas."
								} else if(!oldCoordsOOB && newCoordsOOB) {
									message = "cloneArea: Destination is or extends outside of the canvas."
								} else if(oldCoordsOOB && newCoordsOOB) {
									message = "cloneArea: Source and destination are or extend outside of the canvas."
								} else {
									message = "cloneArea: ??? (Something has gone wrong with the OOB handling code.)"
								};
								throw new Error(message)
							} else {
								results.skipped_OOB++;
								continue
							}
						};
						if(isEmpty(newCoords.x,newCoords.y)) {
							//Empty destination, full source
							if(!(isEmpty(oldCoords.x,oldCoords.y))) {
								clonePixel(pixelMap[oldCoords.x][oldCoords.y],newCoords.x,newCoords.y);
								results.created++;
							}
						} else {
							//Full destination, empty source
							if(isEmpty(oldCoords.x,oldCoords.y)) {
								//Delete the blocking pixel only if pre-clearing
								if(oldPixelHandling_PreClear1_OnlyReplace2_Ignore3 == 1) {
									deletePixel(newCoords.x,newCoords.y);
									results.deleted++
								} else {
									results.skipped++
								}
							}
							//Full destination, full source
							else {
								//Delete the blocking pixel if not ignoring (as for the above case, pre-clearing is not ignoring)
								if(oldPixelHandling_PreClear1_OnlyReplace2_Ignore3 !== 3) {
									deletePixel(newCoords.x,newCoords.y);
									//Place the cloned pixel
									clonePixel(pixelMap[oldCoords.x][oldCoords.y],newCoords.x,newCoords.y)
									results.replaced++;
								} else {
									results.skipped++
								};
							}
						}
					}
				};
				return results
			};

			function getEmptyVonNeumannNeighbors(pixel) {
				var neighbors = [];
				var x = pixel.x;
				var y = pixel.y;
				for(var i = 0; i < adjacentCoords.length; i++) {
					var finalX = pixel.x + adjacentCoords[i][0];
					var finalY = pixel.y + adjacentCoords[i][1];
					if(isEmpty(finalX,finalY,false)) {
						neighbors.push([finalX,finalY])
					};
				};
				return neighbors
			};

			function getEmptyMooreNeighbors(pixel) {
				var neighbors = [];
				var x = pixel.x;
				var y = pixel.y;
				for(var i = 0; i < mooreDonutCoords.length; i++) {
					var finalX = pixel.x + mooreDonutCoords[i][0];
					var finalY = pixel.y + mooreDonutCoords[i][1];
					if(isEmpty(finalX,finalY,false)) {
						neighbors.push([finalX,finalY])
					};
				};
				return neighbors
			};

			function breakCircle(x,y,radius,respectHardness=false,changeTemp=false,defaultBreakIntoDust=false) {
				var coords = getCirclePixels(x,y,radius);
				coords.forEach(pixel => respectHardness ? tryBreak(pixel,changeTemp,defaultBreakIntoDust) : breakPixel(pixel,changeTemp,defaultBreakIntoDust))
			};

			function fillCircle(element,x,y,radius,overwrite=false) {
				var coords = circleCoords(x,y,radius);
				var newElement = element;
				if(Array.isArray(newElement)) {
					newElement = newElement[Math.floor(Math.random() * newElement.length)];
				};
				for(i = 0; i < coords.length; i++) {
					coordX = Math.round(coords[i].x);
					coordY = Math.round(coords[i].y);
					if(overwrite && !isEmpty(coordX,coordY,true)) {
						changePixel(pixelMap[coordX][coordY],element);
					};
					if(isEmpty(coordX,coordY,false)) {
						createPixel(element,coordX,coordY);
					};
				};
			};

			function fillCircleReturn(element,x,y,radius,overwrite=false) {
				//console.log("fcr");
				var pixels = [];
				//console.log("pixels initted");
				var coords = circleCoords(x,y,radius);
				//console.log("coords gotten");
				var newElement = element;
				//console.log("element processing");
				if(Array.isArray(newElement)) {
					newElement = newElement[Math.floor(Math.random() * newElement.length)];
				};
				//console.log("element processed");
				for(i = 0; i < coords.length; i++) {
					//console.log("iterator through spots: " + i);
					coordX = Math.round(coords[i].x);
					coordY = Math.round(coords[i].y);
					//console.log(`coord: (${coords[i].x},${coords[i].y})`);
					if(overwrite && !isEmpty(coordX,coordY,true)) {
						//console.log("replaced pixel");
						pixels.push(changePixelReturn(pixelMap[coordX][coordY],newElement));
					};
					if(isEmpty(coordX,coordY,false)) {
						//console.log("created pixel");
						pixels.push(createPixelReturn(newElement,coordX,coordY));
					};
				};
				//console.log("fcr finished");
				//console.log(pixels.map(x => x.element));
				return pixels;
			};

			function isOpenAndOnSurface(x,y,includeBottomBound=true) {
				if(!isEmpty(x,y,false)) {
					return false;
				};
				if(y + 1 == height) {
					return includeBottomBound;
				};
				return !isEmpty(x,y+1,true);
			};

			//Freeze pixel
			function freezePixel(pixel,changetemp=true) {
				var info = elements[pixel.element];
				var result = info.stateLow;
				if (!result) {
					return false
				};
				if(result instanceof Array) {
					result = result.filter(elementExists);
					if(result.length == 0) {
						return false;
					};
				} else {
					if(!(elementExists(result))) {
						return false;
					};
				};

				while(result instanceof Array) {
					result = randomChoice(result);
				};

				changePixel(pixel,result,changetemp);
				return true;
			};

			//Melt pixel
			function meltPixel(pixel,changetemp=true) {
				var info = elements[pixel.element];
				var result = info.stateHigh;
				if (!result) {
					return false
				};
				if(result instanceof Array) {
					result = result.filter(elementExists);
					if(result.length == 0) {
						return false;
					};
				} else {
					if(!(elementExists(result))) {
						return false;
					};
				};

				while(result instanceof Array) {
					result = randomChoice(result);
				};

				changePixel(pixel,result,changetemp);
				return true;
			};

		//Logic

			function xor(c1,c2) {
				if(!!c1 && !c2) {
					return true;
				} else if(!c1 && !!c2) {
					return true;
				} else {
					return false;
				};
			};

		//currentPixels operations

			function findInCurrentPixels(x,y) {
				var pixel = currentPixels.filter(function(pixelObject) {
					return pixelObject.x == x && pixelObject.y == y;
				});
				if(pixel.length <= 0) {
					return undefined;
				};
				if(pixel.length > 1) {
					pixel.length = 1;
				};
				pixel = pixel[0];
				return pixel;
			};

			function filterCurrentPixels(filterFunction) {
				return currentPixels.filter(filterFunction);
			};

			//Filter test functions

			function _filterTest_xIsTwenty(pixel) {
				return pixel.x == 20;
			};

			function _filterTest_tempIsOdd(pixel) {
				return Math.trunc(pixel.temp) % 2 == 1;
			};

			function _filterTest_redRock(pixel) {
				var color = rgbStringToHSL(convertColorFormats(pixel.color,"rgb"),"json");
				var isRed = ((color.h % 360) >= 350) || ((color.h % 360) <= 10);
				var isVivid = (color.s > 30);
				var isBright = (color.l > 20);
				return isRed && isVivid && isBright;
			};

			//Ghost pixel repair function
			function rebuildCurrentPixels() {
				var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
				for(pmi = 0; pmi < pixelMap.length; pmi++) {
					var pixelMapPart = pixelMap[pmi];
					for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
						var pixelMapUnit = pixelMapPart[pmj];
						if(typeof(pixelMapUnit) === "object") {
							if(pixelMapUnit !== null) {
								currPix.push(pixelMapUnit);
							};
						};
					};
				};
				currentPixels = currPix;
			};


		//Sugar functions

			function newElement(name="element_name",color="#FF00FF",otherProps={}) {
				elements[name] = {
					color: color,
				};
				for(property in otherProps) {
					elements[name][property] = otherProps[property];
				};
				return elements[name];
			};

		//Fixes

			//fix -1-caused ghost pixels
			function deletePixel(x,y) {
				if(isEmpty(x,y,true)) { return false };
				// remove pixelMap[x][y] from currentPixels
				var pixelIndex = currentPixels.indexOf(pixelMap[x][y]);
				if(pixelIndex !== -1) {
					currentPixels.splice(pixelIndex,1)
					if (pixelMap[x][y]) { delete pixelMap[x][y] };
				};
				//if (pixelMap[x][y]) {pixelMap[x][y].del = true}
				//if (pixelMap[x][y]) { delete pixelMap[x][y] };
				/*for (var i = 0; i < currentPixels.length; i++) {
					if (currentPixels[i].x == x && currentPixels[i].y == y) {
						currentPixels.splice(i, 1);
						break;
					}
				}*/
				/*if (id != null) {
					for (var i = 0; i < currentPixels.length; i++) {
						if (currentPixels[i].id == id) {
							currentPixels.splice(i, 1);
							return;
						}
					}
				}*/
			};

		//Language

			function englishFormatList(thingsArrayIn) {
				var thingsArray = thingsArrayIn;
				var amount = thingsArray.length;
				if(amount == 1) {
					return thingsArray[0]
				} else if(amount == 2) {
					return thingsArray.join(" and ")
				} else {
					var lastItem = thingsArray[thingsArray.length - 1];
					thingsArray[thingsArray.length - 1] = "and " + lastItem;
					return thingsArray.join(", ")
				};
			};

			function capitalizeFirstLetter(string,locale=null) {
				return string[0][locale ? "toLocaleUpperCase" : "toUpperCase"](locale) + string.slice(1)
			};

	//COLOR MANIPULATION TOOLS ##

		var colorToolCounter = 0;
		saturationAmount = 1;
		saturationOp = "add";
		luminanceAmount = 1;
		luminanceOp = "add";
		hueAmount = 1;
		hueOp = "add";
		colorToolElementFilter = "none";

		var ops = ["add","subtract","multiply","divide","set","min","max","+","-","*","x","×","/","÷","=",">",">=","<","<="];

		function colorToolFilterPrompt() {
			var preElement = prompt("Enter the elements you want to limit it to\nSeparate multiple elements with commas\nType \"none\" for no filter");
			if(preElement === null) {
				return false;
			};
			if(preElement.includes(",")) {
				preElement = preElement.split(",");
				colorToolElementFilter = preElement;
				return colorToolElementFilter;
			};
			colorToolElementFilter = preElement;
			return colorToolElementFilter;
		};

		function saturationPrompt() {
			var preSaturation = prompt("Enter the value you want to use");
			var preSatOp = prompt(`Enter the operation ("add", "subtract", "multiply", or "divide", or "set")`);

			//value check
			if(isNaN(parseFloat(preSaturation))) {
				if(preSaturation === "" || preSaturation === null) {
					alert("No value was specified! Defaulting to 1");
					preSaturation = 1;
				} else {
					alert("Invalid value! The value must be a number (defaulting to 1)");
					preSaturation = 1;
				};
			};
			preSaturation = parseFloat(preSaturation);

			//operation check
			if(!ops.includes(preSatOp.toLowerCase())) {
				if(preSatOp === "" || preSatOp === null) {
					alert(`No operation was specified! Defaulting to "add".`);
					preSatOp = "add";
				} else {
					alert(`Invalid operation! Only "add", "subract", "multiply", "divide", "set", "min", and "max" are accepted (defaulting to "add").`);			
					preSatOp = "add";
				};
			};

			saturationAmount = preSaturation;
			saturationOp = preSatOp;
			return [preSaturation, preSatOp];
		};

		function luminancePrompt() {
			var preLuminance = prompt("Enter the value you want to use");
			var preLumOp = prompt(`Enter the operation ("add", "subtract", "multiply", or "divide", or "set")`);

			//value check
			if(isNaN(parseFloat(preLuminance))) {
				if(preLuminance === "" || preLuminance === null) {
					alert("No value was specified! Defaulting to 1");
					preLuminance = 1;
				} else {
					alert("Invalid value! The value must be a number (defaulting to 1)");
					preLuminance = 1;
				};
			};

			//operation check
			if(!ops.includes(preLumOp.toLowerCase())) {
				if(preLumOp === "" || preLumOp === null) {
					alert(`No operation was specified! Defaulting to "add".`);
					preLumOp = "add";
				} else {
					alert(`Invalid operation! Only "add", "subract", "multiply", "divide", "set", "min", and "max" are accepted (defaulting to "add").`);			
					preLumOp = "add";
				};
			};
			preLuminance = parseFloat(preLuminance)

			luminanceAmount = preLuminance;
			luminanceOp = preLumOp;
			return [preLuminance, preLumOp];
		};

		function huePrompt() {
			var preHue = prompt("Enter the value you want to use");
			var preHueOp = prompt(`Enter the operation ("add", "subtract", "multiply", or "divide", or "set")`);

			//value check
			if(isNaN(parseFloat(preHue))) {
				if(preHue === "" || preHue === null) {
					alert("No value was specified! Defaulting to 1");
					preHue = 1;
				} else {
					alert("Invalid value! The value must be a number (defaulting to 1)");
					preHue = 1;
				};
			};

			preHue = parseFloat(preHue);

			//operation check
			if(!ops.includes(preHueOp.toLowerCase())) {
				if(preHueOp === "" || preHueOp === null) {
					alert(`No operation was specified! Defaulting to "add".`);
					preHueOp = "add";
				} else {
					alert(`Invalid operation! Only "add", "subract", "multiply", "divide", "set", "min", and "max" are accepted (defaulting to "add").`);			
					preHueOp = "add";
				};
			};

			hueAmount = preHue;
			hueOp = preHueOp;
			return [preHue, preHueOp];
		};

		elements.multiply_color = {
			color: ["#c27070","#c29c70","#c2c270","#70c270","#70c2c2","#7070c2","#c270c2"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = multiplyColors(pixel.color,currentColor,"rgb");
				};
			},
			customColor: true,
			cooldown: 3,
			category: "color tools", //the toolbar is getting cluttered
			excludeRandom: true, //the toolbar is getting cluttered
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.divide_color = {
			color: ["#c27070","#c29c70","#c2c270","#70c270","#70c2c2","#7070c2","#c270c2"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = divideColors(pixel.color,currentColor,"rgb");
				};
			},
			customColor: true,
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.add_color = {
			color: ["#c27070","#c29c70","#c2c270","#70c270","#70c2c2","#7070c2","#c270c2"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = addColors(pixel.color,currentColor,"rgb");
				};
			},
			customColor: true,
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.subtract_color = {
			color: ["#c27070","#c29c70","#c2c270","#70c270","#70c2c2","#7070c2","#c270c2"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = subtractColors(pixel.color,currentColor,"rgb");
				};
			},
			customColor: true,
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.hue = {
			color: ["#ff0000","#ccff00","#00ff66","#0066ff","#cc00ff","#ff0000"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = changeHue(pixel.color,hueAmount,hueOp,"rgb");
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=huePrompt()>Click here to configure the tool.</span><br/><span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		};

		elements.saturation = {
			color: ["#808080","#996666","#b34d4d","#cc3333","#e61919","#ff0000"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = changeSaturation(pixel.color,saturationAmount,saturationOp,"rgb");
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=saturationPrompt()>Click here to configure the tool.</span><br/><span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>"
		}

		elements.luminance = {
			color: ["#000000","#333333","#666666","#999999","#cccccc","#ffffff"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					pixel.color = changeLuminance(pixel.color,luminanceAmount,luminanceOp,"rgb");
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=luminancePrompt()>Click here to configure the tool.</span><br/><span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>"
		}

		elements.grayscale = {
			color: ["#7f7f7f"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					var oldColor = convertColorFormats(pixel.color,"json");
					var lightness = Math.round((oldColor.r * 0.299) + (oldColor.g * 0.587) + (oldColor.b * 0.114))
					var finalColor = [lightness, lightness, lightness]
					pixel.color = "rgb(" + finalColor.join(",") + ")"
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.invert = {
			color: ["#ff0000", "#00ffff"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					var oldColor = convertColorFormats(pixel.color,"json");
					var finalColor = [(255 - oldColor.r), (255 - oldColor.g), (255 - oldColor.b)]
					pixel.color = "rgb(" + finalColor.join(",") + ")"
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.reverse_R_G_B = {
			color: ["#7f7f7f"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					var oldColor = convertColorFormats(pixel.color,"json");
					var finalColor = [oldColor.b, oldColor.g, oldColor.r]
					pixel.color = "rgb(" + finalColor.join(",") + ")"
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

		elements.shift_R_G_B = {
			color: ["#7f7f7f"],
			tool: function(pixel) {
				var element = pixel.element;
				if(  colorToolElementFilter === "none" || ( (typeof(colorToolElementFilter) === "string" && element === colorToolElementFilter) || (Array.isArray(colorToolElementFilter) && colorToolElementFilter.includes(element)) )  ) {
					var oldColor = convertColorFormats(pixel.color,"json");
					var finalColor = [oldColor.g, oldColor.b, oldColor.r]
					pixel.color = "rgb(" + finalColor.join(",") + ")"
				};
			},
			cooldown: 3,
			category: "color tools",
			excludeRandom: true,
			desc: "<span style='color:#FF00FF' onClick=colorToolFilterPrompt()>Click here to configure the element filter (applies to all color tools).</span>",
		}

	//STRIPED PAINT ##
		stripeFixedDefaultProperties = {
			color2: "rgb(0,0,0)",
			phase: 0,
			scale: 1,
			angle: 0
		};

		stripeSpreadingProperties = {
			color1: "It doesn't matter what I put here; I'm just sick of writing for loops.",
			color2: "stan loona",
		};

		/*stripeSpreadingProperties2 = {
			phase: 0,
			scale: 1, :eggTF:
			angle: 0
		};*/

		function stripeFunction(pixel) {
			if(pixel.color1 == undefined || pixel.color1 == null) {
				pixel.color1 = pixel.color;
			};
			for(prop in stripeFixedDefaultProperties) {
				if(pixel[prop] == undefined || pixel[prop] == null) {
					pixel[prop] = stripeFixedDefaultProperties[prop];
				};
			};

			//color1 and color2 self staining
			for (var i = 0; i < adjacentCoords.length; i++) {
				var x = pixel.x+adjacentCoords[i][0];
				var y = pixel.y+adjacentCoords[i][1];
				if(isEmpty(x,y,true)) {
					continue;
				};
				var otherPixel = pixelMap[x][y];
				for(prop in stripeSpreadingProperties) {
					if(otherPixel.element == pixel.element && pixel[prop] && otherPixel[prop]) {
						otherPixel[prop] = lerpColors(pixel[prop],otherPixel[prop]);
					};
				};
				/*for(prop in stripeSpreadingProperties2) {
					if(otherPixel.element == pixel.element && pixel[prop] !== undefined && otherPixel[prop] !== undefined) {
						otherPixel[prop] = pixel[prop]/2 + otherPixel[prop]/2;
					}; :eggTF:
				};*/
			};

			var radians = pixel.angle * (Math.PI / 180);

			var colorNumber = (pixel.x*Math.cos(radians))+(pixel.y*Math.sin(radians));

			var sineWeight = (1+Math.sin(pixel.phase + colorNumber / pixel.scale))/2;

			var preColor = lerpColors(pixel.color1,pixel.color2,"json",sineWeight);
			for(colorlet in preColor) {
				preColor[colorlet] = Math.round(preColor[colorlet]);
			};
			pixel.color = convertColorFormats(preColor,"rgb");
		};

		stripePaintDesc = `Exactly what it says on the button.
<br/>
Properties: <ol>
<li>color1: The first color of the stripe</li>
<li>color2: The second color of the stripe (defaults to black)</li>
<li>scale: Relative width of the stripes, compared to the default</li>
<li>phase: Offset in the position of the stripes (π/2 = 1 stripe width)</li>
<li>angle: Angle in degrees<ul>
	<li>0 = vertical line</li>
	<li>45 = bottom left to top right</li>
	<li>90 = horizontal line</li>
	<li>135 = top left to bottom right...</li>
</ul></li></ol>

color1 and color2 spread through striped paint like dye does with itself. <u>color1</u> can be set <em>on initial placement</em> through the color picker, but otherwise, properties must be changed through the console or with prop.js's tools.

	<small><em style="color: #ff5555;">This does not work with HSL color and the game will black screen if one is somehow used.</em> The color conversion functions are a clusterf***.</small>`

		elements.stripe_paint = {
			behavior: behaviors.LIQUID,
			state: "liquid",
			density: 998,
			tempHigh: 100,
			stateHigh: "smoke",
			color: elements.paint.color,
			customColor: true,
			category: "special",
			properties: {
				color1: null,
				color2: null,
				scale: 1,
				phase: 0,
				angle: 0
			},
			stain: elements.dye.stain,
			tick: function(pixel) {
				stripeFunction(pixel);
			},
			desc: stripePaintDesc
		};

	//CHANGEPIXEL LOG UNDEFINED-ELEMENT FAILURES ##

		doLog = true;
		oldChangePixel = changePixel;
		changePixel = function(pixel,element,changetemp=true) {
			if(typeof(elements[element]) == "undefined") {
				if(typeof(element) == "undefined" || element == "undefined") { return false };
				if(doLog) { console.error(`Something tried to change a pixel of ${pixel.element} at (${pixel.x},${pixel.y}) to nonexistent element "${element}"`) };
				return false
			};
			oldChangePixel(pixel,element,changetemp);
		};

	//VARIABLES FOR IN-GAME CONSOLE ##

		colorInvalidError = "Color must be in the form \"rgb(red,green,blue)\" or \"hsl(hue,saturation%,lightness%)\" (without quotes)!";
		stringSynonyms = [ "string", "str", "st", "s" ];
		numberSynonyms = [ "number", "num", "nm", "nu", "nb", "integer", "int", "i", "it", "float",
						   "flt", "ft", "fl", "f", "wholenumber", "decimalnumber", "wn", "dn", "w",
						   "d", "deeznuts" ]; /*The purpose of these blatant lies is, through a 
						   reference to the Alice series of software, have an excuse to include deez
						   nuts. (p.s. this was before i picked that name)*/
		objectSynonyms = [ "object", "oj", "obj", "ob", "o", "json" ];
		booleanSynonyms = [ "boolean", "bool", "boole", "boo", "bo", "bl", "b" ];
		arraySynonyms = [ "arr", "a", "ar", "list" ];
		defaultStringTypeValues = ["element","color","clone","changeTo","void","type","spawn"];
		defaultNumberTypeValues = ["x","y","charge","temp","start","vx","vy","chargeCD","start","burnStart","dir","panic","r","frequency","length","delay","volume","debounce","debounceLength","speed","fall","penetrateCounter","chargeCounter","spawnCounter","spawnTime","squadiusX","squadiusY","spawnTries","counter","attachDirection","value","range","xSpacing","ySpacing","maxPixels"];
		defaultBooleanTypeValues = ["burning","dead","hissing","following","dirLocked","del","didChargeBlueTinted","shooting","del","spawnAtPixelTemp","overwrite"];
		defaultArrayTypeValues = ["attachOffsets"];
		synonymsOfTrue = ["true", "t", "1", "yes"];
		synonymsOfFalse = ["false", "f", "0", "no"];

	//ENABLE RUNNING CODE AFTER STATE ELEMENT AUTOGENERATION (runAfterAutogen) ##

		resizeCanvas = function(newHeight,newWidth,newPixelSize,clear) {
			var gameCanvas = document.getElementById("game");
			var ctx = gameCanvas.getContext("2d");
			ctx.canvas.width = newWidth;
			ctx.canvas.height = newHeight;
			document.getElementById("gameDiv").style.width = newWidth + "px";
			pixelSize = newPixelSize;
			pixelSizeHalf = newPixelSize/2;
			height = Math.round(newHeight/newPixelSize)-1;
			width = Math.round(newWidth/newPixelSize)-1;
			mousePos = {x:width/2,y:height/2};
			if (clear!==false) { clearAll(); }
		};

		autoResizeCanvas = function(clear) {
			if (window.innerWidth < 700) {
				pixelSize = 5;
			} else {
				pixelSize = 6;
			}
			if (window.innerWidth < 700) {
				var newWidth = Math.ceil(window.innerWidth / pixelSize) * pixelSize;
				var newHeight = Math.ceil(window.innerHeight*0.6 / pixelSize) * pixelSize;
			}
			else {
				var newWidth  = Math.ceil(window.innerWidth*0.9 / pixelSize) * pixelSize;
				var newHeight = Math.ceil(window.innerHeight*0.675 / pixelSize) * pixelSize;
			}
			// If the new width is greater than 1000, set it to 1000
			if (newWidth > 1000) { newWidth = 1000; }
			// If we are on a desktop and the new height is greater than 500, set it to 500
			if (window.innerWidth > 1000 && newHeight > 500) { newHeight = 500; }
			resizeCanvas(newHeight,newWidth,pixelSize,clear);
		};

		function runAfterAutogen(func) {
			runAfterAutogenList.push(func);
		};

		function runAfterButtons(func) {
			runAfterButtonsList.push(func);
		};

		runAfterAutogenList = [];

		runAfterButtonsList = [];

		function behaviorStringsToArrays() {
			for (var behavior in behaviors) {
				if (typeof behaviors[behavior][0] === "string") {
					var newbehavior = [];
					for (var i = 0; i < behaviors[behavior].length; i++) {
						newbehavior.push(behaviors[behavior][i].split("|"));
					}
					behaviors[behavior] = newbehavior;
				}
			}
		}

		function tripletsToRgbAndGenerateColorObjects() {
			for (var key in elements) {
				if (elements.hasOwnProperty(key)) {
					// if the element has no color, skip it
					if (elements[key].color === undefined) {
						continue;
					}
					// if the color is an array, loop over each one
					if (elements[key].color instanceof Array) {
						var rgbs = [];
						var rgbos = [];
						for (var i = 0; i < elements[key].color.length; i++) {
							var c = elements[key].color[i];
							if(!c.startsWith) {
								console.error(`element ${key} array color ${i} (${c}) isn't a string`);
								rgbos.push("rgb(255,255,255)")
							} else if (c.startsWith("#")) {
								var rgb = hexToRGB(c);
								if(rgb == null) { console.log(key,c); rgb = {r: 255, g: 255, b: 255} };
								rgbs.push("rgb("+rgb.r+","+rgb.g+","+rgb.b+")");
								rgbos.push(rgb);
							}
							else {
								rgbs.push(c);
							}
						}
						elements[key].color = rgbs;
						elements[key].colorObject = rgbos;
					} else {
						// if elements[key].color starts with #
						if (elements[key].color.startsWith("#")) {
							var rgb = hexToRGB(elements[key].color);
							elements[key].color = "rgb("+rgb.r+","+rgb.g+","+rgb.b+")";
							elements[key].colorObject = rgb;
						}
					}
				}
			}
		}

		// Automatic molten element generation
		// Moved above the exposed autoGenAllElements for ReferenceError purposes
		function autoGen(newname,element,autoType) {
			var autoInfo = autoElements[autoType];
			var newcolor = elements[element].colorObject;
			var colorList = [];
			var colorObjectList = [];
			// if newcolor is not an array, put it in an array
			if (!(newcolor instanceof Array)) { newcolor = [newcolor]; }
			// for every color in the newcolor array, add a new color with the same value, but with the r and g values increased
			for (var i = 0; i < newcolor.length; i++) {
				var c = newcolor[i] ?? "#ff00ff";
				for (var j = 0; j < autoInfo.rgb.length; j++) {
					var newc = autoInfo.rgb[j];
					r = Math.floor(c.r * newc[0]);
					g = Math.floor(c.g * newc[1]);
					b = Math.floor(c.b * newc[2]);
					if (r > 255) {r = 255;} if (g > 255) {g = 255;}
					colorList.push("rgb("+r+","+g+","+b+")");
					colorObjectList.push({r:r,g:g,b:b});
				}
			}
			var newelem = {
				//"name": newname.replaceAll("_"," "),
				behavior: autoInfo.behavior,
				hidden: autoInfo.hidden || false,
				state: autoInfo.state || "solid",
				category: autoInfo.category || "states",
			}
			if (colorList.length <= 1) { colorList = colorList[0]; }
			if (colorObjectList.length <= 1) { colorObjectList = colorObjectList[0]; }
			newelem.color = colorList;
			newelem.colorObject = colorObjectList;
			var multiplier = 1.1;
			if (autoInfo.type === "high") {
				if (!elements[element].stateHigh) {elements[element].stateHigh = newname;}
				newelem.temp = elements[element].tempHigh;
				newelem.tempLow = elements[element].tempHigh+(autoInfo.tempDiff || 0);
				newelem.stateLow = element;
				// Change density by *0.9
				if (elements[element].density) { newelem.density = Math.round(elements[element].density * 0.9 * 10) / 10; }
			}
			else if (autoInfo.type === "low") {
				if (!elements[element].stateLow) {elements[element].stateLow = newname;}
				newelem.temp = elements[element].tempLow;
				newelem.tempHigh = elements[element].tempLow+(autoInfo.tempDiff || 0);
				newelem.stateHigh = element;
				multiplier = 0.5;
				// Change density by *1.1
				if (elements[element].density) { newelem.density = Math.round(elements[element].density * 1.1 * 10) / 10; }
			}
			if (!elements[element].ignore) { elements[element].ignore = [] }
			elements[element].ignore.push(newname);
			if (elements[element].viscosity || autoInfo.viscosity) {
				newelem.viscosity = elements[element].viscosity || autoInfo.viscosity;
			}
			// Change by *multiplier
			if (elements[element].conduct) { newelem.conduct = Math.round(elements[element].conduct * multiplier * 10) / 10; }
			if (elements[element].burn) { newelem.burn = Math.round(elements[element].burn * multiplier * 10) / 10; }
			if (elements[element].burnTime) { newelem.burnTime = Math.round(elements[element].burnTime * multiplier * 10) / 10; }
			if (elements[element].burnInto) { newelem.burnInto = elements[element].burnInto; }
			if (elements[element].fireColor) { newelem.fireColor = elements[element].fireColor; }
			// If the new element doesn't exist, add it
			if (!elements[newname]) { elements[newname] = newelem; }
			else {
				// Loop through newelem's keys and values, copy them to the new element if they are not already defined
				for (var key in newelem) {
					if (elements[newname][key] == undefined) { elements[newname][key] = newelem[key]; }
				}
			}

			/*if (autoType === "molten" && (elements.molten_slag && elements.molten_slag.ignore && elements.molten_slag.ignore.indexOf(element) === -1)) { // Slag reactions
				if (newname !== "molten_slag") {
					if (!elements[newname].reactions) { elements[newname].reactions = {}; }
					elements[newname].reactions.ash = { "elem1":null, "elem2":"molten_slag" };
					elements[newname].reactions.dust = { "elem1":null, "elem2":"molten_slag" };
					elements[newname].reactions.magma = { "elem1":null, "elem2":"molten_slag" }
				};
			}*/
		}

		function autoGenAllElements() {
			for (element in elements) {
				if (elements[element].tempHigh!==undefined && (elements[element].stateHigh===undefined||elements[element].forceAutoGen)) {
					var newname = elements[element].stateHighName;
					if ((elements[element].state==="solid" || !elements[element].state)) { // Melting
						if (!newname) { newname = "molten_"+element }
						autoGen(newname,element,"molten");
					}
					else if (elements[element].state==="liquid") { // Evaporating
						if (!newname) {
							newname = element;
							if (newname.startsWith("liquid_")) { newname = newname.substring(7); }
							if (newname.startsWith("molten_")) { newname = newname.substring(7); }
							newname += "_gas";
						}
						autoGen(newname,element,"evaporate");
					}
				}
				if (elements[element].tempLow!==undefined && (elements[element].stateLow===undefined||elements[element].forceAutoGen)) {
					var newname = elements[element].stateLowName;
					if (elements[element].state==="liquid") { // Freezing
						if (!newname) {
							newname = element;
							if (newname.startsWith("liquid_")) { newname = newname.substring(7); }
							if (newname.endsWith("_water")) { newname = newname.substring(0,newname.length-6); }
							newname += "_ice";
						}
						autoGen(newname,element,"frozen");
					}
					else if (elements[element].state==="gas") { // Condensing
						if (!newname) {
							newname = element;
							if (newname.endsWith("_gas")) { newname = newname.substring(0,newname.length-4); }
							newname = "liquid_"+newname;
						}
						autoGen(newname,element,"condense");
					}
				}
				if (elements[element].behavior && typeof elements[element].behavior[0] === "string") {
					var newbehavior = [];
					for (var i = 0; i < elements[element].behavior.length; i++) {
						newbehavior.push(elements[element].behavior[i].split("|"));
					}
					elements[element].behavior = newbehavior;
				}
				if (elements[element].behaviorOn && typeof elements[element].behaviorOn[0] === "string") {
					var newbehavior = [];
					for (var i = 0; i < elements[element].behaviorOn.length; i++) {
						newbehavior.push(elements[element].behaviorOn[i].split("|"));
					}
					elements[element].behaviorOn = newbehavior;
				}
			}
		}

		function doFinalChecks() {
			nextid = 1;
			for (key in elements) {
				if(!elements[key]) {
					console.error(`Nonexistent element ${key}`);
					continue
				};
				elements[key].id = nextid;
				nextid++;
				// If the element has no behavior, set it to behaviors.WALL
				if (!elements[key].behavior && !elements[key].tick) {
					elements[key].tick = function(pixel) {};
				}
				// If the behavior is a function, delete it and set tick to it instead
				if (typeof elements[key].behavior === "function") {
					if (elements[key].tick) {
						elements[key].tick1 = elements[key].tick;
						elements[key].tick2 = elements[key].behavior;
						elements[key].tick = function(pixel) {
							if (pixel.start === pixelTicks) {return}
							var id = elements[pixel.element].id;
							elements[pixel.element].tick1(pixel);
							if (!pixel.del && id === elements[pixel.element].id) {
								elements[pixel.element].tick2(pixel);
							}
						}
					}
					else {
						elements[key].tick = elements[key].behavior;
					}
					delete elements[key].behavior;
				}
				// If the element has no color, set it to white
				if (elements[key].color === undefined) {
					elements[key].color = "rgb(255,255,255)";
					elements[key].colorObject = {r:255,g:255,b:255};
				}
				// If the element's behavior is an array and contains M1 or M2, set its movable to true
				if (elements[key].behavior && typeof elements[key].behavior[0] === "object") {
					var bstring = JSON.stringify(elements[key].behavior);
					if (bstring.indexOf("M1")!==-1 || bstring.indexOf("M2")!==-1) { elements[key].movable = true; }
				}
				if (elements[key].tick) { elements[key].movable = true; }
				if (elements[key].behavior) {
					// If the element's behavior[1][1] includes "FX", set it's flippableX to true
					if (elements[key].behavior[1][1].indexOf("FX") !== -1) {
						elements[key].flippableX = true;
					}
					// If the element's behavior[1][1] includes "FY", set it's flippableY to true
					if (elements[key].behavior[1][1].indexOf("FY") !== -1) {
						elements[key].flippableY = true;
					}

					// If the element's behavior stringified includes "BO", loop through the behavior
					if (elements[key].behavior.toString().indexOf("BO") !== -1 && !elements[key].rotatable) {
						for (var i = 0; i < elements[key].behavior.length; i++) {
							// Loop through each array in the behavior
							for (var j = 0; j < elements[key].behavior[i].length; j++) {
								// If the behavior includes "BO", set the behaviorOn to the behavior
								if (elements[key].behavior[i][j].indexOf("BO") !== -1) {
									if ((i==0 && j==0) || (i==0 && j==2) || (i==2 && j==0) && (i==2 && j==2)) {
										elements[key].flippableX = true;
										elements[key].flippableY = true;
									}
									else if (i==0 || i==2) {
										elements[key].flippableY = true;
									}
									else if (j==0 || j==2) {
										elements[key].flippableX = true;
									}
								}
							}
						}
					}

					// If the element's behavior[1][1] includes "RT", set it's rotatable to "true"
					if (elements[key].behavior[1][1].indexOf("RT") !== -1) {
						elements[key].rotatable = true;
					}

				}

				// If the element's state is "gas", isGas = true
				if (elements[key].state === "gas") {
					elements[key].isGas = true;
				}
				// Else if the state is not "solid" or "liquid", delete it
				else if (elements[key].state !== "solid" && elements[key].state !== "liquid") {
					delete elements[key].state;
				}

				// If the element has reactions, loop through each one (it is an object), if the value for elem1 or elem2 is not an element and is not null, remove that key
				if (elements[key].reactions) {
					for (var reaction in elements[key].reactions) {
						if(!(elements[key].reactions[reaction])) {
							console.error(`Nonexistent reaction ${reaction} in element ${key}`);
							continue
						}
						// If elem1 exists
						if (elements[key].reactions[reaction].elem1) {
							// If elem1 is an array, loop through each element, else check once. Don't delete if it === null
							if (Array.isArray(elements[key].reactions[reaction].elem1)) {
								for (var i = 0; i < elements[key].reactions[reaction].elem1.length; i++) {
									if (elements[key].reactions[reaction].elem1[i] && !elements[elements[key].reactions[reaction].elem1[i]]) {
										elements[key].reactions[reaction].elem1.splice(i,1);
									}
								}
							}
							else if (elements[key].reactions[reaction].elem1 && !elements[elements[key].reactions[reaction].elem1]) {
								delete elements[key].reactions[reaction].elem1;
							}
						}
						// If elem2 exists
						if (elements[key].reactions[reaction].elem2) {
							// If elem2 is an array, loop through each element, else check once. Don't delete if it === null
							if (Array.isArray(elements[key].reactions[reaction].elem2)) {
								for (var i = 0; i < elements[key].reactions[reaction].elem2.length; i++) {
									if (elements[key].reactions[reaction].elem2[i] && !elements[elements[key].reactions[reaction].elem2[i]]) {
										elements[key].reactions[reaction].elem2.splice(i,1);
									}
								}
							}
							else if (elements[key].reactions[reaction].elem2 && !elements[elements[key].reactions[reaction].elem2]) {
								delete elements[key].reactions[reaction].elem2;
							}
						}
					}
				}

				// If the element's stateHigh or stateLow is not an element, remove it and tempHigh/Low
				if (elements[key].stateHigh) {
					// If it's an array, do it for each item, otherwise, just do it once
					if (Array.isArray(elements[key].stateHigh)) {
						for (var i = 0; i < elements[key].stateHigh.length; i++) {
							if (!elements[elements[key].stateHigh[i]] && elements[key].stateHigh[i] !== null) {
								elements[key].stateHigh.splice(i,1);
							}
						}
						if (elements[key].stateHigh.length == 0) {
							delete elements[key].stateHigh;
							delete elements[key].tempHigh;
						}
					}
					else {
						if (!elements[elements[key].stateHigh] && elements[key].stateHigh !== null) {
							delete elements[key].stateHigh;
							delete elements[key].tempHigh;
						}
					}
				}
				if (elements[key].stateLow) {
					if (Array.isArray(elements[key].stateLow)) {
						for (var i = 0; i < elements[key].stateLow.length; i++) {
							if (!elements[elements[key].stateLow[i]] && elements[key].stateLow[i] !== null) {
								elements[key].stateLow.splice(i,1);
							}
						}
						if (elements[key].stateLow.length == 0) {
							delete elements[key].stateLow;
							delete elements[key].tempLow;
						}
					}
					else {
						if (!elements[elements[key].stateLow] && elements[key].stateLow !== null) {
							delete elements[key].stateLow;
							delete elements[key].tempLow;
						}
					}
				}
				// same for burnInto
				if (elements[key].burnInto) {
					if (Array.isArray(elements[key].burnInto)) {
						for (var i = 0; i < elements[key].burnInto.length; i++) {
							if (!elements[elements[key].burnInto[i]]) {
								delete elements[key].burnInto[i];
							}
						}
						if (elements[key].burnInto.length == 0) {
							delete elements[key].burnInto;
						}
					}
					else {
						if (!elements[elements[key].burnInto]) {
							delete elements[key].burnInto;
						}
					}
				}
				// same for breakInto
				if (elements[key].breakInto) {
					if (Array.isArray(elements[key].breakInto)) {
						for (var i = 0; i < elements[key].breakInto.length; i++) {
							if (elements[key].breakInto[i]!==null && !elements[elements[key].breakInto[i]]) { delete elements[key].breakInto[i]; }
						}
						if (elements[key].breakInto.length == 0) { delete elements[key].breakInto; }
					}
					else {
						if (elements[key].breakInto[i]!==null && !elements[elements[key].breakInto]) { delete elements[key].breakInto; }
					}
				}

				if (elements[key].colorPattern) {
					if (!elements[key].colorKey) {
						delete elements[key].colorPattern;
					}
					else {
					var newPattern = [];
					for (var i = 0; i < elements[key].colorPattern.length; i++) {
						newPattern.push([]);
						var line = elements[key].colorPattern[i];
						// loop through each character in the line
						for (var j = 0; j < line.length; j++) {
							var char = line[j];
							if (elements[key].colorKey[char]) {
								if (elements[key].colorKey[char].startsWith("#")) {
									var rgb = hexToRGB(elements[key].colorKey[char]);
									elements[key].colorKey[char] = "rgb("+rgb.r+","+rgb.g+","+rgb.b+")";
								}
								newPattern[i].push(elements[key].colorKey[char]);
							}
							else {
								newPattern[i].push("rgb(255,255,255)");
							}
						}
					}
					elements[key].colorPattern = newPattern;
					delete elements[key].colorKey;
					}
				}
			}
		};

		function createWorldGenOptions() {
			for (var key in worldgentypes) {
				document.getElementById("worldgenselect").innerHTML += "<option value='" + key + "'>" + key.replace(/_/g, " ").replace(/\b\w/g, l => l.toUpperCase()) + "</option>";
			}
		};

		function validateWorldGenSelection() {
			if (settings["worldgen"] && !worldgentypes[settings["worldgen"]]) {
				settings["worldgen"] = "off";
			}
		};

		function validateRandomEventChoices() {
			for (var key in randomEventChoices) {
				for (var i = 0; i < randomEventChoices[key].length; i++) {
					if (!elements[randomEventChoices[key][i]]) {
						randomEventChoices[key].splice(i,1);
					}
				}
			}
		};

		function setEqualReactions(fromElement,toElement) {
			if (elements[fromElement] && elements[toElement]) {
				if (elements[fromElement].reactions) {
					elements[toElement].reactions = elements[fromElement].reactions;
					return true;
				};
			};
			return false;
		};

		function loadSettings() {
			var settingSpans = document.getElementsByClassName("setting-span");
			for (var i = 0; i < settingSpans.length; i++) {
				var setting = settingSpans[i].getAttribute("setting");
				if (setting in settings) {
					var settingValue = settings[setting];
					var settingElements = settingSpans[i].getElementsByTagName("select") || settingSpans[i].getElementsByTagName("input");
					if (settingElements.length > 0) {
						settingElements[0].value = settingValue;
					}
				}
			}
		};

		function setCanvasWidthAndHeight(ctx) {
			var newWidth  = Math.ceil(window.innerWidth*0.9 / pixelSize) * pixelSize;
			var newHeight = Math.ceil(window.innerHeight*0.675 / pixelSize) * pixelSize;
			// If the new width is greater than 800, set it to 800
			if (newWidth > 1000) { newWidth = 1000; }
			// If we are on a desktop and the new height is greater than 600, set it to 600
			if (window.innerWidth > 1000 && newHeight > 500) { newHeight = 500; }
			ctx.canvas.width = newWidth;
			ctx.canvas.height = newHeight;
			document.getElementById("gameDiv").style.width = newWidth + "px";
			document.getElementById("loadingP").style.display = "none";
			document.getElementById("canvasDiv").style.display = "block";

			width = Math.round(newWidth/pixelSize)-1;
			height = Math.round(newHeight/pixelSize)-1;
		};

		function definePixelMap() {
			if (settings["worldgen"]) {
				clearAll();
			}
			else {
				// Object with width arrays of pixels starting at 0
				pixelMap = [];
				for (var i = 0; i < width; i++) {
					pixelMap[i] = [];
				}
			}
		};

		function setRandomChoices() {
			randomChoices = Object.keys(elements).filter(function(e) {
				return elements[e].excludeRandom != true && elements[e].category != "tools" && !elements[e].tool;
			});
		};

		function addCanvasAndWindowListeners(gameCanvas) {
			gameCanvas.addEventListener("mousedown", mouseClick);
			gameCanvas.addEventListener("touchstart", mouseClick, { passive: false });
			window.addEventListener("mouseup", mouseUp);
			window.addEventListener("touchend", mouseUp, { passive: false });
			window.addEventListener("mousemove", mouseMove);
			gameCanvas.addEventListener("touchmove", mouseMove, { passive: false });
			gameCanvas.addEventListener("wheel", wheelHandle);
			gameCanvas.ontouchstart = function(e) {
				if (e.touches) e = e.touches[0];
				return false;
			}
			gameCanvas.addEventListener("dragenter", function(e){e.stopPropagation(); e.preventDefault();})
			gameCanvas.addEventListener("dragover", function(e){e.stopPropagation(); e.preventDefault();})
			gameCanvas.addEventListener("drop", function(e){
				e.stopPropagation();
				e.preventDefault();
				var url = e.dataTransfer.getData('text/plain');
				if (url) {
					var img = new Image();
					img.onload = function(){placingImage = img; placeImage(); placingImage = null;}
					img.src = url;
				} else {
					if (!e.dataTransfer.files || e.dataTransfer.files.length === 0) { return; }
					var file = e.dataTransfer.files[0];
					// for img file(s), read the file & draw to canvas
					if (file.type.indexOf('image/') !== -1) {
						var img = document.createElement("img");
						img.classList.add("obj");
						img.file = file;
						var reader = new FileReader();
						reader.onload = (function(aImg){
							return function(e) {
								aImg.onload=function(){
									placingImage = aImg;
									placeImage();
									placingImage = null;
								}
								// e.target.result is a dataURL for the image
								aImg.src = e.target.result;
							}; 
						})(img);
						reader.readAsDataURL(file);
					}
					else if (file.name.indexOf(".sbxls") !== -1 || file.name.indexOf(".json") !== -1) {
						if (currentPixels.length!==0 && !confirm("Clear this scene and load save file?")) { return }
						var reader = new FileReader();
						reader.onload = function(e) {
							loadSave(JSON.parse(e.target.result));
						}
						reader.readAsText(file);
					}
				}
			}, false);
			// handle pasting
			window.addEventListener("paste", function(e){
				if (e.clipboardData) {
					var items = e.clipboardData.items;
					if (items.length === 0 && e.clipboardData.files.length !== 0) {
						items = e.clipboardData.files;
					}
					if (!items) { return; }
					var item = items[items.length-1];
					console.log(item);
					if (item.type.indexOf('image/') !== -1) {
						var blob = item.getAsFile();
						var URLObj = window.URL || window.webkitURL;
						var source = URLObj.createObjectURL(blob);
						var img = new Image();
						img.onload = function(){placingImage = img; placeImage(); placingImage = null;}
						img.src = source;
					}
					else if (item.type === "" || item.type.indexOf('application/json') !== -1) {
						if (currentPixels.length!==0 && !confirm("Clear this scene and load save file?")) { return }
						var reader = new FileReader();
						reader.onload = function(e) {
							loadSave(JSON.parse(e.target.result));
						}
						reader.readAsText(item.getAsFile());
					}
				}
			}, false);
			window.onbeforeunload = function(){
				if (currentPixels.length > 0){
					return 'Are you sure you want to leave?';
				}
			};
		};

		function generateModManagerList() {
			if (enabledMods.length > 0) {
				modManagerList = document.getElementById("modManagerList");
				for (var i = 0; i < enabledMods.length; i++) {
					var mod = enabledMods[i];
					// modName is the last part of the mod's path
					var modName = mod.split("/").pop();
					modManagerList.innerHTML += "<li><a href='" + mod + "' target='_blank'>" + modName + "</a> <span class='removeModX' onclick='removeMod(\"" + mod + "\")'>X</span></li>";
				}
			}
			else {
				document.getElementById("noMods").style.display = "block";
			}
		};

		//redefine mouseRange to support even sizes
        function mouseRange(mouseX,mouseY,size) {
            var coords = [];
            size = size || mouseSize;
            if (elements[currentElement].maxSize < mouseSize) {
                var mouseOffset = Math.trunc(elements[currentElement].maxSize/2);
            }
            else {
                var mouseOffset = Math.trunc(size/2);
            }
            var topLeft = [mouseX-mouseOffset,mouseY-mouseOffset];
            var bottomRight = [mouseX+mouseOffset,mouseY+mouseOffset];
			if(size % 2 == 0) {
				bottomRight[0]--;
				bottomRight[1]--;
			};
            // Starting at the top left, go through each pixel
            for (var x = topLeft[0]; x <= bottomRight[0]; x++) {
                for (var y = topLeft[1]; y <= bottomRight[1]; y++) {
                    // If the pixel is empty, add it to coords
                    coords.push([x,y]);
                }
            }
            return coords;
        };

		//this part defines basically all of the keybinds
		function addKeyboardListeners() {
			document.addEventListener("keydown", function(e) {
				if (e.ctrlKey || e.metaKey) {
					return
				}
				// else if tab, set document.body.class to "usingTab"
				else if (e.keyCode == 9) {
					document.body.classList.add("usingTab");
				}
				// F1 = hide #underDiv, #infoParent, #modParent, #pagetitle, #colorSelector if they aren't hidden, otherwise show them
				if (e.keyCode == 112) {
					e.preventDefault();
					if (document.getElementById("underDiv").style.display == "none") {
						document.getElementById("underDiv").style.display = "block";
						document.getElementById("pagetitle").style.display = "block";
						document.getElementById("colorSelector").style.display = "block";
						document.getElementById("bottomInfoBox").style.display = "block";
					} else {
						document.getElementById("underDiv").style.display = "none";
						if (showingMenu) {
							closeMenu()
						};
						document.getElementById("pagetitle").style.display = "none";
						document.getElementById("colorSelector").style.display = "none";
						document.getElementById("bottomInfoBox").style.display = "none";
					}
				}
				if (showingMenu) {
					// esc or / or tab / or \ (while in settings) to close
					if (e.keyCode == 27 || (e.keyCode == 191 && showingMenu=="info") || e.keyCode == 9 || (e.keyCode == 220 && showingMenu=="settings")) {
						e.preventDefault();
						closeMenu();
					}
					// enter to clear infoSearch
					else if (e.keyCode == 13 && showingMenu == "info") {
						var infoSearch = document.getElementById("infoSearch");
						infoSearch.value = "";
						showInfo();
					}
					return;
				}
				// If the user presses [ or -, decrease the mouse size by 2
				if (e.keyCode == 219 || e.keyCode == 189) {
					//If a shift key is pressed, set to 1
					if (shiftDown && shiftDown % 2 == 1) {mouseSize = 1}
					//If an alt key is pressed, decrease by 1
					else if (shiftDown && shiftDown % 2 == 0) { 
						mouseSize--;
						if (mouseSize < 1) { mouseSize = 1 }
					}
					else {
						mouseSize -= 2;
						if (mouseSize < 1) { mouseSize = 1 }
					}
				}
				// If the user presses ] or =, increase the mouse size by 2
				if (e.keyCode == 221 || e.keyCode == 187) {
					//If a shift key is pressed, increase by 15
					if (shiftDown && shiftDown % 2 == 1) {mouseSize = (mouseSize+15)-((mouseSize+15) % 15)}
					//If an alt key is pressed, increase by 1
					else if (shiftDown && shiftDown % 2 == 0) {mouseSize++}
					else {mouseSize += 2;}
					// if height>width and mouseSize>height, set mouseSize to height, if width>height and mouseSize>width, set mouseSize to width
					if (mouseSize > (height > width ? height : width)) { mouseSize = (height > width ? height : width); }
				}
				// User presses shift
				else if (e.keyCode == 16) {
					if (event.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
						shiftDown = 1;
					} else if (event.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
						shiftDown = 3;
					}
				}
				// User presses alt
				else if (e.keyCode == 18) {
					if (event.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
						shiftDown = 2;
					} else if (event.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
						shiftDown = 4;
					}
				}
				// p or spacebar or ` or k = pause
				if (e.keyCode == 80 || e.keyCode == 32 || e.keyCode == 192 || e.keyCode == 75) {
					e.preventDefault();
					togglePause();
				}
				// e = chooseElementPrompt()
				else if (e.keyCode == 69) {
					e.preventDefault();
					chooseElementPrompt();
				}
				// . = doFrame()
				else if (e.keyCode == 190) {
					e.preventDefault();
					doFrame();
				}
				// / or i = showInfo()
				else if (e.keyCode == 191 || e.keyCode == 73) {
					e.preventDefault();
					showInfo();
				}
				// f = full screen
				else if (e.keyCode == 70) {
					e.preventDefault();
					if (document.fullscreenElement) {
						document.exitFullscreen(document.body);
					} else {
						requestFullScreen(document.body);
					}
				}
				// 0-9 = setView(the number)
				else if (e.keyCode >= 48 && e.keyCode <= 57) {
					// if not command or control down, set view to the number
					e.preventDefault();
					setView(e.keyCode-48);
				}
				// right arrow = switch the category to the one after the current category
				else if (e.keyCode == 39) {
					e.preventDefault();
					// in categoryControls, find the button with the class categoryButton and the attribute current="true"
					var currentButton = document.querySelector(".categoryButton[current='true']");
					var currentCategory = currentButton.getAttribute("category");
					// get the categoryButton that is after the current one in the div
					var nextButton = currentButton.nextElementSibling;
					// if there is no next button, go to the first one
					if (nextButton == null) {
						nextButton = document.querySelector(".categoryButton");
					}
					var nextCategory = nextButton.getAttribute("category");
					selectCategory(nextCategory);
					// focus on categoryControls
					document.getElementById("categoryControls").focus();
				}
				// left arrow = switch the category to the one before the current category
				else if (e.keyCode == 37) {
					e.preventDefault();
					// in categoryControls, find the button with the class categoryButton and the attribute current="true"
					var currentButton = document.querySelector(".categoryButton[current='true']");
					var currentCategory = currentButton.getAttribute("category");
					// get the categoryButton that is before the current one in the div
					var prevButton = currentButton.previousElementSibling;
					// if there is no previous button, go to the last one
					if (prevButton == null) {
						prevButton = document.querySelector(".categoryButton:last-child");
					}
					var prevCategory = prevButton.getAttribute("category");
					selectCategory(prevCategory);
				}
				// m = closeMenu() and showModManager()
				else if (e.keyCode == 77) {
					e.preventDefault();
					closeMenu();
					showModManager();
				}
				// \ = closeMenu() and showSettings()
				else if (e.keyCode == 220) {
					e.preventDefault();
					closeMenu();
					showSettings();
				}
				// c or F2 = screenshot
				else if (e.keyCode == 67 || e.keyCode == 113) {
					e.preventDefault();
					var link = document.createElement('a');
					link.setAttribute('download', 'sandboxels-screenshot.png');
					link.setAttribute('href', document.getElementById("game").toDataURL("image/png").replace("image/png", "image/octet-stream"));
					document.body.appendChild(link);
					link.click();
					document.body.removeChild(link);
				}


				// x = explodeAt()
				/*else if (e.keyCode == 88) {
					e.preventDefault();
					explodeAt(mousePos.x, mousePos.y, Math.round(mouseSize/2));
				}*/

			});
		};

		function addUnshiftListeners() {
			document.addEventListener("keyup", function(e) {
				if (e.keyCode == 16 || e.keyCode == 18) {
					shiftDown = 0;
					if (shaping) {
						shaping = 0;
						shapeStart = null;
					}
				}
			});
		};

		function createButtonsAndCountElements() {
			elementCount = 0;
			hiddenCount = 0;
			categoryList = [];
			for (var element in elements) {
				elementCount++;
				if (settings.cheerful && elements[element].nocheer) {
					elements[element].hidden = true;
					hiddenCount++;
					continue;
				}
				var category = elements[element].category;
				if (category==null) {category="other"}
				if (categoryList.indexOf(category) === -1) {
					categoryList.push(category);
				}
				if (elements[element].hidden && (!settings["unhide"] || ( settings["unhide"]===2 && !settings.unlocked[element] ))) { hiddenCount++; continue; }
				var categoryDiv = document.getElementById("category-"+category);
				if (categoryDiv == null) {
					createCategoryDiv(category);
					categoryDiv = document.getElementById("category-"+category);
				}
				if(Array.isArray(elements[element].color) && elements[element].color.length == 1) {
					//temporarily make the single-item array into a string just for button generation, and then turn it back into an array just in case
					elements[element].color = elements[element].color[0];
					createElementButton(element);
					elements[element].color = [elements[element].color]
				} else {
					createElementButton(element);
				}
			}
			// Set the first button in categoryControls div to be the current category
			document.getElementById("categoryControls").children[0].click()
			document.getElementById("extraInfo").innerHTML += "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>";
			selectElement(currentElement);
			focusGame();
		};

		window.onload = function() {
			// If the browser is Firefox, set #categoryControls padding-bottom:11px;
			if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
				document.getElementById("categoryControls").style.paddingBottom = "11px";
			}

			// Loop through runAfterLoadList and run each function
			for (var i = 0; i < runAfterLoadList.length; i++) {
				runAfterLoadList[i]();
			}

			// Loop through behaviors and each behavior, if it is a string, split the items and replace the value with the array
			behaviorStringsToArrays();

			// convert every color in the elements object to rgb
			tripletsToRgbAndGenerateColorObjects()

			autoElements = {
				"molten": { // Solid -> Liquid
					rgb: [ [2,1.25,0.5], [2,1,0.5], [2,0.75,0] ],
					behavior: behaviors.MOLTEN,
					type: "high",
					viscosity: 10000,
					hidden: true,
					state: "liquid",
					tempDiff: -100,
				},
				"frozen": { // Liquid -> Solid
					rgb: [ [1.2,1.2,1.3] ],
					behavior: behaviors.WALL,
					type: "low",
					hidden: true,
					state: "solid",
				},
				"condense": { // Gas -> Liquid
					rgb: [ [0.5,0.5,0.5] ],
					behavior: behaviors.LIQUID,
					type: "low",
					hidden: true,
					state: "liquid",
				},
				"evaporate": { // Liquid -> Gas
					rgb: [ [1.5,1.5,1.5] ],
					behavior: behaviors.GAS,
					type: "high",
					hidden: true,
					state: "gas",
				}
			}

			// Loop through each element. If it has a tempHigh, but not a stateHigh, create a new molten element
			autoGenAllElements();

			// Loop through runAfterAutogenList and run each function
			for (var i = 0; i < runAfterAutogenList.length; i++) {
				runAfterAutogenList[i]();
			};

			// Loop through each element, final checks
			doFinalChecks();

			// Generate worldgen options
			// Loop through the worldgentypes object, add the key to the #worldgenselect select as an option with the value of the key and the name of the key capitalized and underscores replaced with spaces
			createWorldGenOptions();
			validateWorldGenSelection();

			// Loop through randomEventChoices, and loop through the array of each. If the element doesn't exist, remove it from the array.
			validateRandomEventChoices();

			// Poison == poison gas reactions
			setEqualReactions("poison","poison_gas");

			// Load settings
			// Loop through all the elements with setting-span class.
			// If the span's setting attribute is in settings, set the first select or input to the value of the setting.
			loadSettings();

				//scared to touch this because ctx is pretty important
			var gameCanvas = document.getElementById("game");
			// Get context
			var ctx = gameCanvas.getContext("2d");

			setCanvasWidthAndHeight(ctx);

			mousePos = {x:width/2,y:height/2};

			definePixelMap();

			// randomChoices = the keys of "elements" with any element with the category "tools" or the property excludeRandom set to true removed
			setRandomChoices();

			addCanvasAndWindowListeners(gameCanvas);
			gameCanvas.ontouchstart = function(e) {
				if (e.touches) e = e.touches[0];
				return false;
			}
			window.onbeforeunload = function(){ // Confirm leaving page if there are pixels on-screen
				if (currentPixels.length > 0){
					return 'Are you sure you want to leave?';
				}
			};

			// If enabledMods has items, add an li to modManagerList for each item with the href to the item, target blank, and the item's name, with "<span class="removeModX" onclick='removeMod('>X</span>" after the link
			generateModManagerList();

			document.getElementById("game").oncontextmenu = function(e) { e.preventDefault(); return false; }
			// If the user presses [ or -, decrease the mouse size by 2
			addKeyboardListeners();

			// If the user releases either shift
			addUnshiftListeners();

			// Create buttons for elements
			// For each element type in elements, create a button in controls that sets the current element to that type
			// Alphabetically sort and loop through dictionary named "elements"
			createButtonsAndCountElements();

			for (var i = 0; i < runAfterButtonsList.length; i++) {
				runAfterButtonsList[i]();
			};

			selectElement(currentElement);
			focusGame();
			// For every button element, onkeyup="event.preventDefault()"
			var buttonElements = document.getElementsByTagName("button");
			for (var i = 0; i < buttonElements.length; i++) {
				buttonElements[i].onkeyup = function(e) {
					e.preventDefault();
				}
			}

			if (window.self !== window.top && !location.ancestorOrigins[0].includes("itch.io")) {
				// Open a message that tells the user they aren't on the real website
				var menuParent = document.createElement("div");
				menuParent.className = "menuParent";
				menuParent.style.display = "block";
				menuParent.innerHTML = `<div class="menuScreen">
	<button class="XButton" onclick="closeMenu();">-</button>
	<span class="menuTitle">Sandboxels</span>
	<div class="menuText" style="padding-top:1em">
	You may be on a website that has embedded our game involuntarily.
	<br><br>
	The real game is at this URL: <a href="https://sandboxels.r74n.com" target="_blank">sandboxels.R74n.com</a>.
	<br><br>
	Please use the main website to support us instead.
	<br><br>
	You can also join our <a href="https://discord.gg/ejUc6YPQuS" target="_blank">Discord</a> if that isn't possible.
	</div>
	<br><br><br><br>
	</div>`
				document.body.appendChild(menuParent);
				showingMenu = "alert";
			}

			//get the first .elementButton in the first .category, and selectElement(button.element)
			var firstDiv = document.getElementsByClassName("category")[0];
			var firstElementButton = firstDiv.getElementsByClassName("elementButton")[0];
			selectElement(firstElementButton.getAttribute("element"));
			
			gameLoaded = true
		};


	//MORE CONFIGURABLE EXPLOSIONS (explodeAtPlus) ##

		velocityBlacklist = [];

		function explodeAtPlus(x,y,radius,firee="fire",smokee="smoke",beforeFunction=null,afterFunction=null,changeTemp=true) {
			var message = "Explosion ";
			var pixel = pixelMap[x]?.[y];
			if(pixel) { message += `of ${pixel.element} ` };
			message += `with radius ${radius} at (${x},${y})`;
			
			// if fire contains , split it into an array
			if(firee !== null) {
				if (firee.indexOf(",") !== -1) {
					firee = firee.split(",");
				};
			};
			if(smokee !== null) {
				if (smokee.indexOf(",") !== -1) {
					smokee = smokee.split(",");
				};
			};
			var coords = circleCoords(x,y,radius);
			var power = radius/10;
			//for (var p = 0; p < Math.round(radius/10+1); p++) {
			for (var i = 0; i < coords.length; i++) {
				var fire = firee;
				var smoke = smokee;
				// damage value is based on distance from x and y
				var damage = Math.random() + (Math.floor(Math.sqrt(Math.pow(coords[i].x-x,2) + Math.pow(coords[i].y-y,2)))) / radius;
				// invert
				damage = 1 - damage;
				if (damage < 0) { damage = 0; }
				damage *= power;
				if (isEmpty(coords[i].x,coords[i].y)) {
					// create smoke or fire depending on the damage if empty
					if (damage < 0.02) { } // do nothing
					else if (damage < 0.2) {
						// if smoke is an array, choose a random item
						if(smoke !== null) {
							while (Array.isArray(smoke)) {
								smoke = randomChoice(smoke);
							};
							if(smoke !== null) { createPixel(smoke,coords[i].x,coords[i].y) };
						}
					}
					else {
						while (Array.isArray(fire)) {
							fire = randomChoice(fire);
						};
						if(fire !== null) { createPixel(fire,coords[i].x,coords[i].y) };
					}
				}
				else if (!outOfBounds(coords[i].x,coords[i].y)) {
					// damage the pixel
					var pixel = pixelMap[coords[i].x][coords[i].y];
					var info = elements[pixel.element];
					if(typeof(beforeFunction) === "function") {
						beforeFunction(pixel,x,y,radius,fire,smoke,power,damage);
					};
					if(!pixel || pixel.del || typeof(pixel) == "undefined" || isEmpty(coords[i].x,coords[i].y)) {
						continue
					};
					if (info.hardness) { // lower damage depending on hardness(0-1)
						if (info.hardness < 1) {
							damage = damage * ((1 - info.hardness)*10);
						}
						else { damage = 0; }
					}
					if (damage > 0.9) {
						if(fire !== null) {
							while (Array.isArray(fire)) {
								fire = randomChoice(fire);
							};
							if(fire !== null) { changePixel(pixel,fire,changeTemp) };
						} else {
							deletePixel(pixel.x,pixel.y);
						}
						continue;
					}
					else if (damage > 0.25) {
						if (info.breakInto) {
							// if it is an array, choose a random item, else just use the value
							if (info.breakInto !== undefined) {
								breakPixel(pixel);
							} else {
								while (Array.isArray(fire)) {
									fire = randomChoice(fire);
								};
								if(fire !== null) { changePixel(pixel,fire,changeTemp) };
							}
							if(info.onExplosionBreakOrSurvive) {
								info.onExplosionBreakOrSurvive(pixel,x,y,radius,fire,smoke,power,damage);
							};
							continue;
						}
						else {
							if(fire !== null) {
								while (Array.isArray(fire)) {
									fire = randomChoice(fire);
								};
								if(fire !== null) { changePixel(pixel,fire,changeTemp) };
							} else {
								deletePixel(pixel.x,pixel.y);
							}
							continue;
						}
					} else {
						if(info.onExplosionBreakOrSurvive) {
							info.onExplosionBreakOrSurvive(pixel,x,y,radius,fire,smoke,power,damage);
						};
					}
					if (damage > 0.75 && info.burn) {
						pixel.burning = true;
						pixel.burnStart = pixelTicks;
					}
					pixel.temp += damage*radius*power;
					pixelTempCheck(pixel);
					// set the pixel.vx and pixel.vy depending on the angle and power
					if (!elements[pixel.element].excludeRandom && !elements[pixel.element].excludeVelocity) {
						var angle = Math.atan2(pixel.y-y,pixel.x-x);
						pixel.vx = Math.round((pixel.vx|0) + Math.cos(angle) * (radius * power/10));
						pixel.vy = Math.round((pixel.vy|0) + Math.sin(angle) * (radius * power/10));
					}
					if(typeof(afterFunction) === "function") {
						//console.log(`running afterFunction ${afterFunction}`)
						//console.log(`arguments: ${pixel}, ${x}, ${y}, ${radius}, ${fire}, ${smoke}, ${power}, ${damage}`)
						afterFunction(pixel,x,y,radius,fire,smoke,power,damage);
					};
				};
			};
		};

		oldExplodeAt = explodeAt;
		/*explodeAt = function(x,y,radius,fire="fire") {
			var message = "Explosion ";
			var pixel = pixelMap[x]?.[y];
			if(pixel) { message += `of ${pixel.element} ` };
			message += `with radius ${radius} with "${fire}" at (${x},${y})`;
			console.log(message);
			
			oldExplodeAt(x,y,radius,fire="fire")
		};*/
		explodeAt = explodeAtPlus;

	//MORE CONFIGURABLE REACTION TEMPERATURE CHANGES ##

		function reactPixels(pixel1,pixel2) {
			var r = elements[pixel1?.element]?.reactions?.[pixel2?.element];
			if(!r) { return false };
			if (r.setting && !(settings[r.setting])) {
				return false;
			}
			var changeTemp = r.changeTemp ?? true
			// r has the attribute "y" which is a range between two y values
			// r.y example: [10,30]
			// return false if y is defined and pixel1's y is not in the range
			if (r.tempMin !== undefined && pixel1.temp < r.tempMin) {
				return false;
			}
			if (r.tempMax !== undefined && pixel1.temp > r.tempMax) {
				return false;
			}
			if (r.charged && !pixel.charge) {
				return false;
			}
			if (r.chance !== undefined && Math.random() > r.chance) {
				return false;
			}
			if (r.y !== undefined && (pixel1.y < r.y[0] || pixel1.y > r.y[1])) {
				return false;
			}
			if (r.elem1 !== undefined) {
				// if r.elem1 is an array, set elem1 to a random element from the array, otherwise set it to r.elem1
				if (Array.isArray(r.elem1)) {
					var elem1 = r.elem1[Math.floor(Math.random() * r.elem1.length)];
				} else { var elem1 = r.elem1; }

				if (elem1 == null) {
					deletePixel(pixel1.x,pixel1.y);
				}
				else {
					changePixel(pixel1,elem1,changeTemp);
				}
			}
			if (r.charge1) { pixel1.charge = r.charge1; }
			if (r.temp1) { pixel1.temp += r.temp1; pixelTempCheck(pixel1); }
			if (r.color1) { // if it's a list, use a random color from the list, else use the color1 attribute
				pixel1.color = pixelColorPick(pixel1, Array.isArray(r.color1) ? r.color1[Math.floor(Math.random() * r.color1.length)] : r.color1);
			}
			if (r.attr1) { // add each attribute to pixel1
				for (var key in r.attr1) {
					pixel1[key] = r.attr1[key];
				}
			}
			if (r.elem2 !== undefined) {
				// if r.elem2 is an array, set elem2 to a random element from the array, otherwise set it to r.elem2
				if (Array.isArray(r.elem2)) {
					var elem2 = r.elem2[Math.floor(Math.random() * r.elem2.length)];
				} else { var elem2 = r.elem2; }

				if (elem2 == null) {
					deletePixel(pixel2.x,pixel2.y);
				}
				else {
					changePixel(pixel2,elem2,changeTemp);
				}
			}
			if (r.charge2) { pixel2.charge = r.charge2; }
			if (r.temp2) { pixel2.temp += r.temp2; pixelTempCheck(pixel2); }
			if (r.color2) { // if it's a list, use a random color from the list, else use the color2 attribute
				pixel2.color = pixelColorPick(pixel2, Array.isArray(r.color2) ? r.color2[Math.floor(Math.random() * r.color2.length)] : r.color2);
			}
			if (r.attr2) { // add each attribute to pixel2
				for (var key in r.attr2) {
					pixel2[key] = r.attr2[key];
				}
			}
			if (r.func) { r.func(pixel1,pixel2); }
			return r.elem1!==undefined || r.elem2!==undefined;
		}

	//CHANGES TO ELECTRICITY CODE (doElectricity changes) ##

		function doElectricity(pixel) {
			if(isNaN(pixel.charge)) {
				pixel.charge = 0;
			};
			if (pixel.charge) {
				// Check each adjacent pixel, if that pixel's charge is false, set it to the same charge
				for (var i = 0; i < adjacentCoords.length; i++) {
					var x = pixel.x+adjacentCoords[i][0];
					var y = pixel.y+adjacentCoords[i][1];
					if (!isEmpty(x,y,true)) {
						var newPixel = pixelMap[x][y];
						var con = elements[newPixel.element].conduct;
						if (con == undefined) {continue}
						if (elements[pixel.element].noConduct?.length && elements[pixel.element].noConduct.includes(newPixel.element)) {continue};
						if (Math.random() < con) { // If random number is less than conductivity
							if (!newPixel.charge && !newPixel.chargeCD) {
								newPixel.charge = isNaN(pixel.charge) ? 0 : pixel.charge; //Actually set it to the same charge
								if (elements[newPixel.element].colorOn) {
									newPixel.color = pixelColorPick(newPixel);
								}
								if(elements[newPixel.element].onCharge) {
									pixel.charge ??= 0;
									if(isNaN(pixel.charge)) { pixel.charge = 0 };
									elements[newPixel.element].onCharge(pixel);
								};
							}
						}
						else if (elements[newPixel.element].insulate != true && !elements[newPixel.element].noResistance) { // Otherwise heat the pixel (Resistance simulation)
							newPixel.temp += isNaN(pixel.charge) ? 0.25 : pixel.charge/4;
							pixelTempCheck(newPixel);
						}
					}
				}
				pixel.charge -= 0.25;
				if (pixel.charge <= 0) {
					delete pixel.charge;
					//console.log(elements[pixel.element].chargeCD);
					var chargeCd = elements[pixel.element].chargeCD ?? 4;
					pixel.chargeCD = chargeCd; //Customizable chargeCD
				}
			}
			// Lower charge cooldown
			else if (pixel.chargeCD) {
				pixel.chargeCD -= 1;
				if (pixel.chargeCD <= 0) {
					delete pixel.chargeCD;
					if (elements[pixel.element].colorOn) {
						pixel.color = pixelColorPick(pixel);
					}
				}
			}
		}

	//MORE CONFIGURABLE FIRE (fireMod changes) ##

		//Variable
		fireSpawnBlacklist = ["fire","cold_fire","rad_fire"];

		//doBurning
		function doBurning(pixel) {
			if (pixel.burning) { // Burning
				pixel.burnStart ??= pixelTicks;
				var info = elements[pixel.element];
				var burnTempChange = info.burnTempChange ?? 1;
				var fireIsCold;
				var fire = info.fireElement === undefined ? "fire" : info.fireElement; //allow null but disallow undefined
				//console.log(info.fireElement,fire);
				while(fire instanceof Array) {
					fire = fire[Math.floor(Math.random()*fire.length)];
				};
				var fireTemp = info.fireSpawnTemp ?? pixel.temp;
				var fireChance = info.fireSpawnChance ?? 10;
				var fireIsCold = (fire === "cold_fire");
				//var fireInfo = fire === null ? null : elements[fire];

				pixel.temp += burnTempChange;
				pixelTempCheck(pixel);

				for (var i = 0; i < adjacentCoords.length; i++) { // Burn adjacent pixels
					var x = pixel.x+adjacentCoords[i][0];
					var y = pixel.y+adjacentCoords[i][1];
					if (!isEmpty(x,y,true)) {
						var newPixel = pixelMap[x][y];
						var newInfo = elements[newPixel.element];
						var newFireIsCold;
						var newFire = newInfo.fireElement == undefined ? "fire" : newInfo.fireElement;
						while(newFire instanceof Array) {
							newFire = newFire[Math.floor(Math.random()*newFire.length)];
						};
						newFireIsCold = (newFire === "cold_fire");
						//console.log(`burning pixel ${pixel.element}: ${fire} (${fireIsCold}) / burned element ${newPixel.element}: ${newFire} (${newFireIsCold})`);
						if((!fireIsCold && !newFireIsCold) || (fireIsCold && newFireIsCold)) {
							if (elements[newPixel.element].burn && !newPixel.burning) {
								if (Math.floor(Math.random()*100) < elements[newPixel.element].burn) {
									newPixel.burning = true;
									newPixel.burnStart = pixelTicks;
								}
							}
						}
					}
				}

				if ((pixelTicks - pixel.burnStart > (info.burnTime || 200)) && Math.floor(Math.random()*100)<(info.burn || 10)) {
					var burnInto = info.burnInto ?? "fire";
					while(burnInto instanceof Array) {
						burnInto = burnInto[Math.floor(Math.random()*burnInto.length)];
					};
					changePixel(pixel,burnInto,burnInto !== "smoke");
					//console.log("ass");
					pixel.temp = fireTemp;
					if (info.fireColor != undefined && burnInto == "fire") {
						pixel.color = pixelColorPick(pixel,info.fireColor);
					}
					else {
						pixel.color = pixelColorPick(pixel)
					}
				}
				else if (Math.floor(Math.random()*100)<fireChance && !fireSpawnBlacklist.includes(pixel.element)) { // Spawn fire
					//console.log(fire);
					if (isEmpty(pixel.x,pixel.y-1)) {
						if(fire !== null) {
							createPixel(fire,pixel.x,pixel.y-1);
							pixelMap[pixel.x][pixel.y-1].temp = fireTemp;
							if (info.fireColor != undefined) {
								pixelMap[pixel.x][pixel.y-1].color = pixelColorPick(pixelMap[pixel.x][pixel.y-1],info.fireColor);
							};
						};
					}
					// same for below if top is blocked
					else if (isEmpty(pixel.x,pixel.y+1)) {
						if(fire !== null) {
							createPixel(fire,pixel.x,pixel.y+1);
							pixelMap[pixel.x][pixel.y+1].temp = fireTemp;
							if (info.fireColor != undefined) {
								pixelMap[pixel.x][pixel.y+1].color = pixelColorPick(pixelMap[pixel.x][pixel.y+1],info.fireColor);
							};
						};
					}
				}
			}
		}

		//No changeTemp for fire=>smoke
		elements.fire.tick = function(pixel){
			if (pixel.start === pixelTicks) {return}
			if (pixel.charge && elements[pixel.element].behaviorOn) {
				pixelTick(pixel)
			}
			var move1Spots = [
				[pixel.x, pixel.y-1],
				[pixel.x+1, pixel.y-1],
				[pixel.x-1, pixel.y-1],
			]
			var moved = false;
			for (var i = 0; i < move1Spots.length; i++) {
				var coords = move1Spots[Math.floor(Math.random()*move1Spots.length)];
				coords = {x: coords[0], y: coords[1]};
				if(!isEmpty(coords.x,coords.y,true) && pixelMap[coords.x]?.[coords.y]?.element == pixel.element && pixelMap[coords.x][coords.y].temp < pixel.temp) {
					swapPixels(pixel,pixelMap[coords.x][coords.y]);
					moved = true; break
				} else {
					if (tryMove(pixel, coords.x, coords.y)) { moved = true; break; }
					else { move1Spots.splice(move1Spots.indexOf(coords), 1);}
				}
			}
			if (!moved && !pixel.del) {
				var move2Spots = [
					[pixel.x, pixel.y+1],
					[pixel.x+1, pixel.y],
					[pixel.x-1, pixel.y],
				]
				for (var i = 0; i < move2Spots.length; i++) {
					var coords = move2Spots[Math.floor(Math.random()*move2Spots.length)];
					if (tryMove(pixel, coords[0], coords[1])) { break; }
					else { move2Spots.splice(move2Spots.indexOf(coords), 1); }
				}
			}
			if (!pixel.del) { doDefaults(pixel); }
			if (!pixel.del && settings.burn===0 && (pixelTicks-pixel.start > 70) && Math.random() < 0.1 ) { changePixel(pixel,"smoke",false) };
		};

		//New elements
		elements.cold_fire.burning = true;
		elements.cold_fire.burnTempChange = -1;
		elements.cold_fire.burnTime = 25;
		elements.cold_fire.burnInto = "cold_smoke";
		elements.cold_fire.fireElement = "cold_fire";
		elements.cold_fire.behavior = [
			"M1|M1|M1",
			"M2|XX|M2",
			"XX|M2|XX"
		],

		elements.cold_smoke = {
			color: "#282848",
			behavior: behaviors.DGAS,
			reactions: {
				"steam": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"rain_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"snow_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"hail_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"acid_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,12], "setting":"clouds" },
				"fire_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,12], "setting":"clouds" },
				"pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" }
			},
			temp: -100,
			tempHigh: 0,
			stateHigh: "smoke",
			tempLow: -114,
			stateLow: "cold_fire",
			category: "gases",
			state: "gas",
			density: 1280,
			stain: 0.075,
		};

		elements.rad_fire = { //this is BBB
			color: ["#daff21","#a6ff00","#ffff00"],
			behavior: [
				"XX|CR:radiation%0.1|XX",
				"CR:radiation%0.1|XX|CR:radiation%0.1",
				"XX|CR:radiation%0.1|XX",
			],
			tick: function(pixel) {
				if(Math.random() < 0.4) {
					pixel.temp++;
				};

				if(Math.random() < 0.05) { //5%/t to radify
					if(typeof(transformAdjacent) === "function" && typeof(radioactiveObject) === "object") {
						transformAdjacent(pixel,radioactiveObject);
					};
				};

				var move1Spots = [[-1,-1],[0,-1],[1,-1]];
				var move2Spots = [[-1,0],[0,1],[1,0]];

				var randomMove1 = move1Spots[Math.floor(Math.random() * move1Spots.length)];

				if(!tryMove(pixel, pixel.x+randomMove1[0], pixel.y+randomMove1[1])) {
					//console.log((pixel.x+randomMove1[0]) + " " + (pixel.y+randomMove1[1]))
					var newPixel = null;
					if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
						newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
					};
					if(outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1]) || !reactionStealer(pixel,newPixel,"radiation")) {
						var randomMove2 = move2Spots[Math.floor(Math.random() * move2Spots.length)];
						if(!tryMove(pixel, pixel.x+randomMove2[0], pixel.y+randomMove2[1])) {
							var newPixel = null;
							if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
								newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
							};
							if(newPixel !== null) { reactionStealer(pixel,newPixel,"radiation") };
						};
					};
				};
				doDefaults(pixel);
			},
			reactions: { //fire + radiation reacts
				//Merged water-radiation reactions, plus altered seltzer
				"water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				"steam": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				"carbon_dioxide": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				"dirty_water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				"salt_water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				"sugar_water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				"seltzer": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
				//Radiation reactions added programatically
			},
			temp:800,
			tempLow:150,
			stateLow: "rad_smoke",
			//tempHigh: 7000,
			//stateHigh: "rad_plasma",
			category: "energy",
			burning: true,
			fireElement: "radiation",
			state: "gas",
			density: 0.1,
			ignoreAir: true,
		};

		elements.rad_smoke = {
			color: "#415c25",
			behavior: behaviors.DGAS,
			behavior: [
				"XX|CR:radiation%0.05|XX",
				"CR:radiation%0.05|XX|CR:radiation%0.05",
				"XX|CR:radiation%0.05|XX",
			],
			tick: function(pixel) {
				if(Math.random() < 0.05) {
					deletePixel(pixel.x,pixel.y);
					return;
				};

				if(Math.random() < 0.2) {
					pixel.temp++;
				};

				if(Math.random() < 0.04) { //4%/t to radify
					if(typeof(transformAdjacent) === "function" && typeof(radioactiveObject) === "object") {
						transformAdjacent(pixel,radioactiveObject);
					};
				};

				var move1Spots = [[0,-1],[1,0],[0,1],[-1,0]];
				var move2Spots = [[-1,-1],[1,-1],[1,1],[-1,1]];

				var randomMove1 = move1Spots[Math.floor(Math.random() * move1Spots.length)];
				if(!tryMove(pixel, pixel.x+randomMove1[0], pixel.y+randomMove1[1])) {
					//console.log((pixel.x+randomMove1[0]) + " " + (pixel.y+randomMove1[1]))
					var newPixel = null;
					if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
						newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
					};
					if(outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1]) || !reactionStealer(pixel,newPixel,"radiation")) {
						var randomMove2 = move2Spots[Math.floor(Math.random() * move2Spots.length)];
						if(!tryMove(pixel, pixel.x+randomMove2[0], pixel.y+randomMove2[1])) {
							var newPixel = null;
							if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
								newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
							};
							if(newPixel !== null) { reactionStealer(pixel,newPixel,"radiation") };
						};
					};
				};
				doDefaults(pixel);
			},
			reactions: {
				//Spreading
				"liquid_fire": { "elem2":"liquid_rad_fire", "chance":0.2 },
				"fire": { "elem2":"rad_fire", "chance":0.2 },
				"smoke": { "elem2":"rad_smoke", "chance":0.2 },
				/*"steam": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"rain_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"snow_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"hail_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
				"acid_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,12], "setting":"clouds" },
				"fire_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,12], "setting":"clouds" },
				"pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },*/
				//Radiation reactions added programatically
			},
			temp: 134,
			tempHigh: 595,
			stateHigh: "rad_fire",
			category: "gases",
			state: "gas",
			density: 1340,
			stain: 0.075,
		};

		elements.holy_fire = {
			color: ["#FFFF96","#FFBF49","#CE743B"], //placeholder
			tick: function(pixel) {

				var moveResult = tryMoveAndReturnBlockingPixel(pixel,pixel.x + randomSign(),pixel.y - 1);
				var blockingPixels = [];
				var secondMoveResult = null;
				
				if(typeof(moveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(moveResult.element))) {
					blockingPixels.push(moveResult)
				};
				
				//if move1Result = true then nothing else happens
				
				if(moveResult !== true) {
					var coords = randomChoice([[-1,0],[0,1],[1,0]]).map(offsetPair => addArraysInPairs(offsetPair,[pixel.x,pixel.y]));
					secondMoveResult = tryMoveAndReturnBlockingPixel(pixel,...coords);
					
					if(typeof(secondMoveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(secondMoveResult.element))) {
						blockingPixels.push(secondMoveResult)
					};
				};

				if(blockingPixels.length > 0) {
					blockingPixels.forEach(function(pixel) {
						var blessRxn = elements.bless.reactions[pixel.element];
						if(typeof(blessRxn) == "object") { 
							var elem2 = blessRxn.elem2;
							if(elem2 !== null) {
								while(Array.isArray(elem2)) { elem2 = randomChoice(elem2) };
								changePixel(pixel,elem2)
							}
						};
						var value = Math.random();
						if(value < 0.01) {
							if(pixel.burnInto) {
								finishBurn(pixel)
							} else {
								changePixel(pixel,randomChoice(["ash","ash","light"]))
							}
						} else if(value < 0.20) {
							pixel.burning = true;
							pixel.burnStart ??= pixelTicks;
						} else if(value < 0.205) {
							changePixel(pixel,randomChoice(["ash","ash","light"]))
						} else {
							pixel.temp += 10; pixelTempCheck(pixel)
						};
						return
					})
				};
				doDefaults(pixel);
			},
			temp:6000,
			tempLow:1000,
			stateLow: ["bless","fire"],
			burnInto: ["bless","fire"],
			category: "energy",
			burning: true,
			burnTime: 500,
			burnTempChange: 8,
			fireElement: ["bless","plasma"],
			ignore: ["ash","light","bless","plasma","fire","smoke","liquid_holy_fire"].concat(searchElements("haseulite")).concat("haseulite_singularity"),
			state: "gas",
			density: 0.08,
			ignoreAir: true
		};
		
		elements.holy_bomb = {
			color: ["#dbb260", "#94591e"],
			tick: function(pixel) {
				if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
					var newPixel = pixelMap[pixel.x][pixel.y-1];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					if(newInfo.state !== "gas" && newElement !== pixel.element) {
						explodeAtPlus(pixel.x,pixel.y,13,"holy_fire","plasma",null,firebombFire);
					};
				};
				if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
					var newPixel = pixelMap[pixel.x][pixel.y+1];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					if(newInfo.state !== "gas" && newElement !== pixel.element) {
						explodeAtPlus(pixel.x,pixel.y,13,"holy_fire","plasma",null,firebombFire);
					};
				};
				if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
					explodeAtPlus(pixel.x,pixel.y,13,"holy_fire","plasma",null,firebombFire);
				};
				if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
					Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
				};
			},
			category: "weapons",
			state: "solid",
			density: 4000,
			excludeRandom: true,
			desc: "A bomb that burns the world to pure ash. <br/>To enable automatic bomb generation, set the generateBombs query parameter.",
		};

		elements.bless.ignore ??= [];
		elements.bless.ignore.push("holy_fire");

		elements.plasma_explosion = {
			color: ["#c78fff","#ea8fff","#be8fff"],
			tick: function(pixel) {
				explodeAtPlus(pixel.x,pixel.y,10,"plasma","fire");
				return
			},
			temp: 6500,
			category: "energy",
			state: "gas",
			density: 1000,
			excludeRandom: true,
			noMix: true
		};

		elements.god_slayer_fire = {
			color: ["#FFBACE","#FC6DCA","#9954B0"],
			tick: function(pixel) {

				var moveResult = tryMoveAndReturnBlockingPixel(pixel,pixel.x + randomSign(),pixel.y - 1);
				var blockingPixels = [];
				var secondMoveResult = null;
				
				if(typeof(moveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(moveResult.element))) {
					blockingPixels.push(moveResult)
				};
				
				//if move1Result = true then nothing else happens
				
				if(moveResult !== true) {
					var coords = randomChoice([[-1,0],[0,1],[1,0]]).map(offsetPair => addArraysInPairs(offsetPair,[pixel.x,pixel.y]));
					secondMoveResult = tryMoveAndReturnBlockingPixel(pixel,...coords);
					
					if(typeof(secondMoveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(secondMoveResult.element))) {
						blockingPixels.push(secondMoveResult)
					};
				};

				if(blockingPixels.length > 0) {
					blockingPixels.forEach(function(pixel) {
						var value = Math.random();
						var randomDefaultResult = randomChoice(["ash","slag","plasma"]);
						var oldTemp = pixel.temp;
						if(value < 0.03) {
							if(pixel.stateHigh) {
								meltPixel(pixel);
							}
						} else if(value < 0.06) {
							if(pixel.burnInto) {
								finishBurn(pixel);
							} else {
								changePixel(pixel,randomDefaultResult)
							}
						} else if(value < 0.09) {
							if(pixel.breakInto) {
								breakPixel(pixel);
							} else {
								changePixel(pixel,randomDefaultResult)
							}
						} else if(value < 0.24) {
							pixel.burning = true;
							pixel.burnStart ??= pixelTicks;
						} else if(value < 0.245) {
							changePixel(pixel,randomDefaultResult)
						} else {
							pixel.temp += 25; pixelTempCheck(pixel)
						};
						if(pixel) {
							pixel.temp = Math.max(oldTemp,pixel.temp)
						};
						return
					})
				};

				doDefaults(pixel);
			},
			temp:10000,
			tempLow:1500,
			stateLow: ["plasma_explosion","plasma","plasma","plasma","plasma","plasma","plasma","plasma"],
			burnInto: ["plasma_explosion","plasma","plasma","plasma","plasma","plasma","plasma","plasma"],
			category: "energy",
			ignore: ["ash","slag","wall","plasma","fire","smoke","liquid_god_slayer_fire"].concat(searchElements("haseulite")).concat("haseulite_singularity"),
			burning: true,
			burnTime: 500,
			burnTempChange: 10,
			fireElement: ["plasma"],
			state: "gas",
			density: 0.07,
			ignoreAir: true
		};

		elements.liquid_holy_fire = {
			color: ["#FFFF96","#FFBF49","#CE743B"], //placeholder
			tick: function(pixel) {

				var moveResult = tryMoveAndReturnBlockingPixel(pixel,pixel.x + randomIntegerBetweenTwoValues(-1,1),pixel.y + 1);
				var blockingPixels = [];
				var secondMoveResult = null;
				
				if(typeof(moveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(moveResult.element))) {
					blockingPixels.push(moveResult)
				};
				
				//if move1Result = true then nothing else happens
				
				if(moveResult !== true) {
					var coords = [randomSign(),0].map(offsetPair => addArraysInPairs(offsetPair,[pixel.x,pixel.y]));
					secondMoveResult = tryMoveAndReturnBlockingPixel(pixel,...coords);
					
					if(typeof(secondMoveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(secondMoveResult.element))) {
						blockingPixels.push(secondMoveResult)
					};
				};

				if(blockingPixels.length > 0) {
					blockingPixels.forEach(function(pixel) {
						var blessRxn = elements.bless.reactions[pixel.element];
						if(typeof(blessRxn) == "object") { 
							var elem2 = blessRxn.elem2;
							if(elem2 !== null) {
								while(Array.isArray(elem2)) { elem2 = randomChoice(elem2) };
								changePixel(pixel,elem2)
							}
						};
						var value = Math.random();
						if(value < 0.01) {
							if(pixel.burnInto) {
								finishBurn(pixel)
							} else {
								changePixel(pixel,randomChoice(["ash","ash","light"]))
							}
						} else if(value < 0.20) {
							pixel.burning = true;
							pixel.burnStart ??= pixelTicks;
						} else if(value < 0.205) {
							changePixel(pixel,randomChoice(["ash","ash","light"]))
						} else {
							pixel.temp += 10; pixelTempCheck(pixel)
						};
						return
					})
				};
				doDefaults(pixel);
			},
			temp:8000,
			tempLow:2000,
			stateLow: "holy_fire",
			burnInto: ["bless","fire"],
			category: "energy",
			burning: true,
			burnTime: 1000,
			burnTempChange: 10,
			fireElement: "holy_fire",
			ignore: ["light","bless","wall","plasma","fire","smoke","holy_fire"].concat(searchElements("haseulite")).concat("haseulite_singularity"),
			state: "liquid",
			density: 270,
			ignoreAir: true
		};
		
		elements.liquid_god_slayer_fire = {
			color: ["#FFBACE","#FC6DCA","#9954B0"],
			tick: function(pixel) {

				var moveResult = tryMoveAndReturnBlockingPixel(pixel,pixel.x + randomIntegerBetweenTwoValues(-1,1),pixel.y + 1);
				var blockingPixels = [];
				var secondMoveResult = null;
				
				if(typeof(moveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(moveResult.element))) {
					blockingPixels.push(moveResult)
				};
				
				//if move1Result = true then nothing else happens
				
				if(moveResult !== true) {
					var coords = [randomSign(),0].map(offsetPair => addArraysInPairs(offsetPair,[pixel.x,pixel.y]));
					secondMoveResult = tryMoveAndReturnBlockingPixel(pixel,...coords);
					
					if(typeof(secondMoveResult) == "object" && !(elements[pixel.element].ignore.concat(pixel.element).includes(secondMoveResult.element))) {
						blockingPixels.push(secondMoveResult)
					};
				};

				if(blockingPixels.length > 0) {
					blockingPixels.forEach(function(pixel) {
						var value = Math.random();
						var randomDefaultResult = randomChoice(["ash","slag","plasma"]);
						var oldTemp = pixel.temp;
						if(value < 0.03) {
							if(pixel.stateHigh) {
								meltPixel(pixel);
							}
						} else if(value < 0.06) {
							if(pixel.burnInto) {
								finishBurn(pixel);
							} else {
								changePixel(pixel,randomDefaultResult)
							}
						} else if(value < 0.09) {
							if(pixel.breakInto) {
								breakPixel(pixel);
							} else {
								changePixel(pixel,randomDefaultResult)
							}
						} else if(value < 0.24) {
							pixel.burning = true;
							pixel.burnStart ??= pixelTicks;
						} else if(value < 0.245) {
							changePixel(pixel,randomDefaultResult)
						} else {
							pixel.temp += 25; pixelTempCheck(pixel)
						};
						if(pixel) {
							pixel.temp = Math.max(oldTemp,pixel.temp)
						};
						return
					})
				};

				doDefaults(pixel);
			},
			temp:15000,
			tempLow:2000,
			stateLow: ["plasma_explosion","liquid_plasma","liquid_plasma","liquid_plasma","liquid_plasma","liquid_plasma","liquid_plasma","liquid_plasma"],
			burnInto: "god_slayer_fire",
			category: "energy",
			ignore: ["ash","slag","wall","plasma","fire","smoke","god_slayer_fire"].concat(searchElements("haseulite")).concat("haseulite_singularity"),
			burning: true,
			burnTime: 1000,
			burnTempChange: 14,
			fireElement: "god_slayer_fire",
			state: "liquid",
			density: 380,
			ignoreAir: true
		};

		elements.god_slayer_bomb = {
			color: ["#a43dcc", "#49b6d1"],
			tick: function(pixel) {
				if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
					var newPixel = pixelMap[pixel.x][pixel.y-1];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					if(newInfo.state !== "gas" && newElement !== pixel.element) {
						explodeAtPlus(pixel.x,pixel.y,40,"god_slayer_fire","plasma");
					};
				};
				if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
					var newPixel = pixelMap[pixel.x][pixel.y+1];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					if(newInfo.state !== "gas" && newElement !== pixel.element) {
						explodeAtPlus(pixel.x,pixel.y,40,"god_slayer_fire","plasma");
					};
				};
				if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
					explodeAtPlus(pixel.x,pixel.y,40,"god_slayer_fire","plasma");
				};
				if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
					Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
				};
			},
			category: "weapons",
			state: "solid",
			density: 4500,
			excludeRandom: true,
			desc: "A bomb that makes gods tremble. <br/>To enable automatic bomb generation, set the generateBombs query parameter.",
		};

		elements.cloner.burnTime = Infinity;
		elements.cloner.burnInto = "cloner";

		elements.cold_torch = {
			"color": "#4394d6",
			"behavior": [
				"XX|CR:cold_fire|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			"reactions": {
				"water": { "elem1": "wood" },
				"sugar_water": { "elem1": "wood" },
				"salt_water": { "elem1": "wood" },
				"seltzer": { "elem1": "wood" },
				"dirty_water": { "elem1": "wood" },
				"pool_water": { "elem1": "wood" },
				"steam": { "elem1": "wood" },
				"smog": { "elem1": "wood" },
				"rain_cloud": { "elem1": "wood" },
				"cloud": { "elem1": "wood" },
				"snow_cloud": { "elem1": "wood" },
				"hail_cloud": { "elem1": "wood" },
				"black_damp": { "elem1": "wood" }
			},
			"temp": -200,
			"category": "special",
			"breakInto": "sawdust",
			"tempHigh": 600,
			"stateHigh": "wood",
		};

		var GTDR = { "elem1": "torch", chance: 0.01 }; //grand torch degradation reaction

		elements.grand_torch = {
			"color": "#FFBF2F",
			"tick": function(pixel) {
				var coords = circleCoords(pixel.x,pixel.y,2).concat([[-1,-2],[1,-2],[-1,2],[1,2],[-2,-1],[2,-1],[-2,1],[2,1],[-1,-3],[0,-3],[1,-3],[0,-4],[0,-5]].map(
					function(offsets) {
						return {x: offsets[0]+pixel.x, y: offsets[1]+pixel.y}
					}
				));
				for(var i = 0; i < coords.length; i++) {
					var coordPair = coords[i];
					if(outOfBounds(coordPair.x,coordPair.y)) { continue };
					if(coordPair.x == pixel.x && coordPair.y == pixel.y) { continue };
					if(!(isEmpty(coordPair.x,coordPair.y))) { continue };
					var newPixel = tryCreatePixelReturn("fire",coordPair.x,coordPair.y);
					if(typeof(newPixel) == "object") {
						newPixel.temp = pixel.temp
					}
				};
			},
			"reactions": {
				"water": GTDR,
				"sugar_water": GTDR,
				"salt_water": GTDR,
				"seltzer": GTDR,
				"dirty_water": GTDR,
				"pool_water": GTDR,
				"steam": GTDR,
				"smog": GTDR,
				"rain_cloud": GTDR,
				"cloud": GTDR,
				"snow_cloud": GTDR,
				"hail_cloud": GTDR,
				"black_damp": { "elem1": "wood", chance: 0.02 },
				"magic": { "elem1": ["grand_torch","grand_torch","grand_plasma_torch"], "elem2": null, changeTemp: true }
			},
			"temp": 1500,
			"category": "special",
			"breakInto": "charcoal",
			"tempHigh": 6000,
			"stateHigh": "grand_plasma_torch",
			"tempLow": 1000,
			"stateLow": "torch",
		};
		
		elements.torch.reactions ??= {};
		elements.torch.reactions.magic = { elem1: ["torch","torch","grand_torch"], elem2: null, changeTemp: true };
		elements.torch.tempHigh = 1500;
		elements.torch.stateHigh = "grand_torch";

		var PTDR = { "elem1": "wood", chance: 0.003 };

		elements.plasma_torch = {
			"color": "#86579c",
			"tick": function(pixel) {
				var offset;
				switch((pixel.r ?? 0) % 4) {
					case 0.5:
						offset = [-1,-1];
						break;
					case 1:
						offset = [-1,0];
						break;
					case 1.5:
						offset = [-1,1];
						break;
					case 2:
						offset = [0,1];
						break;
					case 2.5:
						offset = [1,1];
						break;
					case 3:
						offset = [1,0];
						break;
					case 3.5:
						offset = [1,-1];
						break;
					case 4:
					case 0:
					default:
						offset = [0,-1];
						break;
				};
				//base strength (distance) 2, plus 1 for each 500*C above 7K*C; when charged, increase by 10% for each full or partial unit of charge (0.25 yields 10%, 1 yields 10%, 1.55 yields 20%, 2 yields 20%...)
				var strength = Math.max(2,Math.min(300, ((2 + Math.floor((pixel.temp - 7000) / 500)) * (1 + (Math.ceil(pixel.charge ?? 0) * 0.1))))); //bound to 2-300, in part for performance reasons
				//console.log(strength);
				for(var i = 1; i <= strength; i++) {
					var newOffsets = offset.map(x => x * i);
					var finalPos = {x: pixel.x + newOffsets[0], y: pixel.y + newOffsets[1]};
					//console.log({x:pixel.x,y:pixel.y},finalPos);
					if(!(isEmpty(finalPos.x,finalPos.y))) {
						if(pixelMap[finalPos.x]?.[finalPos.y] && pixelMap[finalPos.x][finalPos.y].element == "plasma") {
							continue
						} else {
							break
						}
					};
					var newPlasma = tryCreatePixelReturn("plasma",finalPos.x,finalPos.y);
					if(!newPlasma) {
						break
					} else {
						newPlasma.temp = pixel.temp
					}
				}
			},
			"reactions": {
				"water": PTDR,
				"sugar_water": PTDR,
				"salt_water": PTDR,
				"seltzer": PTDR,
				"dirty_water": PTDR,
				"pool_water": PTDR,
				"steam": PTDR,
				"smog": PTDR,
				"rain_cloud": PTDR,
				"cloud": PTDR,
				"snow_cloud": PTDR,
				"hail_cloud": PTDR,
				"black_damp": { "elem1": "wood", change: 0.02 }
			},
			"temp": 7000,
			"category": "special",
			"breakInto": "charcoal",
			"tempLow": 4999,
			"stateLow": "grand_torch",
		};

		var GrPTDR = { "elem1": "plasma_torch", chance: 0.001 }; //grand plasma torch degradation reaction

		elements.grand_plasma_torch = {
			"color": "#b92eff",
			"tick": function(pixel) {
				var coords = circleCoords(pixel.x,pixel.y,4).concat([[4,4],[4,4],[3,3],[3,3],[1,1],[-1,-1],[-3,-3],[-4,-4],[-4,-4],[-3,-3],[-1,-1],[1,1],[0,0],[0,0],[0,0],[0,0],[1,1],[1,1],[2,2],[-1,-1],[-1,-1],[-2,-2]].map( //ae = filterCurrentPixels(function(pixel) { return pixel.element == "ivory_growth_crystal" }).map(px => [130-px.x,67-px.y])
					function(offsets) {
						return {x: offsets[0]+pixel.x, y: offsets[1]+pixel.y}
					}
				));
				for(var i = 0; i < coords.length; i++) {
					var coordPair = coords[i];
					if(outOfBounds(coordPair.x,coordPair.y)) { continue };
					if(coordPair.x == pixel.x && coordPair.y == pixel.y) { continue };
					if(!(isEmpty(coordPair.x,coordPair.y))) { continue };
					var newPixel = tryCreatePixelReturn("plasma",coordPair.x,coordPair.y);
					if(typeof(newPixel) == "object") {
						newPixel.temp = pixel.temp
					}
				};
			},
			"reactions": {
				"water": GrPTDR,
				"sugar_water": GrPTDR,
				"salt_water": GrPTDR,
				"seltzer": GrPTDR,
				"dirty_water": GrPTDR,
				"pool_water": GrPTDR,
				"steam": GrPTDR,
				"smog": GrPTDR,
				"rain_cloud": GrPTDR,
				"cloud": GrPTDR,
				"snow_cloud": GrPTDR,
				"hail_cloud": GrPTDR,
				"black_damp": { "elem1": "wood", chance: 0.02 },
			},
			"temp": 8500,
			"category": "special",
			"breakInto": "charcoal",
			"tempLow": 6000,
			"stateLow": "plasma_torch",
		};

		elements.rad_torch = {
			"color": "#85d643",
			"behavior": [
				"XX|CR:rad_fire|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			"reactions": {
				"water": { "elem1": "wood" },
				"sugar_water": { "elem1": "wood" },
				"salt_water": { "elem1": "wood" },
				"seltzer": { "elem1": "wood" },
				"dirty_water": { "elem1": "wood" },
				"pool_water": { "elem1": "wood" },
				"steam": { "elem1": "wood" },
				"smog": { "elem1": "wood" },
				"rain_cloud": { "elem1": "wood" },
				"cloud": { "elem1": "wood" },
				"snow_cloud": { "elem1": "wood" },
				"hail_cloud": { "elem1": "wood" },
				"black_damp": { "elem1": "wood" }
			},
			"temp": 800,
			"category": "special",
			"breakInto": "sawdust",
			"tempLow": -273,
			"stateHigh": "wood",
		};

		elements.mystic_torch = {
			"color": ["#8e27ba", "#b3297e"],
			"behavior": [
				"XX|CR:mystic_fire|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			"reactions": {
				"water": { "elem2": "steam" },
				"sugar_water": { "elem2": "steam" },
				"salt_water": { "elem1": "salt" },
				"seltzer": { "elem1": "steam" },
				"dirty_water": { "elem1": "steam" },
				"pool_water": { "elem1": "steam" },
				"smog": { "elem1": "smoke" },
				"rain_cloud": { "elem1": "cloud" },
				"snow_cloud": { "elem1": "cloud" },
				"hail_cloud": { "elem1": "cloud" },
				"black_damp": { "elem1": "wood" }
			},
			"temp": 20000,
			"category": "special",
			"breakInto": ["molten_ash","carbon_dioxide","charcoal","electric","magic"],
			"tempLow": 8000,
			"stateLow": new Array(99).fill("mystic_torch").concat("plasma_torch"), //1 in 100 chance to cool to a plasma torch, to mitigate inexplicable explosive cooling's tendency to cause the torch to spontaneously "downgrade"
			"hardness": 0.999
		};
		elements.mystic_fire.state = "gas";

		elements.napalm = {
			color: "#e0873e",
			behavior: [
				"XX|SA%40 AND ST|XX",
				"M2%10 AND SA%40 AND ST|XX|M2%10 AND SA%40 AND ST",
				"M2%50 AND M1%10|M1 AND SA%40 AND ST|M2%50 AND M1%10"
			],
			category: "weapons",
			state: "liquid",
			viscosity: 1000,
			density: 1200, //google was f***ing useless and i'm not searching that again, so arbitrary 1.2 it is
			burnTempChange: 3,
			burn: 300,
			burnTime: 500,
			temp: airTemp,
		};

		elements.hypernapalm = {
			name: "h y p e r n a p a l m", //HYPERNAPALM
			color: "#bd34eb",
			behavior: [
				"XX|SA%40 AND ST|XX",
				"M2%10 AND SA%40 AND ST|XX|M2%10 AND SA%40 AND ST",
				"M2%50 AND M1%10|M1 AND SA%40 AND ST|M2%50 AND M1%10"
			],
			category: "weapons",
			state: "liquid",
			viscosity: 1000,
			density: 1200,
			fireElement: "plasma",
			fireSpawnChance: 33,
			fireSpawnTemp: 7200,
			burnTempChange: 30,
			burn: 300,
			burnTime: 500,
		};

		elements.cold_napalm = {
			color: "#3e87e0",
			behavior: [
				"XX|SA%40 AND ST|XX",
				"M2%10 AND SA%40 AND ST|XX|M2%10 AND SA%40 AND ST",
				"M2%50 AND M1%10|M1 AND SA%40 AND ST|M2%50 AND M1%10"
			],
			category: "weapons",
			state: "liquid",
			viscosity: 1000,
			density: 1200,
			burn: 300,
			burnTime: 500,
			fireElement: "cold_fire",
			burnTempChange: -1,
			burnInto: "cold_fire",
		};

		elements.rad_napalm = {
			color: "#cdf760",
			behavior: [
				"XX|SA%40 AND ST AND CR:radiation%1|XX",
				"M2%10 AND SA%40 AND ST AND CR:radiation%1|HT%2.5|M2%10 AND SA%40 AND ST AND CR:radiation%1",
				"M2%50 AND M1%10|M1 AND SA%40 AND ST AND CR:radiation%1|M2%50 AND M1%10"
			],
			category: "weapons",
			state: "liquid",
			viscosity: 1000,
			density: 1300,
			burnTempChange: 2,
			burn: 300,
			burnTime: 500,
			fireElement: "rad_fire",
			temp: airTemp,
			burnInto: "rad_fire",
		};
		
		runAfterLoad(function() {
			if(eLists.spout) {
				eLists.spout.push("cold_torch");
				eLists.spout.push("rad_torch");
			};

			elements.liquid_fire = {
				color: ["#ff6b21","#ffa600","#ff4000"],
				behavior: [
				"XX|M2|XX",
				"M2|XX|M2",
				"M1|M1|M1",
				],
				reactions: {
					"water": { "elem1": "liquid_smoke" },
				},
				temp:600,
				tempLow:100,
				stateLow: "liquid_smoke",
				tempHigh: 7000,
				stateHigh: "liquid_plasma",
				category: "energy liquids",
				burning: true,
				burnTime: Infinity,
				burnTempChange: 2,
				fireSpawnChance: 5,
				state: "liquid",
				density: 200,
			};

			elements.liquid_cold_fire = {
				color: ["#21cbff","#006aff","#00ffff"],
				behavior: [
					"XX|M2|XX",
					"M2|XX|M2",
					"M1|M1|M1",
				],
				reactions: {
					"fire": { "elem1": "liquid_smoke", "elem2": "liquid_smoke" },
					"plasma": { "elem1": "le_liquid_light", "elem2": "le_liquid_light" }, //prefixed to avoid conflict with F&M liquid_light
				},
				temp:-200,
				tempHigh:0,
				stateHigh: "liquid_smoke",
				burning: true,
				burnTempChange: -2,
				fireSpawnChance: 5,
				burnTime: Infinity,
				fireElement: "cold_fire",
				category: "energy liquids",
				state: "liquid",
				density: 420,
			};

			elements.liquid_rad_fire = {
				color: ["#daff21","#a6ff00","#ffff00"],
				behavior: [
					"XX|CR:radiation%0.1|XX",
					"CR:radiation%0.1|XX|CR:radiation%0.1",
					"XX|CR:radiation%0.1|XX",
				],
				tick: function(pixel) {
					if(Math.random() < 0.4) {
						pixel.temp++;
					};

					if(Math.random() < 0.06) { //6%/t to radify
						if(typeof(transformAdjacent) === "function" && typeof(radioactiveObject) === "object") {
							transformAdjacent(pixel,radioactiveObject);
						};
					};

					var move1Spots = [[-1,1],[0,1],[1,1]];
					var move2Spots = [[-1,0],[0,-1],[1,0]];

					var randomMove1 = move1Spots[Math.floor(Math.random() * move1Spots.length)];
					if(!tryMove(pixel, pixel.x+randomMove1[0], pixel.y+randomMove1[1])) {
						//console.log((pixel.x+randomMove1[0]) + " " + (pixel.y+randomMove1[1]))
						var newPixel = null;
						if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
							newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
						};
						if(outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1]) || !reactionStealer(pixel,newPixel,"radiation")) {
							var randomMove2 = move2Spots[Math.floor(Math.random() * move2Spots.length)];
							if(!tryMove(pixel, pixel.x+randomMove2[0], pixel.y+randomMove2[1])) {
								var newPixel = null;
								if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
									newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
								};
								if(newPixel !== null) { reactionStealer(pixel,newPixel,"radiation") };
							};
						};
					};
					doDefaults(pixel);
				},
				reactions: { //fire + radiation reacts
					//Merged water-radiation reactions, plus altered seltzer
					"water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					"steam": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					"carbon_dioxide": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					"dirty_water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					"salt_water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					"sugar_water": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					"seltzer": { "elem1": "rad_smoke", "elem2":"rad_steam", "chance":0.4 },
					//Radiation reactions added programatically
				},
				temp:800,
				//tempLow:100,
				//stateLow: "liquid_smoke",
				//tempHigh: 7000,
				//stateHigh: "liquid_plasma",
				category: "energy liquids",
				burning: true,
				burnTime: Infinity,
				burnTempChange: 3,
				fireSpawnChance: 5,
				fireElement: "rad_fire",
				state: "liquid",
				density: 210,
			};

			elements.radiation.reactions.liquid_fire = { "elem2":"liquid_rad_fire", "chance":0.4 };
			elements.radiation.reactions.fire = { "elem2":"rad_fire", "chance":0.4 };
			elements.radiation.reactions.smoke = { "elem2":"rad_smoke", "chance":0.4 };

			runAfterLoad(function() {
				for(key in elements.radiation.reactions) {
					var value = elements.radiation.reactions[key];

					if(typeof(elements.rad_fire.reactions[key]) === "undefined") {
						elements.rad_fire.reactions[key] = value;
					};
				};
			});

			elements.unrealistically_flammable_gas.burnTempChange = 10;
			elements.unrealistically_flammable_gas.fireElement = "plasma";
			elements.unrealistically_flammable_powder.burnTempChange = 20;
			elements.unrealistically_flammable_powder.fireElement = "plasma";
			elements.burning_unrealistically_flammable_gas.burnTempChange = 15;
			elements.burning_unrealistically_flammable_gas.fireElement = "plasma";
			elements.burning_unrealistically_flammable_powder.burnTempChange = 30;
			elements.burning_unrealistically_flammable_powder.fireElement = "plasma";
		});

	//CONFIGURABLE MAXIMUM COLOR OFFSET (maxColorOffset) ##

		defaultColorOffset = 15;

		pixelColorPick = function(pixel,customColor=null,maxOffset=null) {
			var element = pixel.element;
			var elementInfo = elements[element];
			//if (elementInfo.behavior instanceof Array) {

			if (pixel.charge && elementInfo.colorOn) {
				customColor = elementInfo.colorOn;
			}
			if (customColor !== null) {
				if (Array.isArray(customColor)) {
					customColor = customColor[Math.floor(Math.random() * customColor.length)];
				} else if (customColor.startsWith?.("#")) {
					customColor = hexToRGB(customColor);
				}
				var rgb = customColor;
			}
			else {
				var rgb = elements[element].colorObject; // {r, g, b}
				// If rgb is an array, choose a random item
				if (Array.isArray(rgb)) {
					rgb = rgb[Math.floor(Math.random() * rgb.length)];
				}
			}
			// Randomly darken or lighten the RGB color
				//try maxOffset parameter, then info maxColorOffset, then default 15
			var offsetAmount; 
			if(maxOffset !== null) {
				offsetAmount = maxOffset;
			} else {
				offsetAmount = elementInfo?.maxColorOffset ?? defaultColorOffset;
			};

			var maxColorOffset = Math.floor(Math.random() * (Math.random() > 0.5 ? -1 : 1) * Math.random() * offsetAmount);
			var r = rgb.r + maxColorOffset;
			var g = rgb.g + maxColorOffset;
			var b = rgb.b + maxColorOffset;
			// Make sure the color is within the RGB range
			r = Math.max(0, Math.min(255, r));
			g = Math.max(0, Math.min(255, g));
			b = Math.max(0, Math.min(255, b));
			var color = "rgb("+r+","+g+","+b+")";

			/*}
			else {
				var color = elementInfo.color;
				if (Array.isArray(color)) {
					color = color[Math.floor(Math.random() * color.length)];
				}
			}*/
			return color;
		}

	//FIND MODE, PIXEL PROPERTIES LINKED TO SPECIAL CODE, CONFIGURABLE VISUAL DISTORTION AND VISUAL PIXEL SHAPE SETTINGS (acid_and_shapes.js) ##
	//two separate things i.e. not "pixel properties linked to special code, configurable visual distortion, and visual pixel shape settings" though there's basically no semantic difference
		var style = document.createElement('style');
		style.type = 'text/css';
		style.id = 'findStatusStylesheet';
		style.innerHTML = '.findStatus { color: #E11; text-decoration: none; }';
		document.getElementsByTagName('head')[0].appendChild(style);

		find = false;
		findElement = "sand";
		findColorPulseTimer = 0;
		findColorPulseTimerSubTimer = 0;

		function marasi(number) {
			return Math.min(255,Math.round(Math.abs(Math.sin(number) * 255)));
		};

		function updateFindDescription() {
			var elems = findElement;
			if(elems instanceof Array) {
				elems = elems.join(", ");
			};
			elements.find_toggle.desc = `<em>I'm running out of keybinds</em>

<span class="findStatus">If this text is green or underlined, find mode is on.</span> Currently finding: ${elems} <small style="font-size:80%;">(this display does not update automatically)</small>.
<span onclick=toggleFind() style="color: #ff00ff;";>Click here</span> to toggle find mode. This highlights the currently selected element.<br/>
<span style='color:#FF00FF' onClick=findFilterPrompt()>Click here to configure the find filter.</span>`;
		};

		function toggleFind() {
			if(find != true) {
				find = true;
				document.getElementById("findStatusStylesheet").innerHTML = '.findStatus { color: #1E1; text-decoration: underline; }'; //Displayed info doen't update until it's pulled up again, so I'm using CSS to dynamically change the color of an element.
			} else {
				find = false;
				document.getElementById("findStatusStylesheet").innerHTML = '.findStatus { color: #E11; text-decoration: none; }';
			};
			updateFindDescription();
		};

		elements.find_toggle = {
			color: ["#000000", "#000000", "#000000", "#000000", "#ff0000", "#ff0000", "#ff0000", "#ff0000"],
			name: "find toggle (look at info)",
			behavior: behaviors.SELFDELETE,
			category: "special",
			excludeRandom: true,
			desc: `
<em>I'm running out of keybinds</em>


<span class="findStatus">If this text is green or underlined, find mode is on.</span> Currently finding: sand <small style="font-size:80%;">(this display does not update automatically)</small>.
<span onclick=toggleFind() style="color: #ff00ff;";>Click here</span> to toggle find mode. This highlights the currently selected element.<br/>
<span style='color:#FF00FF' onClick=findFilterPrompt()>Click here to configure the find filter.</span>`,
		};

		function findFilterPrompt() {
			var preElement = prompt("Enter the elements you want to highlight\nSeparate multiple elements with commas");
			if(preElement === null || preElement === "") {
				return false;
			};
			if(preElement.includes(",")) {
				preElement = preElement.split(",");
				findElement = preElement;
				updateFindDescription();
				return findElement;
			};
			findElement = preElement;
			updateFindDescription();
			return findElement;
		};
		var incrementt = 0;

		var interval = setInterval( increment, 500/30);

		function increment(){
			incrementt = incrementt % (Math.PI*8.8) + (Math.PI/30);
		}

		shapeModes = ["normal","circles","triangles"];
		settings.shapeMode ??= 0;
		settings.doAcid ??= false;
		settings.acidFunction ??= "none";

		function getShapeMode() { 
			return shapeModes[settings.shapeMode] ?? "normal";
		};

		specialProperties = {
			/*red: {
				specialFunction: function(pixel) { pixel.color = "rgb(255,0,0)" }
			},*/
		};

		acidFunctions = {
			sin: Math.sin,
			tan: Math.tan,
			none: function(number) { return number }
		};

		var tickBehaviorStringCache = {
			POWDER: behaviors.POWDER.toString(),
			LIQUID: behaviors.LIQUID.toString(),
			UL_UR_OPTIMIZED: behaviors.UL_UR_OPTIMIZED.toString()
		};

		//I hate overwriting drawPixels
		runAfterAutogen(function() {
			//rAA because velocity.js already puts its redef in a rAL and rAA comes after that
			drawPixels = function(forceTick=false) {
				// Draw the current pixels
				var canvas = document.getElementById("game");
				var ctx = canvas.getContext("2d");
				// newCurrentPixels = shuffled currentPixels
				var newCurrentPixels = currentPixels.slice();
				var pixelsFirst = [];
				var pixelsLast = [];
				if (!paused || forceTick) {
					shuffleArray(newCurrentPixels);
				}
				/*{newCurrentPixels.sort(function(p) { // shuffle the pixels but keep elements[p.element].isGas last
					return 0.5 - Math.random();
				})} // shuffle the pixels if not paused*/
				for (var i = 0; i < newCurrentPixels.length; i++) {
					pixel = newCurrentPixels[i];
					if(typeof(elements[pixel.element]) == "undefined") { continue };
					if(typeof(pixel) == "undefined") { continue };
					//if (pixelMap[pixel.x][pixel.y] == undefined || currentPixels.indexOf(pixel) == -1) {continue}
					if (pixel.del) {continue}
					if (!paused || forceTick) {
						if(typeof(elements[pixel.element]) == "undefined") { continue };
						doVelocity(pixel);
						if (elements[pixel.element].tick) { // Run tick function if it exists
							elements[pixel.element].tick(pixel);
						}
						if (pixel.del) {continue}
						if (elements[pixel.element].behavior) { // Parse behavior if it exists
							pixelTick(pixel);
						}
					};
					if (elements[pixel.element].isGas) {
						pixelsLast.push(pixel);
					}
					else {
						pixelsFirst.push(pixel);
					}
				}
				ctx.clearRect(0, 0, canvas.width, canvas.height);
				if (!settings["bg"]) {ctx.clearRect(0, 0, canvas.width, canvas.height)}
				else {
					if(settings["bg"] instanceof Array) {
						settings.bgAngle ??= 90;
						var angle = (settings.bgAngle) * Math.PI / 180;
						ctx.fillStyle = ctx.createLinearGradient(
							0,
							0,
							canvas.width * Math.cos(angle) + 0,
							canvas.height * Math.sin(angle)
						);
						var colors = settings["bg"];
						for(i = 0; i < colors.length; i++) {
							var color = colors[i];
							var position = i / (colors.length - 1);
							ctx.fillStyle.addColorStop(position,color);
						};
					} else {
						ctx.fillStyle = settings["bg"];
					};
					ctx.fillRect(0, 0, canvas.width, canvas.height);
				}
				var pixelDrawList = pixelsFirst.concat(pixelsLast);
				for (var i = 0; i < pixelDrawList.length; i++) {
					pixel = pixelDrawList[i];
					if (pixelMap[pixel.x][pixel.y] == undefined) {continue}
					if (pixel.con) { pixel = pixel.con }
					if (view===null || view===3) {
						var colorOut = pixel.color;
						for(var imsorryaboutthelagthiswillcause in specialProperties) {
							if(pixel[imsorryaboutthelagthiswillcause] !== undefined && specialProperties[imsorryaboutthelagthiswillcause].specialColorFunction) {
								colorOut = specialProperties[imsorryaboutthelagthiswillcause].specialColorFunction(pixel,oldColor=colorOut)
							}
						}
						ctx.fillStyle = colorOut;
					}
					else if (view === 2) { // thermal view
						// set the color to pixel.temp, from hottest at -66 (294.1875) hue to coldest 225 hue, with the minimum being -273, max being 7755
						var temp = pixel.temp;
						temp = Math.min(Math.max(temp,(settings.abszero ?? -273.15)),55530);
						var hue,sat,lig;
						sat = 100;
						lig = 50;
						if(temp <= 7755) {
							hue = 225 - (Math.min(7755,temp)/6000)*225;
							if (hue < 0) {hue += (360 * Math.ceil(hue / -360))}
							if (temp < 0 && hue > 280) {hue = 280}
						} else if(temp <= 9255) {
							hue = 294.1875;
							lig = 50 + (Math.max(0,temp - 7755) * (50/1500));
						} else if(temp <= 11255) {
							hue = 294.1875;
							sat = 0;
							lig = 100 - (Math.max(0,temp - 9255) * (100 / 2000));
						} else if(temp <= 11755) {
							hue = 225;
							lig = (Math.max(0,temp - 11255) * (25 / 500));
						} else if(temp <= 19510) {
							hue = 225 - (Math.min(19510,Math.max(0,temp - 11755))/6000)*225;
							if (hue < 0) {hue += (360 * Math.ceil(hue / -360))}
							lig = 25;
						} else if(temp <= 20510) {
							hue = 294.1875
							//lig = scale(temp,19510,20010,25,75);
							//hue = scale(temp,19510,20010,294.1875,585) % 360;
							sat = scale(temp,19510,20510,100,50);
							lig = scale(temp,19510,20510,25,75);
						} else if(temp <= 28265) {
							hue = scale(temp,20510,28265,294.1875,585) % 360;
							sat = 50;
							lig = 75;
						} else if(temp <= 29265) {
							hue = 225;
							sat = scale(temp,28265,29265,50,40);
							lig = scale(temp,28265,29265,75,87.5);
						} else if(temp <= 37020) {
							hue = scale(temp,29265,37020,225,654.1875) % 360;
							sat = 40;
							lig = 87.5;
						} else if(temp <= 39020) {
							hue = 294.1875;
							sat = 40;
							lig = scale(temp,37020,39020,87.5,50);
						} else if(temp <= 46775) { //46775
							hue = scale(temp,39020,46775,294.1875,585) % 360;
							sat = 40;
							lig = 50;
						} else if(temp <= 47775) {
							hue = 225;
							sat = scale(temp,46775,47775,40,20);
							lig = 50;
						} else { //55530
							hue = scale(temp,47775,55530,225,654.1875) % 360;
							sat = 20;
							lig = 50;
						};
						ctx.fillStyle = "hsl("+hue+","+sat+"%,"+lig+"%)";
					}
					else if (view === 4) { // smooth view, average of surrounding pixels
						// E/N: i'm too scared to do smooth view
						var colorlist = [];
						// check adjacent coords on the pixelMap, add the color to the list if the pixel is not empty and the color indexOf "rgb" is not -1
						for (var j = 0; j < biCoords.length; j++) {
							var x = pixel.x + biCoords[j][0];
							var y = pixel.y + biCoords[j][1];
							if (isEmpty(x,y,true) || elements[pixelMap[x][y].element].state !== elements[pixel.element].state) {continue}
							var color = pixelMap[x][y].color;
							if (color.indexOf("rgb") !== -1) {
								colorlist.push(color.match(/\d+/g));
							}
						}
						if (colorlist.length === 0) {
							ctx.fillStyle = pixel.color;
						}
						else {
							ctx.fillStyle = averageRGB(colorlist);
						}
					}
					else if (view === 5) { // velocity view
						var data = elements[pixel.element];
						
						var vx = pixel.vx ?? 0;
						var vy = pixel.vy ?? 0;
						/*
						var pseudoVelocity = 0;
						var coordsToCheck;
						var behaviorCoordsToCheck;

						if(Array.isArray(data.behavior)) {
							switch((pixel.r ?? 0) % 4) {
								default:
								case 0:
									coordsToCheck = [0,1];
									behaviorCoordsToCheckOffset = [2,1];
									break;
								case 1:
									coordsToCheck = [-1,0];
									behaviorCoordsToCheckOffset = [1,0];
									break;
								case 2:
									coordsToCheck = [0,-1];
									behaviorCoordsToCheckOffset = [0,1];
									break;
								case 3:
									coordsToCheck = [1,0];
									behaviorCoordsToCheckOffset = [1,2];
									break;									
							};
							if(data.behavior[behaviorCoordsToCheckOffset[0]][behaviorCoordsToCheckOffset[1]] == "M1") {
								if(isEmpty(pixel.x+coordsToCheck[0],pixel.y+coordsToCheck[1])) {
									pseudoVelocity = 1;
								} else {
									if(!(isEmpty(pixel.x+behaviorCoordsToCheckOffset[0],pixel.y+behaviorCoordsToCheckOffset[1],true))) {
										newPixel = pixelMap[pixel.x+behaviorCoordsToCheckOffset[0]][pixel.y+behaviorCoordsToCheckOffset[1]];
										newData = elements[newPixel.element];
										if(newData.id !== data.id && typeof(data.density) === "number" && typeof(newData.density) === "number") {
											var chance = (data.density - newData.density)/(data.density + newData.density);
											pseudoVelocity = chance
										}
									}
								}
							};
							if(pseudoVelocity) {
								switch((pixel.r ?? 0) % 4) {
									default:
									case 0:
										vy += pseudoVelocity;
										break;
									case 1:
										vx -= pseudoVelocity;
										break;
									case 2:
										vy -= pseudoVelocity;
										break;
									case 3:
										vx += pseudoVelocity;
										break;
								}
							};
						} else {
							if(data.tick && [behaviors.POWDER,behaviors.LIQUID].includes(data.tick)) {
								pseudoVelocity = 1;
							} else if(data.tick == behaviors.UL_UR_OPTIMIZED) {
								pseudoVelocity = -1;
							} else if(pixel.element == "hail") {
								pseudoVelocity = 2;
							};
							vy += pseudoVelocity;
						};
						*/

						if(vx === 0 && vy === 0) {
							ctx.fillStyle = "rgb(15,15,15)"
						} else {
							var magnitude = Math.sqrt ((vx ** 2) + (vy ** 2));

							var direction = Math.atan2(pixel.vy ?? 0,pixel.vx ?? 0)*180/Math.PI;
							if(direction < 0) { direction = scale(direction,-180,0,360,180) };
							
							hue = direction;
							sat = 100;
							lig = bound(scale(magnitude,0,100,10,100),0,100);

							ctx.fillStyle = "hsl("+hue+","+sat+"%,"+lig+"%)";
						}
					}

					if(find) { //if find and matching, override fill style with the find coloration
						if(findElement instanceof Array ? findElement.includes(pixel.element) : pixel.element === findElement) {
							ctx.fillStyle = "rgb(255," + marasi(findColorPulseTimer / 10) + ",0)";
						}
					};

					var mode = getShapeMode();
					settings.acidFunction ??= "none";
					var acidFunction;
					if([false,"false"].includes(settings.doAcid)) {
						acidFunction = acidFunctions.none
					} else {
						acidFunction = acidFunctions[settings.acidFunction ?? "none"]
					};
					var acidOffset1,acidOffset2;
					if(settings.doAcid && settings.acidFunction != "none" && !!acidFunction) {
						acidOffset1 = (settings.doAcid ?? false) * (18*acidFunction((pixel.y+incrementt)/4.4));
						acidOffset2 = (settings.doAcid ?? false) * (18*acidFunction((pixel.x+incrementt)/4.4))
					} else {
						acidOffset1 = 0;
						acidOffset2 = 0
					};
					if ((view === null || view === 4) && elements[pixel.element].isGas) {
						//gas rendering
						switch(mode) {
							case "circles":
								ctx.globalAlpha = 0.66;
								ctx.beginPath();
								ctx.arc((pixel.x+0.5)*pixelSize+acidOffset1, (pixel.y+0.5)*pixelSize+acidOffset2, pixelSize, 0, 2 * Math.PI, false);
								ctx.fill();
								ctx.globalAlpha = 1;
								break;
							case "triangles":
								ctx.globalAlpha = 0.66;
								ctx.beginPath();
								ctx.moveTo((pixel.x-0.75)*pixelSize+acidOffset1,(pixel.y+1.5)*pixelSize+acidOffset2);
								ctx.lineTo((pixel.x+0.5)*pixelSize+acidOffset1,(pixel.y-1)*pixelSize+acidOffset2);
								ctx.lineTo((pixel.x+1.75)*pixelSize+acidOffset1,(pixel.y+1.5)*pixelSize+acidOffset2);
								ctx.fill();
								ctx.globalAlpha = 1;
								break;
							default:
								ctx.globalAlpha = 0.5;
								ctx.fillRect((pixel.x-1)*pixelSize+acidOffset1, (pixel.y)*pixelSize+acidOffset2, pixelSize*3, pixelSize);
								ctx.fillRect((pixel.x)*pixelSize+acidOffset1, (pixel.y-1)*pixelSize+acidOffset2, pixelSize, pixelSize*3);
								ctx.globalAlpha = 1;
								break;
						};
					}
					else { // draw the pixel (default)
						switch(mode) {
							case "circles":
								ctx.beginPath();
								ctx.arc((pixel.x+0.5)*pixelSize+acidOffset1, (pixel.y+0.5)*pixelSize+acidOffset2, pixelSize/2, 0, 2 * Math.PI, false);
								ctx.fill();
								ctx.globalAlpha = 1;
								break;
							case "triangles":
								ctx.beginPath();
								ctx.moveTo(pixel.x*pixelSize+acidOffset1,(pixel.y+1)*pixelSize+acidOffset2);
								ctx.lineTo((pixel.x+0.5)*pixelSize+acidOffset1,(pixel.y)*pixelSize+acidOffset2);
								ctx.lineTo((pixel.x+1)*pixelSize+acidOffset1,(pixel.y+1)*pixelSize+acidOffset2);
								ctx.fill();
								ctx.globalAlpha = 1;
								break;
							default:
								ctx.fillRect(pixel.x*pixelSize+acidOffset1, pixel.y*pixelSize+acidOffset2, pixelSize, pixelSize);
								break;
						};
					}
					if (pixel.charge && view !== 2) { // Yellow glow on charge
						if (!elements[pixel.element].colorOn) {
							ctx.fillStyle = "rgba(255,255,0,0.5)";
							switch(mode) {
								case "circles":
									ctx.beginPath();
									ctx.arc((pixel.x+0.5)*pixelSize+acidOffset1, (pixel.y+0.5)*pixelSize+acidOffset2, pixelSize/2, 0, 2 * Math.PI, false);
									ctx.fill();
									ctx.globalAlpha = 1;
									break;
								case "triangles":
									ctx.beginPath();
									ctx.moveTo(pixel.x*pixelSize+acidOffset1,(pixel.y+1)*pixelSize+acidOffset2);
									ctx.lineTo((pixel.x+0.5)*pixelSize+acidOffset1,(pixel.y)*pixelSize+acidOffset2);
									ctx.lineTo((pixel.x+1)*pixelSize+acidOffset1,(pixel.y+1)*pixelSize+acidOffset2);
									ctx.fill();
									ctx.globalAlpha = 1;
									break;
								default:
									ctx.fillRect(pixel.x*pixelSize+acidOffset1, pixel.y*pixelSize+acidOffset2, pixelSize, pixelSize);
									break;
							};
						}
					}
					if (pixel.burning && settings.burnOverlay && view !== 2) { // Red glow on burn
						if (!elements[pixel.element].colorOn) {
							ctx.fillStyle = "rgba(255,0,0,0.5)";
							switch(mode) {
								case "circles":
									ctx.beginPath();
									ctx.arc((pixel.x+0.5)*pixelSize+acidOffset1, (pixel.y+0.5)*pixelSize+acidOffset2, pixelSize/2, 0, 2 * Math.PI, false);
									ctx.fill();
									ctx.globalAlpha = 1;
									break;
								case "triangles":
									ctx.beginPath();
									ctx.moveTo(pixel.x*pixelSize+acidOffset1,(pixel.y+1)*pixelSize+acidOffset2);
									ctx.lineTo((pixel.x+0.5)*pixelSize+acidOffset1,(pixel.y)*pixelSize+acidOffset2);
									ctx.lineTo((pixel.x+1)*pixelSize+acidOffset1,(pixel.y+1)*pixelSize+acidOffset2);
									ctx.fill();
									ctx.globalAlpha = 1;
									break;
								default:
									ctx.fillRect(pixel.x*pixelSize+acidOffset1, pixel.y*pixelSize+acidOffset2, pixelSize, pixelSize);
									break;
							};
						}
					}
				};

				if (ctx.globalAlpha < 1) {
					ctx.globalAlpha = 1;
				};

				if (elements[currentElement].maxSize < mouseSize) {
					var mouseOffset = Math.trunc(elements[currentElement].maxSize/2);
				}
				else {
					var mouseOffset = Math.trunc(mouseSize/2);
				}
				var topLeft = [mousePos.x-mouseOffset,mousePos.y-mouseOffset];
				var bottomRight = [mousePos.x+mouseOffset,mousePos.y+mouseOffset];
				if(mouseSize % 2 == 0) {
					bottomRight[0]--;
					bottomRight[1]--;
				};
				// Draw a square around the mouse
				ctx.strokeStyle = "white";
				ctx.strokeRect(topLeft[0]*pixelSize,topLeft[1]*pixelSize,(bottomRight[0]-topLeft[0]+1)*pixelSize,(bottomRight[1]-topLeft[1]+1)*pixelSize);
				// draw one transparent pixel in the center
				if (settings.precision) {
					ctx.fillStyle = "rgba(255,255,255,0.5)";
					ctx.fillRect(mousePos.x*pixelSize,mousePos.y*pixelSize,pixelSize,pixelSize);
				}
				if ((!paused) || forceTick) {pixelTicks++};

				findColorPulseTimerSubTimer++;
				if(findColorPulseTimerSubTimer >= 2) {
					findColorPulseTimer++;
					findColorPulseTimerSubTimer = 0;
				};
			}
		});
		//I hate overwriting drawPixels

        viewKey = {
            2: "thermal",
            3: "basic",
            4: "smooth",
            5: "velocity"
        };

        function setView(n) {
            if (viewKey[n]) { // range of number keys with valid views
                view = n;
            }
            else { // reset view
                view = null;
            }
        };

		runAfterLoad(function() {
			//Setting
			var settingsMenu = document.getElementById("settingsMenu").getElementsByClassName("menuText")[0];

			var settingNodes = [...settingsMenu.childNodes].filter(function(node) { return node.nodeType == 1 });
			var lastSetting = settingNodes[settingNodes.length - 1];
			//console.log(lastSetting);
			//console.log(lastSetting.getAttribute("style"));
			lastSetting.removeAttribute("style"); //restore padding for worldgen setting;
			//console.log(lastSetting.getAttribute("style"));

			//Shape setting
			var shapeSettingSpan = document.createElement("span");
			shapeSettingSpan.setAttribute("setting","shapeMode");
			shapeSettingSpan.setAttribute("class","setting-span");
			shapeSettingSpan.textContent = "Pixel Shape (laggy) ";
				var settingDropdown = document.createElement("select");
				settingDropdown.setAttribute("onchange","settings.shapeMode = parseInt(this.value); saveSettings();");
				var options = {
					0: "Squares",
					1: "Circles",
					2: "Triangles"
				};
				for(value in options) {
					var newOption = document.createElement("option");
					if(value == "0") {
						newOption.setAttribute("selected","");		
					};
					newOption.setAttribute("value",value);
					newOption.innerText = options[value];
					settingDropdown.appendChild(newOption);
				};
				shapeSettingSpan.appendChild(settingDropdown);
			settingsMenu.appendChild(shapeSettingSpan);

			//Acid setting
			var acidSettingSpan = document.createElement("span");
			acidSettingSpan.setAttribute("setting","doAcid");
			acidSettingSpan.setAttribute("class","setting-span");
			acidSettingSpan.textContent = "\"Acid\" distortion ";
				var settingDropdown = document.createElement("select");
				settingDropdown.setAttribute("onchange","settings.doAcid = (this.value === 'true'); saveSettings();");
				var options = {
					"false": "Disabled",
					"true": "Enabled"
				};
				for(value in options) {
					var newOption = document.createElement("option");
					if(value == "0") {
						newOption.setAttribute("selected","");		
					};
					newOption.setAttribute("value",value);
					newOption.innerText = options[value];
					settingDropdown.appendChild(newOption);
				};
				acidSettingSpan.appendChild(settingDropdown);
			settingsMenu.appendChild(acidSettingSpan);

			//Acid function setting
			var acidFuncSpan = document.createElement("span");
			acidFuncSpan.setAttribute("setting","acidFunction");
			acidFuncSpan.setAttribute("class","setting-span");
			acidFuncSpan.textContent = "\"Acid\" distortion function";
				var settingDropdown = document.createElement("select");
				settingDropdown.setAttribute("onchange","settings.acidFunction = this.value; console.log(settings.acidFunction,this.value); saveSettings();");
				var options = {
					"none": "None",
					"sin": "Sine",
					"tan": "Tangent"
				};
				for(value in options) {
					var newOption = document.createElement("option");
					if(value == "0") {
						console.log("selected",value);
						newOption.setAttribute("selected","")
					};
					newOption.setAttribute("value",value);
					newOption.innerText = options[value];
					settingDropdown.appendChild(newOption);
				};
				acidFuncSpan.appendChild(settingDropdown);
			settingsMenu.appendChild(acidFuncSpan);

			//Handle the styling of the last setting
			settingNodes = [...settingsMenu.childNodes].filter(function(node) { return node.nodeType == 1 });
			lastSetting = settingNodes[settingNodes.length - 1];
			//console.log(lastSetting);
			lastSetting.setAttribute("style","padding-bottom:0"); //remove padding from last setting;

			var redBurnSettingSpan = document.createElement("span");
			redBurnSettingSpan.setAttribute("setting","burnOverlay");
			redBurnSettingSpan.setAttribute("class","setting-span");
			redBurnSettingSpan.textContent = "Red overlay on burning pixels ";
				var settingDropdown = document.createElement("select");
				settingDropdown.setAttribute("onchange","settings.burnOverlay = (this.value === 'true'); saveSettings();");
				var options = {
					"false": "Disabled",
					"true": "Enabled"
				};
				for(value in options) {
					var newOption = document.createElement("option");
					if(value == "0") {
						newOption.setAttribute("selected","");		
					};
					newOption.setAttribute("value",value);
					newOption.innerText = options[value];
					settingDropdown.appendChild(newOption);
				};
				redBurnSettingSpan.appendChild(settingDropdown);
			settingsMenu.appendChild(redBurnSettingSpan);

			console.log(everyTick(function() {
				if(paused) { return };
				for(var propName in specialProperties) {
					//thanks, I hate not being able to pass arguments to filter functions
					var _filterFunction = function(pixel) {
						return pixel.hasOwnProperty(propName)
					};
					var pixelsWithProp = currentPixels.filter(_filterFunction);
					//console.log(pixelsWithProp.map(p => [p.x,p.y,propName,p[propName]].join(",")).join("\n"));
					for(var i in pixelsWithProp) {
						var propPixel = pixelsWithProp[i];
						if(!propPixel || propPixel.del) { continue };
						specialProperties[propName]?.specialFunction?.(propPixel);
					};
				}
			}),"Property handler tick callback set")
		});

	//FUNCTION EXECUTION WHEN A PIXEL TRIES TO MOVE INTO ANOTHER (onTryMoveInto) ##

		elements.on_try_move_into_test = {
			color: "#ffffff",
			properties: {
				ticks: 0,
				attemptedMovesIntoPixel: 0
			},	
			behavior: behaviors.POWDER,
			reactions: {
				"dirt": { elem1: "diamond" }
			},
			state: "solid",
			hidden: true,
			excludeRandom: true,
			category: "special",
			density: 1000,
			tick: function(pixel) {
				pixel.ticks++;
			},
			onTryMoveInto: function(pixel,otherPixel) {
				pixel.attemptedMovesIntoPixel++;
				var otherElement = otherPixel.element;
				if(otherElement === "ash") {
				console.log(`This is a test of potentially undesired multiplicate running. (tick: ${pixelTicks}, move attempts: ${pixel.attemptedMovesIntoPixel})`);
					//if(deletePixel(pixel.x,pixel.y)) {
					//	console.log("This pixel has been deleted.");
					//};
				};
			},
			desc: "Try burying this pixel and see what happens. (Use Debug)\n\nonTryMoveInto is run as part of tryMove, <em>before reactions</em>, while tick functions are run as part of pixelDraw.\n<span style='color:red;'>In some circumstances, such as a pixel being buried under a pile of anything that isn't a sturdy powder, this function may run multiple times per tick.</span> For example, bury this pixel in ash and look in the console.\n\nTo use this function, include in your element definition the \"onTryMoveInto\" key with a function value, similarly to tick functions. This function takes two arguments; \"otherPixel\" is the pixel that is trying to move and \"pixel\" is the pixel whose position otherPixel is trying to move into.",
			related: ["debug", "ash"],
		}

		function tryMove(pixel,nx,ny,leaveBehind,force) {
			if(!pixel) { return false };
			if (pixel.drag && !force) { return true; }
			var info = elements[pixel.element];
			var oob = outOfBounds(nx,ny);
			if (isEmpty(nx,ny,false,oob)) { // If coords is empty, move to coords
				//console.log(`Moving ${pixel.element} (${pixel.x},${pixel.y}) to (${nx},${ny})`);
				movePixel(pixel,nx,ny,leaveBehind);
				return true;
			}
			else if (!oob) {
				//console.log(`Moving ${pixel.element} (${pixel.x},${pixel.y}) to (${nx},${ny})`);
				// Reactions
				newPixel = pixelMap[nx][ny];
				var newInfo = elements[newPixel.element];
				if(typeof(newInfo) == "undefined") { return false };
				var returnVal = false;
				if(newInfo.onTryMoveInto !== undefined) {
					newInfo.onTryMoveInto(newPixel,pixel);
					if(!pixel || pixel.del) {
						return "deleted";
					};
					returnVal = false;
				};
				var rr1 = false;
				if (info.reactions !== undefined && info.reactions[newPixel.element] !== undefined) {
					rr1 = reactPixels(pixel,newPixel)
					if (rr1) {
						return true;
					}
				}
				if (!rr1 && elements[newPixel.element].reactions !== undefined && elements[newPixel.element].reactions[pixel.element] !== undefined && !elements[newPixel.element].reactions[pixel.element].oneway) {
					if (reactPixels(newPixel,pixel)) {
						return true;
					}
				}
				// Density
				if (elements[pixel.element].id !== elements[newPixel.element].id) {
					if (info.density !== undefined && elements[newPixel.element].density !== undefined) {
						// if the pixel's state + ">" + newPixel's state is in validDensitySwaps, and the pixel's density is larger than the newPixel's density, swap the pixels
						if (validDensitySwaps[info.state][elements[newPixel.element].state] && info.density >= elements[newPixel.element].density) {
							// chance depending on the difference in density
							if (Math.random() < (info.density - elements[newPixel.element].density)/(info.density + elements[newPixel.element].density)) {
								swapPixels(pixel,newPixel);
								return true;
							}
						}
					}
				}
				if(returnVal) {
					return true;
				}
			}
			return false;
		}

	//PIXEL MOVER TOOLS ##

		elements.move_up = {
			color: "#1C0000",
			tool: function(pixel) {
				tryMove(pixel,pixel.x,pixel.y-1);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_down = {
			color: "#000038",
			tool: function(pixel) {
				tryMove(pixel,pixel.x,pixel.y+1);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_left = {
			color: "#007000",
			tool: function(pixel) {
				tryMove(pixel,pixel.x-1,pixel.y);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_right = {
			color: "#000E00",
			tool: function(pixel) {
				tryMove(pixel,pixel.x+1,pixel.y);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_up_left = {
			color: "#E00000",
			tool: function(pixel) {
				tryMove(pixel,pixel.x-1,pixel.y-1);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_down_left = {
			color: "#0001C0",
			tool: function(pixel) {
				tryMove(pixel,pixel.x-1,pixel.y+1);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_up_right = {
			color: "#038000",
			tool: function(pixel) {
				tryMove(pixel,pixel.x+1,pixel.y-1);
			},
			category: "movement tools",
			excludeRandom: true,
		},

		elements.move_down_right = {
			color: "#000007",
			tool: function(pixel) {
				tryMove(pixel,pixel.x+1,pixel.y+1);
			},
			category: "movement tools",
			excludeRandom: true,
		}

	//TOOL THAT DELETES EVERY ELEMENT OF THE CLICKED TYPE(S) ##

		elements.delete_all_of_element = {
			name: "delete all of element",
			color: ["#a7a7a7", "#a7a7a7", "#a7a7a7", "#a7a7a7", "#000000", "#000000", "#000000", "#000000"],
			tool: function(pixel) {
				for (var i = 1; i < width; i++) {
					for (var j = 1; j < height; j++) {
						if (!isEmpty(i,j)) {
							if(pixelMap[i][j].element == pixel.element) {
								deletePixel(i,j)
							}
						}
					}
				}
			},
			category: "tools",
			excludeRandom: true,
		};

	//TEMPERATURE TOOLS ##

		//base syntax by sightnado
		/*elements.warm = {
			color: "#7fff7f",
			tool: function(pixel) {
				pixel.temp = 20;
				pixelTempCheck(pixel)
			},
			category: "tools",
		};*/
		//warm is redundant due to vanilla room_temp
		elements.room_temp.category = "tools";
		elements.slow_cool = {
			color: elements.cook.color.map(colorCode => Object.values(convertColorFormats(colorCode,"json"))).map(x => RGBToHex(x.map(y => 255 - y))),
			tool: function(pixel) {
					pixel.temp -= (0.5 * (1 + shiftDown));
			},
			category: "energy",
			excludeRandom: true
		};
		elements.ultraheat = {
			color: ["#ff0000", "#ffbf4f", "#ff0000", "#ffbf4f", "#ff0000", "#ffbf4f"],
			tool: function(pixel) {
				if(shiftDown) { pixel.temp += (350 * (1 + shiftDown)) } else { pixel.temp += 350 }
				pixelTempCheck(pixel)
			},
			category: "tools",
		};
		elements.ultracool = {
			color: ["#0000ff", "#4fbfff", "#0000ff", "#4fbfff", "#0000ff", "#4fbfff"],
			tool: function(pixel) {
				if(shiftDown) { pixel.temp -= (350 * (1 + shiftDown)) } else { pixel.temp -= 350 }
				pixelTempCheck(pixel)
			},
			category: "tools",
		};
		elements.nan_temp = {
			name: "NaN temp",
			color: ["#000000", "#ff00ff", "#000000", "#ff00ff"],
			tool: function(pixel) {
				pixel.temp = NaN;
				pixelTempCheck(pixel)
			},
			category: "cursed tools",
		};

		elements.inf_temp = {
			color: ["#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff", "#ff0000", "#ffffff"],
			tool: function(pixel) {
				pixel.temp = Infinity;
				pixelTempCheck(pixel)
			},
			category: "cursed tools",
		};

	//OTHER TOOLS ##

		elements.burn = {
			color: ["#FF6B21", "#FFA600", "#FF4000"],
			tool: function(pixel) {
				pixel.burnStart = pixelTicks;
				pixel.burning = true;
			},
			category: "tools",
			excludeRandom: true,
		};

		elements.cursed_shock = {
			color: ["#ffff00", "#00ff00", "#ffff00", "#00ff00", "#ffff00", "#00ff00", "#ffff00", "#00ff00"],
			tool: function(pixel) {
				if(pixel.chargeCD) {
					delete pixel.chargeCD;
				};
				if (!pixel.charge) {
					pixel.charge = 1;
					if (elements[pixel.element].colorOn) {
						pixel.color = pixelColorPick(pixel);
					};
				};
			},
			category: "cursed tools",
			excludeRandom: true,
		};

	//TROLL PIXELS ##

		elements.troll_1 = {
			color: "#eeeeee",
			tick: function(pixel) {
				var target = randomChoice(currentPixels);
				if(target && !(isEmpty(target.x,target.y))) {
					deletePixel(target.x,target.y)
				}
			},
			category: "troll machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
			desc: "Deletes random pixels"
		},

		elements.troll_2 = {
			color: "#eeeeee",
			tick: function(pixel) {
				var target = randomChoice(currentPixels);
				target.color = "rgb(0,0,0)"
			},
			category: "troll machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
			desc: "Paints random pixels black"
		},

		elements.troll_3 = {
			color: "#eeeeee",
			tick: function(pixel) {
				var target = randomChoice(currentPixels);
				if(target && !(isEmpty(target.x,target.y))) {
					swapPixels(pixel,target)
				}
			},
			category: "troll machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
			desc: "Swaps with random pixels"
		},

		elements.troll_4 = {
			color: "#eeeeee",
			tick: function(pixel) {
				for (var i = 1; i < width; i++) {
					for (var j = 1; j < height; j++) {
						var eeaa = (Math.floor(Math.random()*5))-2
						if(Math.random() < 0.00007) { explodeAt(i,j,9+eeaa) }
						if(j == height) { j == 1 }
					}
					if(i == height) { i == 1 }
				}
			},
			category: "troll machines",
			insulate: true,
			hardness: 1.0,
			state: "solid",
			excludeRandom: true,
			desc: "Causes random explosions"
		},

		elements.offset_fourth_y = {
			color: ["#000000", "#ff00ff", "#000000", "#ff00ff", "#000000", "#ff00ff", "#000000", "#ff00ff"],
			tool: function(pixel) {
				tryMove(pixel,pixel.x,pixel.y+0.25);
				pixelTempCheck(pixel)
			},
			category: "cursed tools",
		},

		elements.offset_half_y = {
			color: ["#000000", "#ff00ff", "#000000", "#ff00ff", "#000000", "#ff00ff", "#000000", "#ff00ff"],
			tool: function(pixel) {
				tryMove(pixel,pixel.x,pixel.y+0.5);
				pixelTempCheck(pixel)
			},
			category: "cursed tools",
		},

		elements.offset_three_fourth_y = {
			color: ["#000000", "#ff00ff", "#000000", "#ff00ff", "#000000", "#ff00ff", "#000000", "#ff00ff"],
			tool: function(pixel) {
				tryMove(pixel,pixel.x,pixel.y+0.75);
				pixelTempCheck(pixel)
			},
			category: "cursed tools",
		},

		elements.troll_5 = {
			color: "#eeeeee",
			tick: function() {
				var target = randomChoice(currentPixels);
				if(target && !(isEmpty(target.x,target.y))) {
					target.r ??= 0;
					target.r += 1; target.r %= 4;
				}
			},
			category: "troll machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
			desc: "Randomly rotates pixels"
		}

		elements.troll_6 = {
			color: "#eeeeee",
			tick: function() {
				if(pixel.temp < -273) {
					pixel.temp = -273;
				};
				if(isNaN(pixel.temp)) {
					pixel.temp = -1;
				};
				pixel.bemp = Math.floor(pixel.temp);
				if(pixel.bemp > 273) {
					pixel.bemp = 273;
				};
				if(pixel.temp >= 4000) {
					pixelTicks = -1;
					pixel.temp = 4000;
				} else {
					pixelTicks += pixel.bemp;
				};
			},
			category: "troll machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
			temp: -1,
			desc: "Advances the simulation timer (not time travel) by its temperature"
		},

		elements.troll_7 = {
			color: "#eeeeee",
			tick: function(pixel) {
				var target = randomChoice(currentPixels);
				if(target && !(isEmpty(target.x,target.y))) {
					target.temp += (Math.floor(Math.random() * 500 + 1) - 250)
				}
			},
			category: "troll machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
			desc: "Randomly heats and cools random pixels"
		}

	//TRIGGERABLE RANDOM-TOOL POWDERS ##

		if(typeof(randomChoices) == "undefined") {randomChoices = []}; //this is generated after mod load, but JS will probably die if I don't have it defined in the code

		elements.heat_random = {
			name: "heat-randomized powder",
			color: ["#4e5f8a","#b334ec","#fa96f9","#b6ecf6","#80ebc8","#e9286b","#8eed91","#b18b30"], //"random"'s colors plus 0x100000
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(pixel.temp >= 50) {
					changePixel(pixel,randomChoice(randomChoices),true);
				};
			},
			state: "solid",
			category: "special",
			density: 1987, //average density of all vanilla elements: 1987.2842952763815
			temp: 20,
			desc: "Turns to a random element when heated to 50&deg;C",
		};

		elements.cold_random = {
			name: "cold-randomized powder",
			color: ["#3e5f9a","#a334fc","#e490ff","#9de3ff","#70ebd8","#d9287b","#7eeda1","#a18b40"], //"random"'s colors plus 0x000010 except where the last byte was above 0xef, where substraction was done to the first two bytes to compensate
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(pixel.temp <= -50) {
					changePixel(pixel,randomChoice(randomChoices),true);
				};
			},
			state: "solid",
			density: 1987, //average density of all vanilla elements: 1987.2842952763815
			temp: 20,
			category: "special",
			desc: "Turns to a random element when cooled to -50&deg;C",
		};

		elements.shock_random = {
			name: "shock-randomized powder",
			color: ["#4e6f8a","#b344ec","#faa6f9","#b6fcf6","#80fbc8","#e9386b","#8efd91","#b19b30"], //"random"'s colors plus 0x101000
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(pixel.charge) {
					delete pixel.charge;
					pixel.chargeCD = 1;
					changePixel(pixel,randomChoice(randomChoices),true);
				};
			},
			conduct: 1,
			state: "solid",
			density: 1987, //average density of all vanilla elements: 1987.2842952763815
			temp: 20,
			category: "special",
			desc: "Turns to a random element when shocked",
		};

	//ALKAHEST ##

		alkahestBlacklist = ["alkahest","alkahest_fairy","wall","alkahest_spout"]

		elements.alkahest = {
			color: "#33eeee",
			behavior: behaviors.LIQUID_OLD,
			state: "liquid",
			category: "liquids",
			density: 3308,
			hardness: 1,
			tick: function(pixel) {
				for(i = 0; i < adjacentCoords.length; i++) {
					if(Math.random() < 0.1) {
						var pX = pixel.x
						var pY = pixel.y
						var oX = adjacentCoords[i][0];
						var oY = adjacentCoords[i][1];
						var checkPosX = pX+oX;
						var checkPosY = pY+oY;
						if(!isEmpty(checkPosX,checkPosY,true)) {
							var newPixel = pixelMap[checkPosX][checkPosY];
							var newElement = newPixel.element;
							if(!alkahestBlacklist.includes(newElement)) { //unless someone's willing to implement dragon parts
								if(typeof(pixel[newElement]) === "undefined") {
									pixel[newElement] = 0;
								};
								pixel[newElement]++;
								deletePixel(checkPosX,checkPosY);
							};
						};
					};
				};
			},
		};

	//LIQUID ENERGY ELEMENTS ##

		elements.liquid_plasma = {
			color: ["#8800ff","#b184d9","#8800ff"],
			behavior: [
				"XX|XX|XX",
				"M2|DL%0.1|M2",
				"M1|M1|M1",
			],
			behaviorOn: [
				"XX|CL%5|XX",
				"CL%5 AND M2|XX|CL%5 AND M2",
				"M1|M1 AND CL%5|M1",
			],
			temp:7065,
			tempLow:5000,
			stateLow: "liquid_fire",
			category: "energy liquids",
			state: "liquid",
			density: 70,
			charge: 0.5,
			conduct: 1,
		},

		elements.liquid_fire = {
			color: ["#ff6b21","#ffa600","#ff4000"],
			behavior: [
				"XX|M2|XX",
				"M2|XX|M2",
				"M1|M1|M1",
			],
			reactions: {
				"water": { "elem1": "liquid_smoke" }
			},
			temp:600,
			tempLow:100,
			stateLow: "liquid_smoke",
			tempHigh: 7000,
			stateHigh: "liquid_plasma",
			category: "energy liquids",
			burning: true,
			burnTime: 500,
			burnInto: "liquid_smoke",
			state: "liquid",
			density: 21,
		},

		elements.liquid_smoke = {
			color: "#383838",
			behavior: [
				"XX|XX|XX",
				"M2|DL%0.1|M2",
				"M1|M1|M1",
			],
			reactions: {
				"water": { "elem1": "dirty_water", "elem2": null },
				"steam": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] },
				"rain_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] },
				"snow_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] },
				"acid_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,15] },
				"fire_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,15] },
				"pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,15] }
			},
			temp: 114,
			tempHigh: 605,
			stateHigh: "liquid_fire",
			category: "energy liquids",
			state: "liquid",
			density: 2180,
		},

		elements.liquid_cold_fire = {
			color: ["#21cbff","#006aff","#00ffff"],
			behavior: [
				"XX|M2|XX",
				"M2|CH:liquid_smoke%0.1|M2",
				"M1|M1|M1",
			],
			reactions: {
				"fire": { "elem1": "liquid_smoke", "elem2": "liquid_smoke" },
				"plasma": { "elem1": "le_liquid_light", "elem2": "le_liquid_light" }, //prefixed to avoid conflict with F&M liquid_light
			},
			temp:-200,
			tempHigh:0,
			stateHigh: "liquid_smoke",
			category: "energy liquids",
			state: "liquid",
			density: 42,
		},

		elements.le_liquid_light = {
			color: "#ffffa8",
			behavior: [
				"XX|XX|XX",
				"M2|DL%0.1 AND RT%0.5|M2 AND BO",
				"M1|M1|M1",
			],
			temp: 40,
			category: "energy liquids",
		},

		elements.liquid_laser = {
			color: "#ff0000",
			behavior: [
				"XX|M2|XX",
				"M2|DL%0.05 AND RT%0.5|M2 AND BO:1,2,3",
				"XX|M1|XX",
			],
			temp: 40,
			category: "energy liquids",
		},

		elements.liquid_electric = {
			color: "#dddd00",
			behavior: [
				"CL%3|CL%3 AND SH|CL%3",
				"M2%15 AND CL%3 AND SH|SH%3 AND DL%15|M2%15 AND CL%3 AND SH",
				"M1%15 AND CL%4|M1%50 AND CL%9 AND SH|M1%15 AND CL%4",
			],
			charge: 3,
			category: "energy liquids",
			state: "solid",
			density: 44.1,
		},

		elements.liquid_radiation = {
			color: ["#00ff00","#6fff00"],
			behavior: [
				"XX|M2%50 AND HT|XX",
				"M2%50 AND HT|DL%0.2|M2%50 AND HT",
				"M1%50|M1 AND HT|M1%50",
			],
			reactions: {
				"water": { "elem2":"rad_steam", "chance":0.8 },
				"steam": { "elem2":"rad_steam", "chance":0.8 },
				"salt_water": { "elem2":"rad_steam", "chance":0.8 },
				"sugar_water": { "elem2":"rad_steam", "chance":0.8 },
				"dirty_water": { "elem2":"rad_steam", "chance":0.8 },
				"bubble": { "elem2":"rad_steam", "chance":0.8 },
				"foam": { "elem2":"rad_steam", "chance":0.8 },
				"ice": { "elem2":"rad_steam", "chance":0.8 },
				"snow": { "elem2":"rad_steam", "chance":0.8 },
				"packed_snow": { "elem2":"rad_steam", "chance":0.8 },
				"slime": { "elem2":"rad_steam", "chance":0.8 },
				"milk": { "elem2":"cheese", "chance":0.8 },
				"permafrost": { "elem1":"rad_steam", "elem2":"dirt", "chance":0.8 },
				"mud": { "elem1":"rad_steam", "elem2":"dirt", "chance":0.8 },
				"wet_sand": { "elem1":"rad_steam", "elem2":"sand", "chance":0.8 },
				"clay": { "elem1":"rad_steam", "elem2":"clay_soil", "chance":0.8 },
				"slaked_lime": { "elem1":"rad_steam", "elem2":"limestone", "chance":0.8 },
				"rain_cloud": { "elem2":"rad_cloud", "chance":0.8 },
				"snow_cloud": { "elem2":"rad_cloud", "chance":0.8 },
				"plant": { "elem2":"straw", "chance":0.8 },
				"grass": { "elem2":["straw","grass_seed","wheat_seed"], "chance":0.8 },
				"algae": { "elem2":["mushroom_spore","lichen","yeast"], "chance":0.8 },
				"mushroom_spore": { "elem2":["lichen","yeast"], "chance":0.8 },
				"mushroom_cap": { "elem2":["lichen","plant"], "chance":0.8 },
				"mushroom_stalk": { "elem2":["lichen","yeast"], "chance":0.8 },
				"mushroom_gill": { "elem2":["lichen","yeast"], "chance":0.8 },
				"flea": { "elem2":["ash","ant","termite"], "chance":0.8 },
				"ant": { "elem2":["ash","flea","termite"], "chance":0.8 },
				"termite": { "elem2":["ash","flea","ant"], "chance":0.8 },
				"fly": { "elem2":["ash","firefly","bee"], "chance":0.8 },
				"bee": { "elem2":["ash","firefly","fly"], "chance":0.8 },
				"firefly": { "elem2":["ash","bee","fly"], "chance":0.8 },
				"frog": { "elem2":["ash","meat","rotten_meat","cooked_meat"], "chance":0.8 },
				"fish": { "elem2":["ash","meat","rotten_meat","cooked_meat"], "chance":0.8 },
				"rat": { "elem2":["ash","meat","rotten_meat","cooked_meat","plague"], "chance":0.8 },
				"bone": { "elem2":["calcium","calcium","calcium","cancer"], "chance":0.8 },
				"meat": { "elem2":["ash","rotten_meat","cooked_meat"], "chance":0.8 },
				"rotten_meat": { "elem2":["ash","meat","cooked_meat"], "chance":0.8 },
				"cooked_meat": { "elem2":["ash","rotten_meat"], "chance":0.8 },
				"bamboo": { "elem2":["wood","plant","bamboo_plant"], "chance":0.8 },
				"bamboo_plant": { "elem2":["wood","plant","bamboo"], "chance":0.8 },
				"sapling": { "elem2":["wood","plant","tree_branch"], "chance":0.8 },
				"tree_branch": { "elem2":["wood","plant","sapling"], "chance":0.8 },
				"grass_seed": { "elem2":["straw","wheat_seed"], "chance":0.8 },
				"lichen": { "elem2":"algae", "chance":0.8 },
				"yeast": { "elem2":["algae","mushroom_spore","lichen"], "chance":0.8 },
				"wheat_seed": { "elem2":["straw","wheat","grass_seed"], "chance":0.8 },
				"flower_seed": { "elem2":["straw","grass","pistil","petal"], "chance":0.8 },
				"pistil": { "elem2":["straw","grass","flower_seed","petal"], "chance":0.8 },
				"petal": { "elem2":["straw","grass","flower_seed","pistil"], "chance":0.8 },
				"vine": { "elem1":["vine"], "chance":0.8 },
				"worm": { "elem2":"ash", "chance":0.8 },
				"corn": { "elem2":"corn_seed", "chance":0.8 },
				"corn_seed": { "elem2":"corn", "chance":0.8 },
				"potato": { "elem2":"potato_seed", "chance":0.8 },
				"potato_seed": { "elem2":"potato", "chance":0.8 },
				"slug": { "elem2":"slime", "chance":0.8 },
				"snail": { "elem2":"slime", "chance":0.8 },
				"cell": { "elem2":"cancer", "chance":0.8 },
				"blood": { "elem2":["infection","cancer"], "chance":0.8 },
				"antibody": { "elem2":"cancer", "chance":0.8 },
				"infection": { "elem2":"cancer", "chance":0.8 },
				"cancer": { "elem2":null, "chance":0.2 },

			},
			state: "liquid",
			density: 4.2,
			category: "energy liquids",
		},

		elements.liquid_explosion = {
			color: ["#ffb48f","#ffd991","#ffad91"],
			behavior: [
				"XX|XX|XX",
				"M2|EX:10>fire,fire,fire,liquid_explosion,liquid_explosion%0.4 AND DL%0.3|M2",
				"M1|M1|M1",
			],
			temp: 300,
			category: "energy liquids",
			state: "liquid",
			density: 2000,
			excludeRandom: true,
		}

		elements.everfire_liquid = {
			"name": "everfire liquid",
			"color": "#06142b",
			"state": "liquid",
			"behavior": behaviors.LIQUID,
			"density": 1290,
			"burn": 100,
			"burnTime": 2000,
			"burnInto": "extinguished_everfire_liquid",
			"category": "energy liquids",
			"fireColor": ["#0041a8","#8ab7ff"],
		},

		elements.extinguished_everfire_liquid = {
			"name": "extinguished everfire liquid",
			"color": "#242d3b",
			"state": "liquid",
			"behavior": behaviors.LIQUID,
			"density": 1290,
			"fireColor": ["#0041a8","#8ab7ff"],
			"category": "energy liquids",
			"hidden": true,
		},

		elements.liquid_magic = {
			"name": "liquid magic",
			"color": ["#a270ff","#f2d9ff"],
			"state": "liquid",
			"behavior": [
				"M2%50|M2%50|M2%50",
				"M2|DL%0.2|M2",
				"M1|M1|M1",
			],
			"density": 21,
			"category": "energy liquids",
			"reactions": elements.magic.reactions,
		},

		elements.liquid_mystic_fire = {
			"name": "liquid mystic fire",
			"color": ["#5454ff","#2020d4","#5800c4"],
			"behavior": [
				"M2%50|M2%50 AND CR:liquid_mystic_fire%10 AND CR:mystic_fire%5|M2%50",
				"M2 AND CR:liquid_mystic_fire%5|EX:15>liquid_mystic_fire%0.1|M2 AND CR:liquid_mystic_fire%5",
				"M1|M1|M1",
			],
			"temp":8500,
			"tempChange":-100,
			"tempLow":8000,
			"stateLow": "liquid_fire",
			"category": "energy liquids",
			"burning": true,
		},

		//concoction and essence are already liquid

		elements.liquid_frostbomb = {
			color: "#72dfed",
			behavior: [
				"XX|XX|XX",
				"M2|EX:15>frostwind,frostwind,frostwind,liquid_frostbomb%0.4 AND DL%0.2|M2",
				"M1|M1|M1",
			],
			temp: 300,
			category: "energy liquids",
			state: "liquid",
			density: 2000,
			excludeRandom: true,
		}

	//LIQUID VOID ##

		elements.liquid_void = {
			color: "#262626",
			behavior: [
				"XX|DL|XX",
				"DL AND M2|XX|DL AND M2",
				"M1|DL AND M1|M1",
			],
			ignore: ["liquid_void", "void", "wall", "cloner", "ecloner", "slow_cloner", "clone_powder", "floating_cloner", "clone_liquid", "liquid_cloner", "fire_cloner", "antigravity_powder_cloner", "floating_cloner_spout", "clone_liquid_spout", "liquid_cloner_spout", "fire_cloner_spout", "converter", "liquid_void_spout"],
			/*The hardcoded array of cloners is used because I don't know how to detect them.
			Generation code: 
				elementArray = Object.keys(elements);
				for (let i = 0; i < elementArray.length; i++) {
					var elementName = elementArray[i];
					if(elementName.indexOf("lone") !== -1) {
						console.log(elementName);
					};
				};
			*/
			category: "liquids",
			state: "liquid",
			density: 6969,
			excludeRandom: true,
		}

		if(!elements.void.ignore) {
			elements.void.ignore = [];
		};

		elements.void.ignore.push("liquid_void");

	//LIQUID CLONER ##

		elements.clone_liquid = {
			color: "#f0f000",
			behavior: [
				"XX|CF|XX",
				"CF AND M2|XX|CF AND M2",
				"M1|CF AND M1|M1",
			],
			ignore: ["cloner","ecloner","slow_cloner","floating_cloner","clone_powder","clone_liquid_spout"],
			category:"machines",
			insulate:true,
			state:"gas",
			density:2710,
			hardness: 1,
		},

		elements.floating_cloner.state = "gas";

		elements.floating_cloner.ignore.push("floating_cloner_spout");

	//ASSORTED RANDOM THINGS ##

		//TPT reference
		elements.warp = {
			name: "warp",
			color: "#111111",
			behavior: [
				"M1%30 AND SW%30|M1%30 AND SW%30|M1%30 AND SW%30",
				"M1%30 AND SW%30|DL%1|M1%30 AND SW%30",
				"M1%30 AND SW%30|M1%30 AND SW%30|M1%30 AND SW%30",
			],
			category: "special",
			state: "gases",
		},

		elements.unrealistically_flammable_gas = {
			color: "#ddee11",
			behavior: [
				"M1%05 AND SW%2 AND HT:1%1|M1%05 AND SW%2 AND HT:1%1|M1%05 AND SW%2 AND HT:1%1",
				"M1%10 AND SW%2 AND HT:1%1|HT:1%1.000000000000000000|M1%10 AND SW%2 AND HT:1%1",
				"M1%15 AND SW%2 AND HT:1%1|M1%15 AND SW%2 AND HT:1%1|M1%15 AND SW%2 AND HT:1%1",
			],
			behaviorOn: [
				"M1%10 AND SW%4 AND HT:2%2|M1%10 AND SW%4 AND HT:2%2|M1%10 AND SW%4 AND HT:2%2",
				"M1%20 AND SW%4 AND HT:2%2|HT:2%2 AND CH:plasma%0.01|M1%20 AND SW%4 AND HT:2%2",
				"M1%30 AND SW%4 AND HT:2%2|M1%30 AND SW%4 AND HT:2%2|M1%30 AND SW%4 AND HT:2%2",
			],
			category: "gases",
			burn: 3000,
			burnTime: 5,
			burnInto: "burning_unrealistically_flammable_gas",
			state: "gas",
			density: 2,
			tempHigh: 95,
			stateHigh: "burning_unrealistically_flammable_gas",
			conduct: 0.2
			}, 

		elements.burning_unrealistically_flammable_gas = {
			color: "#eedd11",
			behavior: [
				"M2 AND HT:3750%70 AND CR:plasma%10|M1 AND HT:3750%70 AND CR:plasma%10.000000000000000000000000000000000000000000|M2 AND HT:3750%70 AND CR:plasma%10",
				"M1 AND HT:3750%70 AND CR:plasma%10|HT:3750%70.000000 AND CH:plasma%6.71 AND EX:9>plasma,plasma,burning_unrealistically_flammable_gas%0.25|M1 AND HT:3750%70 AND CR:plasma%10",
				"M2 AND HT:3750%70 AND CR:plasma%10|M1 AND HT:3750%70 AND CR:plasma%10.000000000000000000000000000000000000000000|M2 AND HT:3750%70 AND CR:plasma%10",
			],
			behaviorOn: [
				"M2 AND HT:7500%70 AND CR:plasma%15|M1 AND HT:7500%70 AND CR:plasma%15.00000000000000000000000000000000000|M2 AND HT:7500%70 AND CR:plasma%15",
				"M1 AND HT:7500%70 AND CR:plasma%15|HT:7500%70 AND CH:plasma%5.60 AND EX:11>plasma,plasma,burning_unrealistically_flammable_gas%0.5|M2 AND HT:7500%70 AND CR:plasma%15",
				"M2 AND HT:7500%70 AND CR:plasma%15|M1 AND HT:7500%70 AND CR:plasma%15.00000000000000000000000000000000000|M2 AND HT:7500%70 AND CR:plasma%15",
			],
			category: "gases",
			burn: 2000,
			burnTime: 950,
			burnInto: "plasma",
			state: "gas",
			density: 1.5,
			tempHigh: 200001,
			stateHigh: "plasma",
			hidden: true,
			excludeRandom: true,
		}, 

		elements.unrealistically_flammable_powder = {
			color: "#cddd22",
			behavior: [
				"HT:2%2 AND CR:unrealistically_flammable_gas%3|HT:2%2 AND CR:unrealistically_flammable_gas%3|HT:2%2 AND CR:unrealistically_flammable_gas%3",
				"HT:2%2 AND CR:unrealistically_flammable_gas%1|HT:2%2.00000000000000000000|HT:2%2 AND CR:unrealistically_flammable_gas%1",
				"M2 AND HT:2%2.0000000000000|M1 AND HT:2%2.0000000000000|M2 AND HT:2%2.0000000000000",
			],
			behaviorOn: [
				"HT:4%4 AND CR:unrealistically_flammable_gas%6|HT:4%4 AND CR:unrealistically_flammable_gas%6|HT:4%4 AND CR:unrealistically_flammable_gas%6",
				"HT:4%4 AND CR:unrealistically_flammable_gas%2|HT:4%4.00000000000000000000|HT:4%4 AND CR:unrealistically_flammable_gas%2",
				"M2 AND HT:4%4.0000000000000|M1 AND HT:4%4.0000000000000|M2 AND HT:4%4.0000000000000",
			],
			category: "powders",
			burn: 3000,
			burnTime: 5,
			burnInto: "burning_unrealistically_flammable_gas",
			state: "powders",
			density: 20,
			tempHigh: 95,
			stateHigh: "burning_unrealistically_flammable_gas",
			conduct: 0.4,
		}, 

		elements.burning_unrealistically_flammable_powder = {
			color: "#ddcd22",
			behavior: [
				"HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7|HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7.0000000000000000000000000000000000000000000000000000000000000000000000000000|HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7",
				"HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7|HT:89850%70 AND CH:plasma%00000000005.60 AND EX:12>plasma,plasma,plasma,burning_unrealistically_flammable_gas,burning_unrealistically_flammable_powder%0.5|HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7",
				"M2 AND HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7|M1 AND HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7.00000000000000000000000000000000000000000000000000000000000000|M2 AND HT:89850%70 AND CR:burning_unrealistically_flammable_gas%7",
			],
			behaviorOn: [
				"HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9|HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9.00000000000000000000000000000000000000000000000000000000000000000000000000|HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9",
				"HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9|HT:179700%70 AND CH:plasma%00000000004.79 AND EX:13>plasma,plasma,plasma,burning_unrealistically_flammable_gas,burning_unrealistically_flammable_gas,burning_unrealistically_flammable_powder%1|HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9",
				"M2 AND HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9|M1 AND HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9.000000000000000000000000000000000000000000000000000000000000|M2 AND HT:179700%70 AND CR:burning_unrealistically_flammable_gas%9",
			],
			category: "powders",
			burn: 2000,
			burnTime: 1150,
			burnInto: "plasma",
			state: "powders",
			density: 15,
			tempHigh: 200001,
			stateHigh: "burning_unrealistically_flammable_gas",
			conduct: 0.4,
			hidden: true,
			excludeRandom: true,
		},

		elements.black_decay = { //random mystcraft mod reference
			name: "black decay",
			color: "#222222",
			behavior: [
				"XX|CH:black_decay%2 AND DL:black_decay%30|XX",
				"CH:black_decay%1|DL%0.2|CH:black_decay%1",
				"XX|CH:black_decay%1 AND M1|XX",
			],
			category: "special",
			excludeRandom: true,
		},

		elements.steel.behavior = behaviors.FAIRYKILL;

		elements.tungstensteel = {
			color: "#555589",
			behavior: behaviors.FAIRYKILL,
			tempHigh: 3600,
			category: "solids",
			density: 19000,
			conduct: 0.48,
		},

		elements.stainless_steel = {	
			color: "#7d8181",
			behavior: behaviors.WALL,
			reactions: {
				"pool_water": { elem1:"rust", chance:0.0009 },
				"salt_water": { elem1:"rust", chance:0.0003 },
				"salt": { elem1:"rust", chance:0.0003 },
				"acid": { elem1:"rust" }
			},
			tempHigh: 1455.5,
			category: "solids",
			density: 7930,
			conduct: 0.42,
			hardness: 0.85
		};
		
		elements.steel.reactions.acid = { elem1:"rust" };
		
		elements.molten_tungsten = {
			density: 17600,
			temp: 3500,
			tempHigh: 5555,
			stateHigh: "tungsten_gas",
		},

		elements.pop_rock_pop = {
			color: ["#ffb49f","#ffd9a1","#ffada1"],
			behavior: [
				"XX|XX|XX",
				"XX|EX:3>carbon_dioxide,sugar|XX",
				"XX|XX|XX",
			],
			category: "energy",
			state: "gas",
			density: 100,
			excludeRandom: true,
			hidden: true,
		},

		elements.pop_rocks = {
			color: ["#d4d4d4","#74d4d4","#d474d4","#7474d4","#d4d474","#74d474","#d47474","#747474","#2f2f2f","#8f2f2f","#2f8f2f","#8f8f2f","#2f2f8f","#8f2f8f","#2f8f8f","#8f8f8f","#606060","#c06060","#60c060","#c0c060","#6060c0","#c060c0","#60c0c0","#c0c0c0"],
			behavior: behaviors.POWDER,
			reactions: {
				water: { elem1: "sugar_water", elem2: [null,"pop_rock_pop"] }
			},
			tempHigh: 138,
			stateHigh: ["caramel","pop_rock_pop"],
			category: "land",
			state: "solid",
			density: 333.333333333, //made-up
			hardness: 0.2,
			breakInto: ["sugar","pop_rock_pop"],
		},

		elements.tungsten_gas = {
			color: "#FFEEE2",
			behavior: [
				"CR:plasma%0.625 AND M2|M1|CR:plasma%0.625 AND M2",
				"M1|XX|M1",
				"CR:plasma%0.625 AND M2|M1|CR:plasma%0.625 AND M2",
			],
			density: 15800, //https://link.springer.com/article/10.1007/s11661-019-05262-5
			temp: 5600,
			tempLow: 5555,
			stateLow: "molten_tungsten",
			category: "gases",
			hidden: true,
		},

		elements.molten_steel ??= {};
		elements.molten_steel.reactions ??= {};
		elements.molten_steel.reactions.molten_tungsten = { "elem1":"molten_tungstensteel", "elem2":"molten_tungstensteel" };
		elements.molten_steel.reactions.molten_chromium = { "elem1":"molten_stainless_steel", "elem2":["molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_stainless_steel"] };

		elements.unrealistically_flammable_substance_bomb = {
			name: "unrealistically flammable bomb",
			color: "#cdad52",
			behavior: [
				"XX|XX|XX",
				"XX|XX|XX",
				"M2|M1 AND EX:10>plasma,burning_unrealistically_flammable_powder,unrealistically_flammable_powder,unrealistically_flammable_powder,unrealistically_flammable_powder,burning_unrealistically_flammable_gas,unrealistically_flammable_gas,unrealistically_flammable_gas,unrealistically_flammable_gas|M2",
			],
			category: "weapons",
			state: "solid",
			density: 1300,
			excludeRandom: true,
		},

		elements.warp_bomb = {
			name: "warp bomb",
			color: "#422e4a",
			behavior: [
				"XX|XX|XX",
				"XX|CC:#5b3a69,#382740,#400e61|XX",
				"M2|M1 AND EX:15>warp|M2",
			],
			category: "weapons",
			state: "solid",
			density: 1300,
			excludeRandom: true,
		},
		
		elements.turbine = {
			color: "#75726a",
			tempHigh: elements.copper.tempHigh,
			stateHigh: ["steel","molten_copper"],
			conduct: 1,
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var neighbors = adjacentCoords.map(offsetPair => pixelMap[pixel.x+offsetPair[0]]?.[pixel.y+offsetPair[1]]).filter(function(pixelOrUndefined) { return typeof(pixelOrUndefined) == "object" });
				if(neighbors.length < 0) { return };
				var neighboringElements = neighbors.filter(function(px) { return !!px }).map(x => x.element);
				var neighboringStates = neighboringElements.map(elemName => elements[elemName].state ?? "solid");
				var nonSolidNeighbors = neighboringStates.filter(function(string) { return (string !== "solid") }).length;
				if(nonSolidNeighbors == 0) { return };
				pixel.charge ??= 0;
				pixel.charge += nonSolidNeighbors / 8;
				pixel.temp += (nonSolidNeighbors / 500);
			},
			onTryMoveInto: function(pixel,otherPixel) {
				pixel.charge ??= 0;
				pixel.charge += 1/8;
				pixel.temp += (1/500);
			},
			hardness: averageNumericArray([elements.copper.hardness,elements.steel.hardness,elements.steel.hardness]),
			breakInto: ["metal_scrap", "steel_scrap", "steel_scrap", "copper_scrap", "copper_scrap", "steel_scrap"],
			state: "solid",
			category: "machines",
			density: averageNumericArray([elements.steel.density, elements.copper.density, airDensity])
		};

		//hormones

			//estrogens

		elements.estradiol = {
			color: "#f2fcee", //it absorbs shorter wavelength UV than testosterone and I am treating this like absorbing violet for convenience
				  //https://www.researchgate.net/publication/226065469_Optical_Properties_of_Two_Types_of_Sex_Hormones_of_the_Cyclopentenephenanthrene_Series
				  //http://depts.washington.edu/cmditr/modules/lum/color.html
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1200,
			tempHigh: 173,
			category: "powders",
		},

		elements.molten_estradiol = {
			tempHigh: 446,
			stateHigh: "vaporized_estradiol",
		},

		elements.vaporized_estradiol = {
			color: ["#ffbf60","#ffdc60","#ff9d60"], //hormone gas wouldn't glow that brightly at these temperatures but just ignore that
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 972,
			temp: 500,
			tempLow: 446,
			stateLow: "molten_estradiol",
		},

			//progestogens

		elements.progesterone = {
			color: "#f7eefc", //slightly different? from testosterone but exaggerated
				  //https://downloads.hindawi.com/journals/ijps/2017/9603140.pdf
				  //these hormones all absorb in the uv region anyway so they would all look white to us
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1100,
			tempHigh: 121,
			category: "powders",
		},

		elements.molten_progesterone = {
			tempHigh: 447,
			stateHigh: "vaporized_progesterone",
		},

		elements.vaporized_progesterone = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 891,
			tempLow: 447,
			stateLow: "molten_progesterone",
		}

			//androgens

				//plain testosterone

		elements.testosterone = {
			color: "#f7eef7", //it absorbs longer wavelength UV than estradiol and I am treating this like absorbing green for convenience
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1100,
			tempHigh: 155,
			category: "powders",
		},

		elements.molten_testosterone = {
			tempHigh: 433,
			temp: 400,
			stateHigh: "vaporized_testosterone",
		},

		elements.vaporized_testosterone = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 891,
			temp: 500,
			tempLow: 433,
			stateLow: "molten_testosterone",
		},

				//undecanoate (form actually used in HRT)

		elements.testosterone_undecanoate = {
			color: "#f8f2fc", //more creatively-interpreted UV data: https://spectrabase.com/spectrum/5Yc7XCCDkA7 plus http://depts.washington.edu/cmditr/modules/lum/color.html and a lot of eyeballing and loose approximation
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1037, //https://www.chembk.com/en/chem/Testosterone%20Undecanoate
			tempHigh: 63,
			category: "powders",
		},

		elements.molten_testosterone_undecanoate = {
			tempHigh: 550,
			stateHigh: "vaporized_testosterone_undecanoate",
			hidden: true,
		},

		elements.vaporized_testosterone_undecanoate = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 834, //made-up due to lack of data
			temp: 600,
			tempLow: 63,
			stateLow: "molten_testosterone_undecanoate",
		},

		//other

			//anti-androgens

				//CPA

		elements.cyproterone_acetate = {
			color: "#efeef8", //it absorbs far longer uv than the others, which i am rendering as red absorption
				  //https://www.researchgate.net/figure/UV-spectrum-for-drospirenone-cyproterone-acetate-desogestrel-and-ethinyl-estradiol-at-1_fig1_315746083
				  //i didn't really expect to find a spectrum for this
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1068,
			tempHigh: 200,
			category: "powders",
		},

		/*	> Hazardous decomposition products:
			> Hydrogen chloride (HCl)
			> Carbon monoxide and carbon dioxide
			> Hydrogen

			> https://cdn.caymanchem.com/cdn/msds/16622m.pdf

			so many interesting effects i can't add
		*/
		elements.molten_cyproterone_acetate = {
			tempHigh: 569,
			stateHigh: "vaporized_cyproterone_acetate",
		},

		elements.vaporized_cyproterone_acetate = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 865,
			tempLow: 569,
			stateLow: "molten_cyproterone_acetate",
		},

				//spironolactone

		elements.spironolactone = {
			color: "#f7eef1", //UV absorbance peak wavelength is slightly shorter than that of testosterone
				  //https://www.researchgate.net/publication/348592381_Quantification_of_Spironolactone_by_first_and_second_order_UV_Derivative_Spectrophotometry_in_bulk_and_tablet_dosage_form/link/6006b3cf299bf14088a649bd/download
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1200,
			tempHigh: 207,
			category: "powders",
		},

		elements.molten_spironolactone = {
			tempHigh: 597,
			stateHigh: "vaporized_spironolactone",
			/*should have more decomps
			https://sci-hub.se/https://link.springer.com/article/10.1007/BF01979243
			> The TG-DTG curves of spironolactone in Fig. 7 demonstrate that the compound is thermally stable up to 200*C, and that its thermal decomposition occurs between 200 and 620*C. Four consecutive steps are observed in the TG-DTG curves. The first step, up to 260*C is ascribed to the elimination of the substituent group, SCOCH_{3} (TG= 19.59%, Calc. = 19.33%). The second step (260-370*C) and the third and fourth steps (370-700*C) involve losses of 42.93% and 37.48%, respectively, but do not permit a suggestion as to which parts of the compound are eliminated in each step. */
		},

		elements.vaporized_spironolactone = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 972,
			tempLow: 597,
			stateLow: "molten_spironolactone",
		},

				//finasteride

		elements.finasteride = {
			color: "#fcfcf1", //UV absorbance peak wavelength is even shorter than that of estradiol
				  //https://www.researchgate.net/publication/312317200
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1100,
			tempHigh: 253,
			category: "powders",
		},

		elements.molten_finasteride = {
			tempHigh: 577,
			stateHigh: "vaporized_finasteride",
		},

		elements.vaporized_finasteride = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 891,
			tempLow: 577,
			stateLow: "molten_finasteride",
		},

				//dutasteride

		elements.dutasteride = {
			color: "#fbf6ee", //High UV absorbances around the peak wavelengths of both estradiol and testosterone
				  //https://sphinxsai.com/sphinxsaivol_2no.1/pharmtech_vol_2no.1/PharmTech_Vol_2No.1PDF/PT=18%20(113-117).pdf
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1303, //https://www.chemicalbook.com/ChemicalProductProperty_EN_CB3254628.htm
			tempHigh: 243,
			category: "powders",
		},

		elements.molten_dutasteride = {
			tempHigh: 620, //http://www.chemspider.com/Chemical-Structure.5293502.html
			stateHigh: "vaporized_dutasteride",
		},

		elements.vaporized_dutasteride = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 1055,
			tempLow: 620,
			stateLow: "molten_dutasteride",
		},

				//bicalutamide

		elements.bicalutamide = {
			color: "#f4fcee", //peaks at 200-220 and at 270
				  //i am probably mapping uv to visible wrong and misreading color.html
				  //https://www.researchgate.net/publication/257679318
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1520, //https://www.chemicalbook.com/ProductMSDSDetailCB7457827_EN.htm
			tempHigh: 192,
			category: "powders",
		},

		elements.molten_bicalutamide = {
			tempHigh: 659,
			stateHigh: "vaporized_bicalutamide",
		},

		elements.vaporized_bicalutamide = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 1231,
			tempLow: 659,
			stateLow: "molten_bicalutamide",
		},

			//puberty blockers

		elements.leuprolide = {
			color: "#f5eefb", //http://dspace.hmlibrary.ac.in:8080/jspui/bitstream/123456789/1143/11/11_Chapter%203.pdf
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1440, //https://www.chemicalbook.com/ProductMSDSDetailCB7457827_EN.htm
			tempHigh: 150,
			category: "powders",
		},

		elements.molten_leuprolide = {
			tempHigh: 1720, //https://web.archive.org/web/20210512074205/http://www.shreejipharmainternational.com/leuprolide-acetate-1177796.html
			stateHigh: "vaporized_leuprolide",
		},

		elements.vaporized_leuprolide = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			density: 1166,
			tempLow: 1720,
			stateLow: "molten_leuprolide",
		},

			//histrelin

		elements.histrelin = {
			color: "#f8f5ee", //no spectrum available
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 1500, //https://www.chemicalbook.com/ProductMSDSDetailCB7457827_EN.htm
			tempHigh: 1800, //https://www.chemsrc.com/en/cas/76712-82-8_1042020.html
			stateHigh: "vaporized_histrelin",
			category: "powders",
		},

		elements.vaporized_histrelin = {
			color: ["#ffbf60","#ffdc60","#ff9d60"],
			behavior: behaviors.GAS,
			state: "gas",
			category: "gases",
			hidden: true,
			tempLow: 1800,
			stateLow: "histrelin",
		},

		//end of hrt section

		elements.densinium = {
			color: ["#565656","#575657","#565257","#554d57","#554659"],
			tempHigh: 4712, //arbitrary
			hardness: 0.9991, //somewhat arbitrary
			density: 39180,
			conduct: 0.86, //arbitrary
			behavior: behaviors.WALL,
			state: "solid",
			category: "solids",
		} //this is effectively a mere interpretation of densinium

		elements.molten_densinium = {
			hardness: 0.9991,
		}

		elements.acid.ignore.push("densinium","molten_densinium")

		//maxColorOffset will only be applied if maxColorOffset.js is enabled
		elements.silk_velvet = {
			color: ["#edece8", "#ede7e4"],
			maxColorOffset: 7,
			category: "land",
			state: "solid",
			behavior: [
				"XX|XX|XX",
				"XX|XX|XX",
				"XX|M1|XX",
			],
			burnInto: "ash",
			burn:72,
			burnTime:25,
			density: 182,
		};

		elements.red_velvet = {
			color: ["#a80508", "#b30b0e"],
			maxColorOffset: 7,
			category: "land",
			state: "solid",
			behavior: [
				"XX|XX|XX",
				"XX|XX|XX",
				"XX|M1|XX",
			],
			tick: function() { //alias for velvet that is red
				pixel.element = "silk_velvet";
			},
			burnInto: "ash",
			burn: 72,
			burnTime: 25,
			density: 182,
		};

		elements.netherrack.hardness = 0.07;
		elements.netherrack.breakInto = ["crushed_netherrack","crushed_netherrack","crushed_netherrack","crushed_netherrack","crushed_netherrack","crushed_netherrack","crushed_netherrack","sulfur"] // and some copper, gold, iron, nickel after processing //sulfur closer to 1/7 in-game
		elements.netherrack.burn = 9
		elements.netherrack.burnTime = 9007199254740995
		elements.netherrack.burnInto = "netherrack"

		elements.crushed_netherrack = {
			color: ["#e34b46","#b04235","#73431f","#522510","#7a3326"],
			behavior: behaviors.POWDER,
			category:"land",
			tempHigh: 2750,
			stateHigh: "molten_netherrack",
			state: "solid",
			density: 1680,
			burn: 20,
			burnTime: 9007199254740995,
			hardness: 0.02,
			hidden: true,
		};

		elements.sencc = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				for (let i = -1; i < 2; i++) {
					for (let j = -1; j < 2; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/8)*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc2 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				for (let i = -3; i < 4; i++) {
					for (let j = -3; j < 4; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/24)*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc3 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 3
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc4 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 4
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc5 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 5
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc6 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 6
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc7 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 7
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc8 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 8
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc9 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 9
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc10 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 10
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc11 = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				var squadius = 11
				for (let i = (-1*squadius); i < (squadius+1); i++) {
					for (let j = (-1*squadius); j < (squadius+1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/((((squadius*2)+1)**2)-1))*pixel.uwu + ",0,0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.sencc2b = { //same element neighbor count check
			color: "#000000",
			uwu: 0,
			owo: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				pixel.owo = 0
				for (let i = -2; i < 3; i++) {
					for (let j = -2; j < 3; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								if(pixel.uwu < 8) {
									pixel.uwu++
								} else {
									pixel.owo++
								}
							}
						}
					}
				}
				pixel.owo -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu) || pixel.owo == undefined || pixel.owo == null || isNaN(pixel.owo)) {
					pixel.color = "rgb(127,127,127)"
				} else {
					pixel.color = "rgb(" + (255/8)*pixel.uwu + "," + (255/16)*pixel.owo + ",0)"
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
		},

		elements.discharge = {
			color: "#7f7f7f",
			tick: function(pixel) {
				for (var i = 1; i < width; i++) {
					for (var j = 1; j < height; j++) {
						if (!isEmpty(i,j)) {
							pixelMap[i][j].charge = 0
						}
					}
				}
				deletePixel(pixel.x, pixel.y)
			},
			category:"special",
			insulate:true,
			state: "solid",
			behavior: behaviors.SELFDELETE,
		},

		elements.troll_powder = {
			color: ["#ffffff","#000000"],
			tick: function(pixel) {
				ddd = Math.random()
				eee = Math.random()
				fff = 1-eee
				doHeat(pixel);
				doBurning(pixel);
				if(ddd < 0.9) {
					if(!tryMove(pixel, pixel.x, pixel.y+1)) {
						if(eee < 1/2) { tryMove(pixel, pixel.x-1, pixel.y+1) } else tryMove(pixel, pixel.x+1, pixel.y+1)
					}
					if(Math.random() < 0.0017) {
						if(fff < 1/5) { tryMove(pixel, pixel.x-2, pixel.y-1) }
						if(fff < 2/5) { tryMove(pixel, pixel.x-1, pixel.y-2) }
						if(fff < 3/5) { tryMove(pixel, pixel.x, pixel.y-3) }
						if(fff < 4/5) { tryMove(pixel, pixel.x+1, pixel.y-2) }
						if(fff < 5/5) { tryMove(pixel, pixel.x+2, pixel.y-1) }
					}
					if(Math.random() < 0.0003) { tryMove(pixel, pixel.y, pixel.y); }
					if(Math.random() < 0.0003) { tryMove(pixel, pixel.x, pixel.x); }		
					if(((Math.floor(pixel.x/2) % 2 == 0) && (Math.floor(pixel.y/2) % 2 == 0)) || ((Math.floor(pixel.x/2) % 2 == 1) && (Math.floor(pixel.y/2) % 2 == 1))) {
						pixel.color = "rgb(32,32,32)"
					} else {
						pixel.color = "rgb(224,224,224)"
					}
				}

				if(ddd >= 0.9) {
					if(!tryMove(pixel, pixel.x, pixel.y-1)) {
						if(eee < 1/2) { tryMove(pixel, pixel.x-1, pixel.y-1) } else tryMove(pixel, pixel.x+1, pixel.y-1)
					}
					if(Math.random() < 0.0017) {
						if(fff < 1/5) { tryMove(pixel, pixel.x-2, pixel.y+1) }
						if(fff < 2/5) { tryMove(pixel, pixel.x-1, pixel.y+2) }
						if(fff < 3/5) { tryMove(pixel, pixel.x, pixel.y+3) }
						if(fff < 4/5) { tryMove(pixel, pixel.x+1, pixel.y+2) }
						if(fff < 5/5) { tryMove(pixel, pixel.x+2, pixel.y+1) }
					}
					if(Math.random() < 0.0003) { tryMove(pixel, pixel.y, pixel.y); }
					if(Math.random() < 0.0003) { tryMove(pixel, pixel.x, pixel.x); }		
					if(((Math.floor(pixel.x/2) % 2 == 0) && (Math.floor(pixel.y/2) % 2 == 0)) || ((Math.floor(pixel.x/2) % 2 == 1) && (Math.floor(pixel.y/2) % 2 == 1))) {
						pixel.color = "rgb(32,32,32)"
					} else {
						pixel.color = "rgb(224,224,224)"
					}
					pixel.temp = pixel.temp + ((Math.floor(Math.random()*3) - 1)*2)
				}
			},
			category: "powders",
			state: "solid",
			density: 1602,
		},

		elements.void_first = {
			color: "#262626",
			tick: function(pixel) {
				if(!pixel.void) {
					//store 4 touching pixels in variables if the variables don't exist
					if(!outOfBounds(pixel.x,pixel.y-1) && !isEmpty(pixel.x,pixel.y-1)) {
						if(!pixel.dc1 && pixelMap[pixel.x][pixel.y-1].element != pixel.element) {
							pixel.dc1 = pixelMap[pixel.x][pixel.y-1].element
						}
					}
					if(!outOfBounds(pixel.x+1,pixel.y) && !isEmpty(pixel.x+1,pixel.y)) {
						if(!pixel.dc2 && pixelMap[pixel.x+1][pixel.y].element != pixel.element) {
							pixel.dc2 = pixelMap[pixel.x+1][pixel.y].element
						}
					}
					if(!outOfBounds(pixel.x,pixel.y+1) && !isEmpty(pixel.x,pixel.y+1)) {
						if(!pixel.dc3 && pixelMap[pixel.x][pixel.y+1].element != pixel.element) {
							pixel.dc3 = pixelMap[pixel.x][pixel.y+1].element
						}
					}
					if(!outOfBounds(pixel.x-1,pixel.y) && !isEmpty(pixel.x-1,pixel.y)) {
						if(!pixel.dc3 && pixelMap[pixel.x-1][pixel.y].element != pixel.element) {
							pixel.dc4 = pixelMap[pixel.x-1][pixel.y].element
						}
					}
					//choose from 1
					if(pixel.dc1 && !pixel.dc2 && !pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							pixel.void = pixel.dc1
						}
					}
					if(!pixel.dc1 && pixel.dc2 && !pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							pixel.void = pixel.dc2
						}
					}
					if(!pixel.dc1 && !pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							pixel.void = pixel.dc3
						}
					}
					if(!pixel.dc1 && !pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							pixel.void = pixel.dc4
						}
					}
					ggg = Math.random()
					hhh = Math.random()
					iii = Math.random()
					//choose from 2
					//1100 and 0011
					if(pixel.dc1 && pixel.dc2 && !pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							if(ggg < 1/2) {
								pixel.void = pixel.dc1
							} else {
								pixel.void = pixel.dc2
							}
						}
					}
					if(!pixel.dc1 && !pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(ggg < 1/2) {
								pixel.void = pixel.dc3
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
					//1010 and 0101
					if(pixel.dc1 && !pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							if(ggg < 1/2) {
								pixel.void = pixel.dc1
							} else {
								pixel.void = pixel.dc3
							}
						}
					}
					if(!pixel.dc1 && pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(ggg < 1/2) {
								pixel.void = pixel.dc2
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
					//0110 and 1001
					if(!pixel.dc1 && pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							if(ggg < 1/2) {
								pixel.void = pixel.dc2
							} else {
								pixel.void = pixel.dc3
							}
						}
					}
					if(pixel.dc1 && !pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(ggg < 1/2) {
								pixel.void = pixel.dc1
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
					//choose from 3
					//0111
					if(!pixel.dc1 && pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(hhh < 1/3) {
								pixel.void = pixel.dc2
							} else if(hhh < 2/3) {
								pixel.void = pixel.dc3
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
					//1011
					if(pixel.dc1 && !pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(hhh < 1/3) {
								pixel.void = pixel.dc1
							} else if(hhh < 2/3) {
								pixel.void = pixel.dc3
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
					//1101
					if(pixel.dc1 && pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(hhh < 1/3) {
								pixel.void = pixel.dc1
							} else if(hhh < 2/3) {
								pixel.void = pixel.dc2
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
					//1110
					if(pixel.dc1 && pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.void) {
							if(hhh < 1/3) {
								pixel.void = pixel.dc1
							} else if(hhh < 2/3) {
								pixel.void = pixel.dc2
							} else {
								pixel.void = pixel.dc3
							}
						}
					}
					//choose from 4
					//1111
					if(pixel.dc1 && pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.void) {
							if(iii < 1/4) {
								pixel.void = pixel.dc1
							} else if(iii < 2/4) {
								pixel.void = pixel.dc2
							} else if(iii < 3/4) {
								pixel.void = pixel.dc3
							} else {
								pixel.void = pixel.dc4
							}
						}
					}
				} else if(pixel.void) {
					if(pixel.dc1 || pixel.dc2 || pixel.dc3 || pixel.dc4) {
						delete pixel.dc1;
						delete pixel.dc2;
						delete pixel.dc3;
						delete pixel.dc4;
					}
				}

				for(i = 0; i < adjacentCoords.length; i++) {
					var pX = pixel.x; var pY = pixel.y; var oX = adjacentCoords[i][0]; var oY = adjacentCoords[i][1]; var nX = pX+oX; var nY = pY+oY;
					if(!isEmpty(nX,nY,true)) {
						var newPixel = pixelMap[nX][nY]
						var newElement = newPixel.element;
						if(newElement != pixel.element && newElement === pixel.void) {
							deletePixel(nX,nY);
						};
					};
				};
			},
			category:"special",
			hardness: 1,
		},

		elements.converter = {
			color: "#2ec408",
			tick: function(pixel) {
				if(!pixel.changeTo) {
					//store 4 touching pixels in variables if the variables don't exist
					if(!outOfBounds(pixel.x,pixel.y-1) && !isEmpty(pixel.x,pixel.y-1)) {
						if(!pixel.dc1 && pixelMap[pixel.x][pixel.y-1].element != pixel.element) {
							pixel.dc1 = pixelMap[pixel.x][pixel.y-1].element
						}
					}
					if(!outOfBounds(pixel.x+1,pixel.y) && !isEmpty(pixel.x+1,pixel.y)) {
						if(!pixel.dc2 && pixelMap[pixel.x+1][pixel.y].element != pixel.element) {
							pixel.dc2 = pixelMap[pixel.x+1][pixel.y].element
						}
					}
					if(!outOfBounds(pixel.x,pixel.y+1) && !isEmpty(pixel.x,pixel.y+1)) {
						if(!pixel.dc3 && pixelMap[pixel.x][pixel.y+1].element != pixel.element) {
							pixel.dc3 = pixelMap[pixel.x][pixel.y+1].element
						}
					}
					if(!outOfBounds(pixel.x-1,pixel.y) && !isEmpty(pixel.x-1,pixel.y)) {
						if(!pixel.dc3 && pixelMap[pixel.x-1][pixel.y].element != pixel.element) {
							pixel.dc4 = pixelMap[pixel.x-1][pixel.y].element
						}
					}
					//choose from 1
					if(pixel.dc1 && !pixel.dc2 && !pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) {
							pixel.changeTo = pixel.dc1
						}
					}
					if(!pixel.dc1 && pixel.dc2 && !pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) {
							pixel.changeTo = pixel.dc2
						}
					}
					if(!pixel.dc1 && !pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) {
							pixel.changeTo = pixel.dc3
						}
					}
					if(!pixel.dc1 && !pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							pixel.changeTo = pixel.dc4
						}
					}
					ggg = Math.random()
					hhh = Math.random()
					iii = Math.random()
					//choose from 2
					//1100 and 0011
					if(pixel.dc1 && pixel.dc2 && !pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) {
							if(ggg < 1/2) {
								pixel.changeTo = pixel.dc1
							} else {
								pixel.changeTo = pixel.dc2
							}
						}
					}
					if(!pixel.dc1 && !pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(ggg < 1/2) {
								pixel.changeTo = pixel.dc3
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
					//1010 and 0101
					if(pixel.dc1 && !pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) { //7989 yay soshi!
							if(ggg < 1/2) {
								pixel.changeTo = pixel.dc1
							} else {
								pixel.changeTo = pixel.dc3
							}
						}
					}
					if(!pixel.dc1 && pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(ggg < 1/2) {
								pixel.changeTo = pixel.dc2
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
					//0110 and 1001
					if(!pixel.dc1 && pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) {
							if(ggg < 1/2) {
								pixel.changeTo = pixel.dc2
							} else {
								pixel.changeTo = pixel.dc3
							}
						}
					}
					if(pixel.dc1 && !pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(ggg < 1/2) {
								pixel.changeTo = pixel.dc1
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
					//choose from 3
					//0111
					if(!pixel.dc1 && pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(hhh < 1/3) {
								pixel.changeTo = pixel.dc2
							} else if(hhh < 2/3) {
								pixel.changeTo = pixel.dc3
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
					//1011
					if(pixel.dc1 && !pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(hhh < 1/3) {
								pixel.changeTo = pixel.dc1
							} else if(hhh < 2/3) {
								pixel.changeTo = pixel.dc3
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
					//1101
					if(pixel.dc1 && pixel.dc2 && !pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(hhh < 1/3) {
								pixel.changeTo = pixel.dc1
							} else if(hhh < 2/3) {
								pixel.changeTo = pixel.dc2
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
					//1110
					if(pixel.dc1 && pixel.dc2 && pixel.dc3 && !pixel.dc4) {
						if(!pixel.changeTo) {
							if(hhh < 1/3) {
								pixel.changeTo = pixel.dc1
							} else if(hhh < 2/3) {
								pixel.changeTo = pixel.dc2
							} else {
								pixel.changeTo = pixel.dc3
							}
						}
					}
					//choose from 4
					//1111
					if(pixel.dc1 && pixel.dc2 && pixel.dc3 && pixel.dc4) {
						if(!pixel.changeTo) {
							if(iii < 1/4) {
								pixel.changeTo = pixel.dc1
							} else if(iii < 2/4) {
								pixel.changeTo = pixel.dc2
							} else if(iii < 3/4) {
								pixel.changeTo = pixel.dc3
							} else {
								pixel.changeTo = pixel.dc4
							}
						}
					}
				} else if(pixel.changeTo) {
					if(pixel.dc1 || pixel.dc2 || pixel.dc3 || pixel.dc4) {
						delete pixel.dc1;
						delete pixel.dc2;
						delete pixel.dc3;
						delete pixel.dc4;
					}
				}

				for(i = 0; i < adjacentCoords.length; i++) {
					var pX = pixel.x; var pY = pixel.y; var oX = adjacentCoords[i][0]; var oY = adjacentCoords[i][1]; var nX = pX+oX; var nY = pY+oY;
					if(!isEmpty(nX,nY,true)) {
						var newPixel = pixelMap[nX][nY]
						var newElement = newPixel.element;
						if((elements[pixel.element].ignore ?? []).includes(newElement)) { return };
						if(newElement != pixel.element) {
							changePixel(newPixel,pixel.changeTo)
						};
					};
				};
			},
			category:"special",
			ignore: ["wall","cloner","liquid_cloner","slow_cloner","void","clone_powder","floating_cloner","void_first","converter"],
			hardness: 1,
		},

		conveyorIgnoreList = ["conveyor_1","conveyor_2","wall"]

		elements.conveyor_1 = {
			color: "#7f7f7f",
			tick: function(pixel) {
				//top right
				if (!isEmpty(pixel.x,pixel.y-1) && !outOfBounds(pixel.x,pixel.y-1)) {
					if (pixelMap[pixel.x][pixel.y-1].element == "body") {
						if(!isEmpty(pixel.x,pixel.y-2) && !outOfBounds(pixel.x,pixel.y-2)) {
							if (pixelMap[pixel.x][pixel.y-2].element == "head") {
								if(isEmpty(pixel.x+1,pixel.y-1) && isEmpty(pixel.x+1,pixel.y-2) && !outOfBounds(pixel.x+1,pixel.y-1) && !outOfBounds(pixel.x+1,pixel.y-2)) {
									tryMove(pixelMap[pixel.x][pixel.y-1],pixel.x+1,pixel.y-1)
									tryMove(pixelMap[pixel.x][pixel.y-2],pixel.x+1,pixel.y-2)
								}
							}
						} else {
							if(isEmpty(pixel.x+1,pixel.y-1) && !outOfBounds(pixel.x+1,pixel.y-1)) {
								tryMove(pixelMap[pixel.x][pixel.y-1],pixel.x+1,pixel.y-1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x][pixel.y-1].element)) {
						tryMove(pixelMap[pixel.x][pixel.y-1],pixel.x+1,pixel.y-1)
					}
				}
				//right down
				if (!isEmpty(pixel.x+1,pixel.y) && !outOfBounds(pixel.x+1,pixel.y)) {
					if (pixelMap[pixel.x+1][pixel.y].element == "body") {
						if(!isEmpty(pixel.x+1,pixel.y-1) && !outOfBounds(pixel.x+1,pixel.y-1)) {
							if (pixelMap[pixel.x+1][pixel.y-1].element == "head") {
								if(isEmpty(pixel.x+1,pixel.y+1) && !outOfBounds(pixel.x+1,pixel.y+1)) {
									tryMove(pixelMap[pixel.x+1][pixel.y],pixel.x+1,pixel.y+1)
									tryMove(pixelMap[pixel.x+1][pixel.y-1],pixel.x+1,pixel.y)
								}
							}
						} else {
							if(isEmpty(pixel.x+1,pixel.y+1) && !outOfBounds(pixel.x+1,pixel.y+1)) {
								tryMove(pixelMap[pixel.x+1][pixel.y],pixel.x+1,pixel.y+1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x+1][pixel.y].element)) {
						tryMove(pixelMap[pixel.x+1][pixel.y],pixel.x+1,pixel.y+1)
					}
				}
				//bottom left
				if (!isEmpty(pixel.x,pixel.y+1) && !outOfBounds(pixel.x,pixel.y+1)) {
					if (pixelMap[pixel.x][pixel.y+1].element == "head") {
						if(!isEmpty(pixel.x,pixel.y+2) && !outOfBounds(pixel.x,pixel.y+2)) {
							if (pixelMap[pixel.x][pixel.y+2].element == "body") {
								if(isEmpty(pixel.x-1,pixel.y+1) && isEmpty(pixel.x-1,pixel.y+2) && !outOfBounds(pixel.x-1,pixel.y+2) && !outOfBounds(pixel.x-1,pixel.y+2)) {
									tryMove(pixelMap[pixel.x][pixel.y+1],pixel.x-1,pixel.y+1)
									tryMove(pixelMap[pixel.x][pixel.y+2],pixel.x-1,pixel.y+2)
								}
							}
						} else {
							if(isEmpty(pixel.x-1,pixel.y+1) && !outOfBounds(pixel.x-1,pixel.y+1)) {
								tryMove(pixelMap[pixel.x][pixel.y+1],pixel.x-1,pixel.y+1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x][pixel.y+1].element)) {
						tryMove(pixelMap[pixel.x][pixel.y+1],pixel.x-1,pixel.y+1)
					}
				}
				//left up
				if (!isEmpty(pixel.x-1,pixel.y) && !outOfBounds(pixel.x-1,pixel.y)) {
					if (pixelMap[pixel.x-1][pixel.y].element == "head") {
						if(!isEmpty(pixel.x-1,pixel.y+1) && !outOfBounds(pixel.x-1,pixel.y+1)) {
							if (pixelMap[pixel.x-1][pixel.y+1].element == "body") {
								if(isEmpty(pixel.x-1,pixel.y-1) && !outOfBounds(pixel.x-1,pixel.y-1)) {
									tryMove(pixelMap[pixel.x-1][pixel.y],pixel.x-1,pixel.y-1)
									tryMove(pixelMap[pixel.x-1][pixel.y+1],pixel.x-1,pixel.y)
								}
							}
						} else {
							if(isEmpty(pixel.x-1,pixel.y-1) && !outOfBounds(pixel.x-1,pixel.y-1)) {
								tryMove(pixelMap[pixel.x-1][pixel.y],pixel.x-1,pixel.y-1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x-1][pixel.y].element)) {
						tryMove(pixelMap[pixel.x-1][pixel.y],pixel.x-1,pixel.y-1)
					}
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
		},

		elements.conveyor_2 = {
			color: "#7f7f7f",
			tick: function(pixel) {
				//top left
				if (!isEmpty(pixel.x,pixel.y-1) && !outOfBounds(pixel.x,pixel.y-1)) {
					if (pixelMap[pixel.x][pixel.y-1].element == "body") {
						if(!isEmpty(pixel.x,pixel.y-2) && !outOfBounds(pixel.x,pixel.y-2)) {
							if (pixelMap[pixel.x][pixel.y-2].element == "head") {
								if(isEmpty(pixel.x-1,pixel.y-1) && isEmpty(pixel.x-1,pixel.y-2) && !outOfBounds(pixel.x-1,pixel.y-1) && !outOfBounds(pixel.x-1,pixel.y-2)) {
									tryMove(pixelMap[pixel.x][pixel.y-1],pixel.x-1,pixel.y-1)
									tryMove(pixelMap[pixel.x][pixel.y-2],pixel.x-1,pixel.y-2)
								}
							}
						} else {
							if(isEmpty(pixel.x-1,pixel.y-1) && !outOfBounds(pixel.x-1,pixel.y-1)) {
								tryMove(pixelMap[pixel.x][pixel.y-1],pixel.x-1,pixel.y-1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x][pixel.y-1].element)) {
						tryMove(pixelMap[pixel.x][pixel.y-1],pixel.x-1,pixel.y-1)
					}
				}
				//right up
				if (!isEmpty(pixel.x+1,pixel.y) && !outOfBounds(pixel.x+1,pixel.y)) {
					if (pixelMap[pixel.x+1][pixel.y].element == "head") {
						if(!isEmpty(pixel.x+1,pixel.y+1) && !outOfBounds(pixel.x+1,pixel.y+1)) {
							if (pixelMap[pixel.x+1][pixel.y+1].element == "body") {
								if(isEmpty(pixel.x+1,pixel.y-1) && !outOfBounds(pixel.x+1,pixel.y-1)) {
									tryMove(pixelMap[pixel.x+1][pixel.y],pixel.x+1,pixel.y-1)
									tryMove(pixelMap[pixel.x+1][pixel.y+1],pixel.x+1,pixel.y)
								}
							}
						} else {
							if(isEmpty(pixel.x+1,pixel.y-1) && !outOfBounds(pixel.x+1,pixel.y-1)) {
								tryMove(pixelMap[pixel.x+1][pixel.y],pixel.x+1,pixel.y-1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x+1][pixel.y].element)) {
						tryMove(pixelMap[pixel.x+1][pixel.y],pixel.x+1,pixel.y-1)
					}
				}
				//bottom right
				if (!isEmpty(pixel.x,pixel.y+1) && !outOfBounds(pixel.x,pixel.y+1)) {
					if (pixelMap[pixel.x][pixel.y+1].element == "head") {
						if(!isEmpty(pixel.x,pixel.y+2) && !outOfBounds(pixel.x,pixel.y+2)) {
							if (pixelMap[pixel.x][pixel.y+2].element == "body") {
								if(isEmpty(pixel.x+1,pixel.y+1) && isEmpty(pixel.x+1,pixel.y+2) && !outOfBounds(pixel.x+1,pixel.y+2) && !outOfBounds(pixel.x+1,pixel.y+2)) {
									tryMove(pixelMap[pixel.x][pixel.y+1],pixel.x+1,pixel.y+1)
									tryMove(pixelMap[pixel.x][pixel.y+2],pixel.x+1,pixel.y+2)
								}
							}
						} else {
							if(isEmpty(pixel.x+1,pixel.y+1) && !outOfBounds(pixel.x+1,pixel.y+1)) {
								tryMove(pixelMap[pixel.x][pixel.y+1],pixel.x+1,pixel.y+1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x][pixel.y+1].element)) {
						tryMove(pixelMap[pixel.x][pixel.y+1],pixel.x+1,pixel.y+1)
					}
				}
				//left down
				if (!isEmpty(pixel.x-1,pixel.y) && !outOfBounds(pixel.x-1,pixel.y)) {
					if (pixelMap[pixel.x-1][pixel.y].element == "body") {
						if(!isEmpty(pixel.x-1,pixel.y-1) && !outOfBounds(pixel.x-1,pixel.y-1)) {
							if (pixelMap[pixel.x-1][pixel.y-1].element == "head") {
								if(isEmpty(pixel.x-1,pixel.y+1) && !outOfBounds(pixel.x-1,pixel.y+1)) {
									tryMove(pixelMap[pixel.x-1][pixel.y],pixel.x-1,pixel.y+1)
									tryMove(pixelMap[pixel.x-1][pixel.y-1],pixel.x-1,pixel.y)
								}
							}
						} else {
							if(isEmpty(pixel.x-1,pixel.y+1) && !outOfBounds(pixel.x-1,pixel.y+1)) {
								tryMove(pixelMap[pixel.x-1][pixel.y],pixel.x-1,pixel.y+1)
							}
						}
					} else if(!conveyorIgnoreList.includes(pixelMap[pixel.x-1][pixel.y].element)) {
						tryMove(pixelMap[pixel.x-1][pixel.y],pixel.x-1,pixel.y+1)
					}
				}
			},
			category: "machines",
			insulate: true,
			state: "solid",
		},

		elements.vanishing_wall = {
			behavior: behaviors.WALL,
			color: "#8080b0",
			colorObject: hexToRGB("#8080b0"),
			density: 3333,
			tick: function(pixel) {
				pixelTick(pixel)
				if(pixel.charge) {
					if(!isEmpty(pixel.x,pixel.y)) {
						deletePixel(pixel.x,pixel.y)
					}
				}
			},
			category: "special",
			state: "solid",
			hardness: 1,
			insulate: true,
			conduct: 1,
			extraInfo: "It disappears when charged.",
		},

		elements.vanishing_steel = {
			color: "#71797E",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				pixelTick(pixel);
				if(pixel.charge) {
					if(!isEmpty(pixel.x,pixel.y)) {
						deletePixel(pixel.x,pixel.y);
					};
				};
			},
			category: "solids",
			state: "solid",
			density: 7850,
			conduct: 1,
			hardness: 0.8,
		};

		elements.polka_dotted_powder = {
			color: ["#000000","#000000","#7f7f7f","#ffffff","#ffffff"],
			behavior: behaviors.POWDER,
			category: "powders",
			state: "solid",
			density: 1400,
			tick: function(pixel) {
				if(pixel.y % 6 == 0) {
					if(pixel.x % 6 == 0) {
						pixel.color = "rgb(255,255,255)"
					} else {
						if(!settings.bg || settings.bg == "#000000") {
							pixel.color = "rgb(15,15,15)"
						} else {
							pixel.color = "rgb(0,0,0)"
						}
					} 
				} else if((pixel.y + 3) % 6 == 0) {
					if((pixel.x + 3) % 6 == 0) {
						pixel.color = "rgb(255,255,255)"
					} else {
						if(!settings.bg || settings.bg == "#000000") {
							pixel.color = "rgb(15,15,15)"
						} else {
							pixel.color = "rgb(0,0,0)"
						}
					} 
				} else {
					if(!settings.bg || settings.bg == "#000000") {
						pixel.color = "rgb(15,15,15)"
					} else {
						pixel.color = "rgb(0,0,0)"
					}
				}
			},
			tempHigh: 800,
		},

		elements.molten_polka_dotted_powder = {
			color: ["#ff7f00","#ff7f00","#ff9f00","#ffbf00","#ffbf00"],
			density: 1100,
			tick: function(pixel) {
				if(pixel.y % 6 == 0) {
					if(pixel.x % 6 == 0) {
						pixel.color = "rgb(255,191,0)"
					} else {
						if(!settings.bg || settings.bg == "#ff7f00") {
							pixel.color = "rgb(255,143,16)"
						} else {
							pixel.color = "rgb(255,127,16)"
						}
					} 
				} else if((pixel.y + 3) % 6 == 0) {
					if((pixel.x + 3) % 6 == 0) {
						pixel.color = "rgb(255,191,0)"
					} else {
						if(!settings.bg || settings.bg == "#ff7f00") {
							pixel.color = "rgb(255,143,16)"
						} else {
							pixel.color = "rgb(255,127,16)"
						}
					} 
				} else {
					if(!settings.bg || settings.bg == "#ff7f00") {
						pixel.color = "rgb(255,143,16)"
					} else {
						pixel.color = "rgb(255,127,16)"
					}
				}
			},
			temp: 850,
			tempLow: 800,
			stateLow: "polka_dotted_powder",
			tempHigh: 2000,
			stateHigh: "vaporized_polka_dotted_powder",
			viscosity: 6,
			hidden: true,
		},

		elements.vaporized_polka_dotted_powder = {
			color: ["#ffdf7f","#ffdf7f","#ffefbf","#ffffff","#ffffff"],
			behavior: behaviors.GAS,
			category: "gases",
			state: "gas",
			density: 550,
			tick: function(pixel) {
				if(pixel.y % 6 == 0) {
					if(pixel.x % 6 == 0) {
						pixel.color = "rgb(255,255,255)"
					} else {
						if(!settings.bg || settings.bg == "#ffdf7f") {
							pixel.color = "rgb(255,233,137)"
						} else {
							pixel.color = "rgb(255,223,127)"
						}
					} 
				} else if((pixel.y + 3) % 6 == 0) {
					if((pixel.x + 3) % 6 == 0) {
						pixel.color = "rgb(255,255,255)"
					} else {
						if(!settings.bg || settings.bg == "#ffdf7f") {
							pixel.color = "rgb(255,143,16)"
						} else {
							pixel.color = "rgb(255,233,137)"
						}
					} 
				} else {
					if(!settings.bg || settings.bg == "#ffdf7f") {
						pixel.color = "rgb(255,233,137)"
					} else {
						pixel.color = "rgb(255,223,127)"
					}
				}
			},
			temp: 2200,
			tempLow: 2000,
			stateLow: "molten_polka_dotted_powder",
			tempHigh: 8000,
			stateHigh: "ionized_polka_dotted_powder",
			hidden: true,
		},

		elements.ionized_polka_dotted_powder = {
			color: ["#fffff0","#fffff0","#fffff7","#ffffff","#ffffff"],
			behavior: [
				"M2 AND CR:plasma%0.3|M1|M2 AND CR:plasma%0.3",
				"M1|XX|M1",
				"M2 AND CR:plasma%0.3|M1|M2 AND CR:plasma%0.3",
			],
			category: "gases",
			state: "gas",
			density: 0.02,
			tick: function(pixel) {
				if(pixel.y % 6 == 0) {
					if(pixel.x % 6 == 0) {
						pixel.color = "rgb(255,255,255)"
					} else {
						if(!settings.bg || settings.bg == "#fffff0") {
							pixel.color = "rgb(255,255,247)"
						} else {
							pixel.color = "rgb(255,255,240)"
						}
					} 
				} else if((pixel.y + 3) % 6 == 0) {
					if((pixel.x + 3) % 6 == 0) {
						pixel.color = "rgb(255,255,255)"
					} else {
						if(!settings.bg || settings.bg == "#fffff0") {
							pixel.color = "rgb(255,255,247)"
						} else {
							pixel.color = "rgb(255,255,240)"
						}
					} 
				} else {
					if(!settings.bg || settings.bg == "#fffff0") {
						pixel.color = "rgb(255,255,247)"
					} else {
						pixel.color = "rgb(255,255,240)"
					}
				}
			},
			temp: 8500,
			tempLow: 8000,
			stateLow: "vaporized_polka_dotted_powder",
			hidden: true,
		},

		elements.hdet = {
			name: "heat- dependent explosion text",
			color: "#33aa44",
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(pixel.charge > 0) {
					var temp = pixel.temp
					if(temp < 0) {
						temp = 0
					}
					if(temp >= 0 && temp < 1) {
						temp = 1
					}
					if(temp > 56000) {
						temp = 56000
					}
					if(isNaN(temp) || isNaN(pixel.temp)) {
						temp = 20
						pixel.temp = 20
					}
					var r = ((Math.sqrt((Math.log(temp)/Math.log(20)))*(temp**0.5))/(6000**0.126284318))/2
					explodeAt(pixel.x,pixel.y,Math.floor(r))
					if(temp > 200) {
						if(Math.random() < (Math.log(temp)/Math.log(56000))**9) {
							pixel.charge = 1
							if(pixel.chargeCD) {
								delete pixel.chargeCD
							}
						}
					}
					if(isNaN(temp) || isNaN(pixel.temp)) {
						temp = 20
						pixel.temp = 20
					}
				}
			},
			density: 1200,
			conduct: 0.5,
			state: "solid",
			category: "special"
		},

		function randInt(max) {
		   return Math.floor(Math.random() * (max + 1))
		}

		function randIntR(min,max) {
			if(min > max) {
				var temp = max; //the need of a temporary space has always annoyed me
				max = min;
				min = temp;
			};
			return Math.floor(Math.random() * (max - min + 1)) + min
		};

		elements.lower_color_copy = {
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(!isEmpty(pixel.x,pixel.y+1,true)) {
					pixel.color = pixelMap[pixel.x][pixel.y+1].color;
				} else {
					if(settings.bg) {
						pixel.color = settings.bg;
					} else {
						pixel.color = "#000000";
					}
				}
			},
			color: ["#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#000000", "#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#007FFF", "#0000FF", "#7F00FF"],
			density: 1250,
			breakInto: ["metal_scrap", "glass_shard"],
			hardness: 0.7,
		}

		elements.brimstone_slag = {
			color: ["#745B57","#534D4A","#463F53","#51113E","#6D283B","#BC4949","#EA9B4E"],
			properties: {
				needsOffset: true
			},
			colorPattern: [
				"FGGFGFGGF",
				"FFGFGFFGG",
				"DEEDEEDEE",
				"DEDEEEEED",
				"BDCBACDCB",
				"BCADCDDBB",
				"ABBCAABCC"
			],
			colorKey: {
				"A": "#745B57",
				"B": "#534D4A",
				"C": "#463F53",
				"D": "#51113E",
				"E": "#6D283B",
				"F": "#BC4949",
				"G": "#EA9B4E",
			},
			behavior: behaviors.POWDER,
			hardness: 0.5,
			enableOffsetsOnTextureColors: true,
			breakInto: ["slag","sulfur"],
			tempHigh: 1780,
			state: "solid",
			category: "solids",
			tick: function(pixel) {
				if(pixel.needsOffset) {
					var offset = (elements[pixel.element].maxColorOffset ?? 15);
					offset = randomIntegerFromZeroToValue(offset) * (Math.random() < 0.5 ? -1 : 1);
					pixel.color = convertColorFormats(pixel.color,"json");
					for(var k in pixel.color) { pixel.color[k] += offset };
					pixel.color = convertColorFormats(pixel.color,"rgb");
					delete pixel.needsOffset;
					return
				}
			}
		};

		elements.molten_slag ??= {};
		elements.molten_slag.reactions ??= {};

		elements.molten_slag.reactions.sulfur = elements.molten_slag.reactions.molten_sulfur = elements.molten_slag.reactions.sulfur_gas = elements.molten_sulfur.reactions.slag = elements.sulfur.reactions.molten_slag = { elem1: "brimstone_slag", elem2: null };
		
		elements.slag.tempHigh = 1780;

		var temp = "firesea,lektre,concoction,mistake,unstable_mistake,toxic_mistake".split(",");
		for(var i = 0; i < temp.length; i++) {
			temp[i].state = "liquid";
			temp[i].category = "liquids"
		};

		
		elements.head.cutInto = ["bone","meat","blood"];
		elements.body.cutInto = ["bone","meat","meat","blood","blood"];
		elements.wood.cutInto = ["wood_plank","wood_plank","wood_plank","wood_plank","wood_plank","wood_plank","wood_plank","wood_plank","sawdust"];
		elements.fish.breakInto = ["meat","meat","bone","blood"];
		elements.fish.cutInto = ["meat","meat","bone","blood"];

		elements.bladesea = {
			color: ["#959696", "#b1b3b3", "#d4d4d4", "#bfbdbd"],
			state: "liquid",
			viscosity: 5,
			behavior: behaviors.LIQUID,
			tick: function(pixel) { //Code from R74n/vanilla "smash" tool
				var pX = pixel.x;
				var pY = pixel.y;
				for(i = 0; i < adjacentCoords.length; i++) {
					var oX = adjacentCoords[i][0];
					var oY = adjacentCoords[i][1];
					var fX = pX+oX;
					var fY = pY+oY;
					if(!isEmpty(fX,fY,true)) {
						var checkPixel = pixelMap[fX][fY];
						var otherElement = elements[checkPixel.element];
						if (typeof(otherElement.cutInto) !== "undefined") {
							var hardness = otherElement.hardness ?? 0;
							if (Math.random() < (1 - hardness)) {
								var cutInto = otherElement.cutInto;
								// if breakInto is an array, pick one
								if (Array.isArray(cutInto)) {
									cutInto = randomChoice(cutInto);
								};
								changePixel(checkPixel,cutInto);
							}
						}
					};
				};
			},
			density: 200,
			category: "liquids",
			hidden: true,
			reactions: {
				"concoction": { "elem1": "bladesea", "elem2": "bladesea", "chance":0.005},
			},
		};

	function newLegacyFnmDye(colorName,hexColor) {
		if(!(hexColor.startsWith("#"))) { hexColor = "#" + hexColor };
		colorName = colorName.toLowerCase();
		var key = `${colorName}_dye`;
		var name = `${colorName.replaceAll("_"," ")} dye`;
		var pixelColor = changeLuminance(hexColor,0.73333333333333,"multiply","hex",null,false);
		
		elements[key] = {
			"name": name,
			"color": pixelColor,
			"state": "solid",
			"behavior": [
				"XX|XX|XX",
				`CC:${hexColor}|CC:${pixelColor}|CC:${hexColor}`,
				`M2 AND CC:${hexColor}|M1 AND CC:${hexColor}|M2 AND CC:${hexColor}`
			],
			"density": 100,
			"category": "dyes"
		};
		eLists.DYE.push(key);		
		elements.concoction.reactions[key] = { "elem1": "mistake", "elem2": null };
		return elements[key]
	};

	var dyeColors = [
		["rose", "#FF0067"],
		["orange", "#FF7F00"],
		["pink", "#FF7FFF"],
		["purple", "#C700CF"],
		["burgundy", "#9F005F"],
		["peach", "#ffbf7f"],
		["mint", "#4df0a9"],
		["gray", "#7F7F7F"],
		["lime", "#7FFF00"],
		["black", "#000000"],
		["white", "#FFFFFF"],
		["sky_blue", "#99d1f2"]
	];
	
	for(var i = 0; i < dyeColors.length; i++) {
		newLegacyFnmDye(dyeColors[i][0],dyeColors[i][1])
	};

	eLists.LED = ["led_r","led_g","led_b"];
	
	function newLED(abbrev,hexColor,baseColorOverrideHex=null) {
		if(!(hexColor.startsWith("#"))) { hexColor = "#" + hexColor };
		if(baseColorOverrideHex && !(baseColorOverrideHex.startsWith("#"))) { baseColorOverrideHex = "#" + baseColorOverrideHex };
		abbrev = abbrev.toLowerCase();
		var key = `led_${abbrev}`;
		var pixelColor = baseColorOverrideHex ?? changeLuminance(hexColor,0x66/0xff,"multiply","hex",null,false);
		
		elements[key] = {
			behavior: behaviors.WALL,
			reactions: {
				"light": {"charge1":1},
				"liquid_light": {"charge1":1},
			},
			color: pixelColor,
			colorOn: hexColor,
			category: "machines",
			tempHigh: 1500,
			stateHigh: ["molten_glass","molten_glass","molten_glass","molten_gallium"],
			conduct: 1,
			breakInto: "glass_shard"
		};
		
		eLists.LED.push(key)
	};

	var ledColors = [
		["c", "#00FFFF"], //cyan
		["y", "#FFFF00"], //yellow
		["m", "#FF00FF"], //magenta (cursed)
		["p", "#AB00C2"], //purple (cursed)
		["v", "#7700FF"], //violet
		["w", "#FFFFFF"], //white (cursed)
		["gy", "#7F7F7F"], //gray (more cursed)
		["bl", "#000000", "#2b2b2b"], //black (super cursed)
		["o", "#FF7F00"], //orange
		["a", "#FFBF00"], //amber
		["l", "#7FFF00"], //lime
		["rs", "#FF0067"], //rose (cursed)
		["pk", "#FF7FFF"], //pink (cursed)
		["bg", "#9F005F"], //burgundy (cursed)
		["pc", "#ffbf7f"], //peach
		["mg", "#4df0a9"], //mint green
		["sb", "#99d1f2"] //sky blue (cursed)
	];

	for(var i = 0; i < ledColors.length; i++) {
		newLED(...ledColors[i]);
	};

	for(var i = 0; i < eLists.LED.length; i++) {
		var key = eLists.LED[i];
		elements.malware.reactions[key] = { elem2:eLists.LED, chance:0.01 }
	};

	//ASSORTED RAINBOW VARIANTS ##

		elements.concoction.reactions.diorite_gravel = {
			elem1: "static", elem2: null
		};

		elements.concoction.reactions.static = { //spread
			elem1: "static", elem2: "static"
		};

		elements.concoction.state = "liquid";

		elements.static.reactions ??= {}; elements.static.reactions.concoction = { "elem1": "static", "elem2": "static", "chance":0.005},


		/*function isRed(colorIn) {
			var color = colorToHsl(colorIn,"json");
			var modularHue = color.h % 360;
			if (!((modularHue >= -10 && modularHue <= 10) || modularHue >= 350)) { return false };
			if (color.s < 55) { return false };
			if (color.l < 40 || color.l > 60) { return false };
			return true
		};

		function isWhite(colorIn) {
			var color = colorToHsl(colorIn,"json");
			return color.s <= 15 && color.l >= 85
		};*/

		elements.rainbow.reactions ??= {};
		elements.rainbow.reactions.fire = { elem1: "fireshimmer", chance: 0.1 };
		elements.rainbow.reactions.plasma = { elem1: "plasmashimmer", chance: 0.1 };
		elements.rainbow.insulate = false;
		elements.rainbow.burnInto = "fireshimmer";
		elements.rainbow.burn = 0.1;
		elements.rainbow.burnTime = 150;
		elements.rainbow.burnTempChange = 0.1;
		elements.rainbow.reactions.bleach = { elem1: "pastel_rainbow" };
		elements.rainbow.reactions.ink = { elem1: "dark_rainbow" };
		elements.rainbow.reactions.coal = { elem1: "dark_rainbow" };
		elements.rainbow.reactions.charcoal = { elem1: "dark_rainbow" };
		elements.rainbow.reactions.coal_dust = { elem1: "dark_rainbow" };
		elements.rainbow.reactions.malware = { elem1: "glitchy_rainbow" };
		elements.rainbow.reactions.ruby = { elem1: "rubyshimmer" };
		elements.rainbow.reactions.molten_ruby = { elem1: "rubyshimmer" };
		elements.rainbow.reactions.topaz = { elem1: "topazshimmer" };
		elements.rainbow.reactions.bee = { elem1: "beeshimmer" };
		elements.rainbow.reactions.sap = { elem1: "ambershimmer" };
		elements.rainbow.reactions.amber = { elem1: "ambershimmer" };
		elements.rainbow.reactions.emerald = { elem1: "emeraldshimmer" };
		elements.rainbow.reactions.molten_emerald = { elem1: "emeraldshimmer" };
		elements.rainbow.reactions.cold_fire = { elem1: "iceshimmer" };
		elements.rainbow.reactions.sapphire = { elem1: "sapphireshimmer" };
		elements.rainbow.reactions.molten_sapphire = { elem1: "sapphireshimmer" };
		elements.rainbow.reactions.blue_dust = { elem1: "sapphireshimmer" };
		elements.rainbow.reactions.amethyst = { elem1: "amethystshimmer" };
		elements.rainbow.reactions.molten_amethyst = { elem1: "amethystshimmer" };
		elements.rainbow.reactions.spinel = { elem1: "spinelshimmer" };
		elements.rainbow.reactions.molten_spinel = { elem1: "spinelshimmer" };
		elements.rainbow.reactions.mullite = { elem1: "mulliteshimmer" };
		elements.rainbow.reactions.molten_mullite = { elem1: "mulliteshimmer" };
		elements.rainbow.reactions.dantite = { elem1: "dantiteshimmer" };
		elements.rainbow.reactions.molten_dantite = { elem1: "dantiteshimmer" };
		elements.rainbow.reactions.turquoise_dust = { elem1: "turquoiseshimmer" };
		elements.rainbow.reactions.diabaline = { elem1: "diabalineshimmer" };
		elements.rainbow.reactions.gold_coin = { elem1: "goldshimmer" };
		elements.rainbow.reactions.gold_scrap = { elem1: "goldshimmer" };
		elements.rainbow.reactions.gold = { elem1: "goldshimmer" };
		elements.rainbow.reactions.molten_gold = { elem1: "goldshimmer" };
		elements.rainbow.reactions.onyx = { elem1: "onyxshimmer" };
		elements.rainbow.reactions.opal = { elem1: "opalshimmer" };
		elements.rainbow.reactions.jadeite = { elem1: "jadeshimmer" };
		elements.rainbow.reactions.dirt = { elem1: "earthshimmer" };
		elements.rainbow.reactions.lead_scrap = { elem1: "leadshimmer" };
		elements.rainbow.reactions.lead = { elem1: "leadshimmer" };
		elements.rainbow.reactions.molten_lead = { elem1: "leadshimmer" };
		elements.rainbow.reactions.water = { elem1: "seashimmer" };
		elements.rainbow.reactions.ultramafic_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.intermediate_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.intermediate_felsic_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.felsic_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.rainbow_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.nellish_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.crimson_magma = { elem1: "lavashimmer" };
		elements.rainbow.reactions.uranium = { elem1: "radioshimmer" };
		elements.rainbow.reactions.ash = { elem1: "ashenshimmer" };
		elements.rainbow.reactions.steel_scrap = { elem1: "steelshimmer" };
		elements.rainbow.reactions.molten_steel = { elem1: "steelshimmer" };
		elements.rainbow.reactions.steel = { elem1: "steelshimmer" };
		elements.rainbow.reactions.iron_scrap = { elem1: "steelshimmer" };
		elements.rainbow.reactions.molten_iron = { elem1: "steelshimmer" };
		elements.rainbow.reactions.iron = { elem1: "steelshimmer" };
		elements.rainbow.reactions.smoke = { elem1: "smokeshimmer" };
		elements.rainbow.reactions.smog = { elem1: "smokeshimmer" };
		elements.rainbow.reactions.cloud = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.fog = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.steam = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.rain_cloud = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.snow_cloud = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.hail_cloud = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.thunder_cloud = { elem1: "cloudshimmer" };
		elements.rainbow.reactions.fluorine = { elem1: "fluoroshimmer" };
		elements.rainbow.reactions.liquid_fluorine = { elem1: "fluoroshimmer" };
		elements.rainbow.reactions.fluorine_ice = { elem1: "fluoroshimmer" };
		elements.rainbow.reactions.chlorine = { elem1: "chloroshimmer" };
		elements.rainbow.reactions.liquid_chlorine = { elem1: "chloroshimmer" };
		elements.rainbow.reactions.chlorine_ice = { elem1: "chloroshimmer" };
		elements.rainbow.reactions.chlorine_snow = { elem1: "chloroshimmer" };
		elements.rainbow.reactions.bromine = { elem1: "bromoshimmer" };
		elements.rainbow.reactions.liquid_bromine = { elem1: "bromoshimmer" };
		elements.rainbow.reactions.bromine_snow = { elem1: "bromoshimmer" };
		elements.rainbow.reactions.bromine_ice = { elem1: "bromoshimmer" };
		elements.rainbow.reactions.iodine = { elem1: "iodoshimmer" };
		elements.rainbow.reactions.molten_iodine = { elem1: "iodoshimmer" };
		elements.rainbow.reactions.iodine_gas = { elem1: "iodoshimmer" };
		runAfterLoad(function() { elements.astatine.tempHigh = 302 });
		elements.molten_astatine ??= {}; elements.molten_astatine.tempHigh = 337;
		elements.rainbow.reactions.astatine = { elem1: "astatoshimmer" };
		elements.rainbow.reactions.molten_astatine = { elem1: "astatoshimmer" };
		elements.rainbow.reactions.astatine_gas = { elem1: "astatoshimmer" };
		elements.rainbow.reactions.blood = { elem1: "bloodshimmer" };
		elements.rainbow.reactions.blood_snow = { elem1: "bloodshimmer" };
		elements.rainbow.reactions.blood_ice = { elem1: "bloodshimmer" };
		elements.rainbow.reactions.salt = { elem1: "saltshimmer" };
		elements.rainbow.reactions.molten_salt = { elem1: "saltshimmer" };
		elements.rainbow.reactions.sand = { elem1: "sandshimmer" };
		elements.rainbow.reactions.hot_sand = { elem1: "sandshimmer" };
		elements.rainbow.reactions.sandy_water = { elem1: "sandshimmer" };
		elements.rainbow.reactions.sand_sediment = { elem1: "sandshimmer" };
		elements.rainbow.reactions.sandstone = { elem1: "sandshimmer" };
		elements.rainbow.reactions.ichor = { elem1: "ichorshimmer" };
		elements.rainbow.reactions.quark_matter = { elem1: "quarkshimmer" };
		elements.rainbow.reactions.heejinite = { elem1: "heejinshimmer" };
		elements.rainbow.reactions.heejinite_powder = { elem1: "heejinshimmer" };
		elements.rainbow.reactions.molten_heejinite = { elem1: "heejinshimmer" };
		elements.rainbow.reactions.heejinite_gas = { elem1: "heejinshimmer" };

		/*elements.rainbow.reactions.dye = {
			func: function(pixel,otherPixel) {
				//The dye reactant is the otherPixel
				//The rainbow is the pixel
				if(
					(typeof(otherPixel) == "undefined")
					|| isEmpty(otherPixel.x,otherPixel.y,true)
					|| typeof(pixelMap[otherPixel.x][otherPixel.y]) == "undefined"
					|| !(currentPixels.includes(pixelMap[otherPixel.x][otherPixel.y]))
				) { return };
				var dyeColorHSL = colorToHsl(otherPixel.color,"json");
				if(isRed(dyeColorHSL)) {
					changePixel(pixel,"red_test");
				} else if(isWhite(dyeColorHSL)) {
					changePixel(pixel,"rainbow_alt_test");
				};
				return true
			}
		};*/

		elements.rainbow.tick = function(pixel) {
			var dyeColor = pixel.dyeColor ?? null;
			var t = pixelTicks+pixel.x+pixel.y;
			var r = Math.floor(127*(1-Math.cos(t*Math.PI/90)));
			var g = Math.floor(127*(1-Math.cos(t*Math.PI/90+2*Math.PI/3)));
			var b = Math.floor(127*(1-Math.cos(t*Math.PI/90+4*Math.PI/3)));
			var baseColor = "rgb("+r+","+g+","+b+")";
			if(!dyeColor) {
				pixel.color = baseColor
			} else {
				var baseJSON = convertColorFormats(baseColor,"json");
				var dyeJSON = convertColorFormats(dyeColor,"json");
				var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
				//80% multiplied
				var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.8);
				//30% dye color, 70% result
				var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.7);
				pixel.color = convertColorFormats(finalColor,"rgb")
			}
		};

		elements.rainbow.state = "solid";
		elements.rainbow.reactions.dye = {
			func: function(pixel,otherPixel) {
				//The dye reactant is the otherPixel
				//The rainbow is the pixel
				if(
					(typeof(otherPixel) == "undefined")
					|| isEmpty(otherPixel.x,otherPixel.y,true)
					|| typeof(pixelMap[otherPixel.x][otherPixel.y]) == "undefined"
					|| !(currentPixels.includes(pixelMap[otherPixel.x][otherPixel.y]))
				) { return false };
				if(pixel.dyeColor == otherPixel.color) {
					return false
				} else {
					pixel.dyeColor = otherPixel.color;
					return true
				}
			}
		};

		rainbowMathlet = function(t,scale,offset) { return Math.cos(t*Math.PI/scale+offset*Math.PI/3) };

		elements.dark_rainbow = {
			color: ["#000000","#ff0000","#000000","#ff8800","#000000","#ffff00","#000000","#00ff00","#000000","#00ffff","#000000","#0000ff","#000000","#ff00ff"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks+pixel.x+pixel.y;
				var l = 50 * (Math.max(0,rainbowMathlet(t,17.1428571,0)) ** 4);
				var h = (((t * 1.05) - 2) % 360);
				var color = {h: h, s: 100, l: l};
				var baseColor = convertHslObjects(color,"rgb");
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//80% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.8);
					//30% dye color, 70% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.7);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.fireshimmer = {
			color: ["#ff0000","#ff8800","#ffff00","#ff8800","#ff0000"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var g = Math.floor(127*((Math.max(0,1-(rainbowMathlet(t,25,0)))) ** 1.1));
				baseColor = "rgb(255,"+g+",0)";
				if(!dyeColor) { 
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			category: "special",
			reactions: {
				dye: elements.rainbow.reactions.dye,
				plasma: {elem1: "plasmashimmer", tempMin: 10000}
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
			movable: false,
		};

		elements.plasmashimmer = {
			color: ["#8800ff","#f2f2f2","#8800ff","#f2f2f2"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var value = Math.floor(127*((Math.max(0,1-(rainbowMathlet(t,25,0)))) ** 1.1));
				baseColor = "rgb(" + [Math.round(127 + (value/2)), value, 255].join(",") + ")";
				if(!dyeColor) { 
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			category: "special",
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
			movable: false,
		};

		elements.glitchy_rainbow = {
			color: ["#ff0000","#ff8800","#ffff00","#ff8800","#ff0000"],
			tick: function(pixel) {
				if(Math.random() < 0.25) { return };
				var dyeColor = pixel.dyeColor ?? null;
				var t = (
					(Math.floor(pixelTicks / 3) * 3)
					+ (Math.floor(pixel.x / 3) * 3)
					+ pixel.y
					+ (Math.floor(Math.random() * 5)-2)
				);
				var r = Math.floor(127*(1-Math.cos(t*Math.PI/90)));
				var g = Math.floor(127*(1-Math.cos(t*Math.PI/90+2*Math.PI/3)));
				var b = Math.floor(127*(1-Math.cos(t*Math.PI/90+4*Math.PI/3)));
				var baseColor = Math.random() < 0.02 ? [g,r,b] : [r,g,b];
				baseColor = "rgb("+baseColor.join(",")+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//80% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.8);
					//30% dye color, 70% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.7);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			category: "special",
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
			movable: false,
		};

		elements.rainbow.behavior = behaviors.WALL;

		elements.dye.ignore ??= [];

		elements.pastel_rainbow = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/240)));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/240+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/240+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye,
				dust: { elem1: "pastel_rainbow_small", elem2: null },
				flameshockwave4: { elem1: "pastel_rainbow_x", elem2: null }, //fs4 = left, fs5 = right
				flameshockwave5: { elem1: "pastel_rainbow_x", elem2: null },
				flameshockwave2: { elem1: "pastel_rainbow_y", elem2: null }, //fs2 = up, fs7 = down
				flameshockwave7: { elem1: "pastel_rainbow_y", elem2: null },
				flameshockwave1: { elem1: "pastel_rainbow_d", elem2: null }, //fs1 ul, fs3 ur, fs6 dl, fs8 dr
				flameshockwave3: { elem1: "pastel_rainbow_d", elem2: null },
				flameshockwave6: { elem1: "pastel_rainbow_d", elem2: null },
				flameshockwave8: { elem1: "pastel_rainbow_d", elem2: null },
				slime: { elem1: "pastel_rainbow_t_d_x", elem2: null },
				feather: { elem1: "pastel_rainbow_t_d_y", elem2: null },
				glue: { elem1: "pastel_rainbow_x_p_y", elem2: null },
				plasma: { elem1: "pastel_rainbow_x_m_y", elem2: null },
				liquid_light: { elem1: "pastel_rainbow_x_t_y", elem2: null },
				tectonic_petrotheum: { elem1: "pastel_rainbow_x_d_y", elem2: null },
				seed: { elem1: "pastel_rainbow_x_e_y", elem2: null },
				dirt: { elem1: "pastel_rainbow_x_r_y", elem2: null },
				sawdust: { elem1: "pastel_rainbow_x_l_y", elem2: null },
				water: { elem1: "pastel_rainbow_s_x", elem2: null },
				magma: { elem1: "pastel_rainbow_c_x", elem2: null },
				gelid_cryotheum: { elem1: "pastel_rainbow_t_x", elem2: null },
				steam: { elem1: "pastel_rainbow_sh_x", elem2: null },
				vaporized_magma: { elem1: "pastel_rainbow_ch_x", elem2: null },
				blazing_pyrotheum: { elem1: "pastel_rainbow_th_x", elem2: null },
				rock: { elem1: "pastel_rainbow_sr_x", elem2: null },
				polka_dotted_powder: { elem1: "pastel_rainbow_x_mod_y", elem2: null }
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.rubyshimmer = {
			color: ["#ff0000","#200000","#ff0000","#200000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				pixel.color = "rgb("+Math.ceil((r*(7/8))+32)+","+0+","+0+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.topazshimmer = {
			color: ["#ffff00","#202000","#ffff00","#202000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+value+",0)";
				doHeat(pixel);
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.beeshimmer = {
			color: ["#ffff00","#202000","#ffff00","#202000","#ffff00","#202000","#ffff00","#202000"],
			tick: function(pixel) {
				var t = pixelTicks*1.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/4)));
				var value = Math.ceil(r/2);
				pixel.color = "rgb("+value+","+value+",0)";
				doHeat(pixel);
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.ambershimmer = {
			color: ["#ff7f00","#201000","#ff7f00","#201000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+Math.round(value*0.5)+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.emeraldshimmer = {
			color: ["#00ff00","#002000","#00ff00","#002000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb(0,"+value+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.iceshimmer = {
			color: ["#00ffff","#001520","#00ffff","#001520"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb(0,"+Math.round(value * 0.75)+","+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.sapphireshimmer = {
			color: ["#0000ff","#000020","#0000ff","#000020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb(0,0,"+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.amethystshimmer = {
			color: ["#7f00ff","#100020","#7f00ff","#100020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value/2)+",0,"+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.spinelshimmer = {
			color: ["#ff00ff","#200020","#ff00ff","#200020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+",0,"+Math.round(value * 0.5)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.mulliteshimmer = {
			color: ["#f8eeff","#151020","#f8eeff","#151020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var l = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((l*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.9)+","+Math.round(value * 0.75)+","+Math.round(value*0.85)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.dantiteshimmer = {
			color: ["#00ff7f","#002010","#00ff7f","#002010"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var l = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((l*(7/8))+32);
				pixel.color = "rgb(0,"+value+","+Math.round(value*0.5)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
			nellfireImmune: true
		};

		elements.turquoiseshimmer = {
			color: ["#00ffff","#002020","#00ffff","#002020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var l = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((l*(7/8))+32);
				pixel.color = "rgb(0,"+value+","+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
			nellfireImmune: true
		};

		elements.diabalineshimmer = {
			color: ["#af7fcf","#141018","#af7fcf","#141018"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.625)+","+Math.round(value*0.5)+","+Math.round(value*0.75)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.goldshimmer = {
			color: ["#ffcf00","#201800","#ffcf00","#201800"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+Math.round(value*0.75)+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.leadshimmer = {
			color: ["#2f2f4f","#040410","#2f2f4f","#040410"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.3)+","+Math.round(value*0.3)+","+Math.round(value*0.5)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.onyxshimmer = {
			color: ["#1f1f1f","#040404","#1f1f1f","#040404"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+16);
				pixel.color = "rgb("+Math.round(value*0.125)+","+Math.round(value*0.125)+","+Math.round(value*0.125)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.opalshimmer = {
			color: function() { var rc = elements.rainbow.color; var rc2 = rc.map(x => lightenColor(x,127,"hex")); return rc2.concat(rc2) }(),
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t1 = pixelTicks+pixel.x+pixel.y;
				var t2 = pixelTicks+pixel.x-pixel.y;
				var scale = 20;
				var r1 = Math.floor(127*(1-Math.cos(t1*Math.PI/scale)));
				var g1 = Math.floor(127*(1-Math.cos(t1*Math.PI/scale+2*Math.PI/3)));
				var b1 = Math.floor(127*(1-Math.cos(t1*Math.PI/scale+4*Math.PI/3)));
				var r2 = Math.floor(127*(1-Math.cos(t2*Math.PI/scale)));
				var g2 = Math.floor(127*(1-Math.cos(t2*Math.PI/scale+2*Math.PI/3)));
				var b2 = Math.floor(127*(1-Math.cos(t2*Math.PI/scale+4*Math.PI/3)));
				var r3 = (r1+r2)*0.75;
				var g3 = (g1+g2)*0.75;
				var b3 = (b1+b2)*0.75;
				var baseColor = {r: r3, g: g3, b: b3};
				baseColor = averageColorObjects(baseColor,whiteColor,0.8);
				baseColor = convertColorFormats(baseColor,"rgb");
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//80% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.8);
					//30% dye color, 70% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.7);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye,
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.jadeshimmer = {
			color: ["#5f8f2f","#0c1206","#5f8f2f","#0c1206"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				pixel.color = "rgb("+Math.ceil((r*(4/16))+32)+","+Math.ceil((r*(8/16))+32)+","+Math.ceil((r*(1/8))+32)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.earthshimmer = {
			color: ["#5f3f00","#0c0800","#5f3f00","#0c0800"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.375)+","+Math.round(value*0.2)+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.seashimmer = {
			color: ["#007fff","#001020","#007fff","#001020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb(0,"+Math.round(value*0.5)+","+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.lavashimmer = {
			color: ["#ff3f00","#200800","#ff3f00","#200800"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+Math.round(value*0.25)+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.radioshimmer = { //if it captures and renders harmless fire+crimmagma+lava then it can provide radiation shielding
			color: ["#7fff00","#102000","#7fff00","#102000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.5)+","+value+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.ashenshimmer = {
			color: ["#cfcfcf","#181818","#cfcfcf","#181818"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.75)+","+Math.round(value*0.75)+","+Math.round(value*0.75)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.steelshimmer = {
			color: ["#7f7f7f","#101010","#7f7f7f","#101010"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.5)+","+Math.round(value*0.5)+","+Math.round(value*0.5)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.smokeshimmer = {
			color: ["#3f3f3f","#050505","#3f3f3f","#050505"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.25)+","+Math.round(value*0.25)+","+Math.round(value*0.25)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.cloudshimmer = {
			color: ["#dfefff","#1d1e20","#dfefff","#1d1e20"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+48);
				pixel.color = "rgb("+Math.round(value*0.8)+","+Math.round(value*0.9)+","+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.fluoroshimmer = {
			color: ["#5f6f2f","#081000","#5f6f2f","#081000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.375)+","+Math.round(value*0.4375)+","+Math.round(value*0.1875)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.chloroshimmer = {
			color: ["#3f7f00","#081000","#3f7f00","#081000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.25)+","+Math.round(value*0.5)+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.bromoshimmer = {
			color: ["#7f1f00","#100400","#7f1f00","#100400"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.5)+","+Math.round(value*0.125)+",0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.iodoshimmer = {
			color: ["#5f007f","#0c0010","#5f007f","#0c0010"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.375)+",0,"+Math.round(value*0.5)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.astatoshimmer = {
			color: ["#230f27","#050205","#230f27","#050205"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.137)+","+Math.round(value*0.059)+","+Math.round(value*0.153)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.bloodshimmer = {
			color: ["#7f0000","#100000","#7f0000","#100000"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+Math.round(value*0.5)+",0,0)";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.saltshimmer = {
			color: ["#ffffff","#202020","#ffffff","#202020"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+value+","+value+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.sandshimmer = {
			color: ["#ffdf7f","#201d10","#ffdf7f","#201d10"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+Math.round(value*0.8)+","+Math.round(value*0.5)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.ichorshimmer = {
			color: ["#ffbf5f","#201804","#ffbf5f","#201804"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				var value = Math.ceil((r*(7/8))+32);
				pixel.color = "rgb("+value+","+Math.round(value*0.75)+","+Math.round(value*0.375)+")";
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.quarkshimmer = {
			color: ["#ff0000","#00ff00","#0000ff","#ff0000","#00ff00","#0000ff"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(127*(((Math.max(0,1-(rainbowMathlet(t,20,0))) / 1.575) ** 5) * 1.575));
				var g = Math.floor(127*(((Math.max(0,1-(rainbowMathlet(t,20,2))) / 1.575) ** 5) * 1.575));
				var b = Math.floor(127*(((Math.max(0,1-(rainbowMathlet(t,20,4))) / 1.575) ** 5) * 1.575));
				baseColor = "rgb("+([r,g,b].join(","))+")";
				if(!dyeColor) { 
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			category: "special",
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
			movable: false,
		};

		elements.heejinshimmer = {
			color: ["#ff007f","#200010","#ff007f","#200010"],
			tick: function(pixel) {
				var t = pixelTicks*2.5+pixel.x+pixel.y;
				var t2 = pixelTicks/2;
				//var r = Math.floor(255*(1-Math.cos(t*Math.PI/24)));
				//var value = Math.ceil((r*(7/8))+32);
				var l = Math.floor(40*(1-Math.cos(t*Math.PI/24)));
				var h = (320 + Math.floor(30*(Math.cos(t2*Math.PI/24)))) % 360;
				//pixel.color = "rgb("+value+",0,"+Math.round(value * 0.5)+")";
				pixel.color = convertHslObjects({h:h,s:100,l:l},"rgb");
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants",
		};

		elements.pastel_rainbow_small = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/60)));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/60+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/60+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_t_d_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (time/x)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixelTicks/pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixelTicks/pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixelTicks/pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_t_d_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (time/y)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixelTicks/pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixelTicks/pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixelTicks/pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x distance)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/pixel.x)));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/pixel.x+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/pixel.x+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye,
				polka_dotted_powder: { elem1: "pastel_rainbow_d_mod_x", elem2: null }
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (y distance)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/pixel.y)));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/pixel.y+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/pixel.y+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye,
				polka_dotted_powder: { elem1: "pastel_rainbow_d_mod_y", elem2: null }
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_d = {
			name: "pastel rainbow (diagonal distance)",
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2)))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye,
				plasma: { elem1: "pastel_rainbow_d_m_x", elem2: null },
				liquid_light: { elem1: "pastel_rainbow_d_t_x", elem2: null }
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_p_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x+y)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x+pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x+pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x+pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_m_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x-y)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x-pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x-pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x-pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_t_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x*y)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x*pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x*pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x*pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_d_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x/y)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x/pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x/pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x/pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye,
				flameshockwave1: { elem1: "pastel_rainbow_x_d_d", elem2: null },
				flameshockwave3: { elem1: "pastel_rainbow_x_d_d", elem2: null },
				flameshockwave6: { elem1: "pastel_rainbow_x_d_d", elem2: null },
				flameshockwave8: { elem1: "pastel_rainbow_x_d_d", elem2: null }
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_e_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x^y)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x**pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x**pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x**pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_r_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (ʸ√x)",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.pow(pixel.x, 1/pixel.y)))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.pow(pixel.x, 1/pixel.y))+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.pow(pixel.x, 1/pixel.y))+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_l_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (logᵧ(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.log(pixel.x) / Math.log(pixel.y)))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.log(pixel.x) / Math.log(pixel.y))+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.log(pixel.x) / Math.log(pixel.y))+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_s_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (sin(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sin(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sin(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sin(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_c_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (cos(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.cos(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.cos(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.cos(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_t_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (tan(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.tan(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.tan(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.tan(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_sh_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (sinh(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sinh(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sinh(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sinh(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_ch_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (cosh(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.cosh(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.cosh(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.cosh(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_th_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (tanh(x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.tanh(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.tanh(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.tanh(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_sr_x = {
			name: "pastel rainbow (√x))",
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sqrt(pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sqrt(pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/Math.sqrt(pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_d_m_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (diagonal distance - x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))-pixel.x)));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))-pixel.x+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))-pixel.x+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_d_t_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (diagonal distance * x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))*pixel.x)));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))*pixel.x+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(Math.sqrt(pixel.x**2+pixel.y**2))*pixel.x+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_d_d = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x / diagonal distance))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x/(Math.sqrt(pixel.x**2+pixel.y**2))))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x/(Math.sqrt(pixel.x**2+pixel.y**2)))+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x/(Math.sqrt(pixel.x**2+pixel.y**2)))+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_x_mod_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (x % y))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x%pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x%pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/(pixel.x%pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_d_mod_x = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (diagonal distance modulo x))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/((Math.sqrt(pixel.x**2+pixel.y**2))%pixel.x))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/((Math.sqrt(pixel.x**2+pixel.y**2))%pixel.x)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/((Math.sqrt(pixel.x**2+pixel.y**2))%pixel.x)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

		elements.pastel_rainbow_d_mod_y = {
			color: ["#ffaacc","#ffaacc","#aaccff","#aaccff","#ffffbb","#ffffbb"],
			name: "pastel rainbow (diagonal distance modulo y))",
			tick: function(pixel) {
				var dyeColor = pixel.dyeColor ?? null;
				var t = pixelTicks*3+pixel.x+pixel.y;
				var r = Math.floor(255*(1-Math.cos(t*Math.PI/((Math.sqrt(pixel.x**2+pixel.y**2))%pixel.y))));
				var g = Math.floor(255*(1-Math.cos(t*Math.PI/((Math.sqrt(pixel.x**2+pixel.y**2))%pixel.y)+2*Math.PI/3)));
				var b = Math.floor(255*(1-Math.cos(t*Math.PI/((Math.sqrt(pixel.x**2+pixel.y**2))%pixel.y)+4*Math.PI/3)));
				var baseColor = "rgb("+Math.ceil((r/2)+127)+","+Math.ceil((g/2)+127)+","+Math.ceil((b/2)+127)+")";
				if(!dyeColor) {
					pixel.color = baseColor
				} else {
					var baseJSON = convertColorFormats(baseColor,"json");
					var dyeJSON = convertColorFormats(dyeColor,"json");
					var dyedColor = multiplyColors(dyeJSON,baseJSON,"json");
					//70% multiplied
					var semiDyedColor = averageColorObjects(dyedColor,baseJSON,0.7);
					//35% dye color, 65% result
					var finalColor = averageColorObjects(semiDyedColor,dyeJSON,0.65);
					pixel.color = convertColorFormats(finalColor,"rgb")
				}
			},
			reactions: {
				dye: elements.rainbow.reactions.dye
			},
			state: "solid",
			category: "rainbow variants"
		};

	//ASSORTED RANDOMLY GENERATED MATERIALS ##

		elements.fowtim_ice = {
			color: "#6c6dec",
			behavior: behaviors.WALL,
			tempHigh: 9382.73,
			temp: 9362.73,
			stateHigh: "fowtim",
			category: "random solids",
			state: "solid",
			density: 1009.20,
			hardness: 0.54,
			breakInto: "fowtim",
			conduct: 0.14,
		};

		elements.fowtim = {
			color: "#2324e2",
			behavior: behaviors.LIQUID,
			tempLow: 9382.73,
			temp: 9402.73,
			tempHigh: 11187.06,
			stateLow: "fowtim_ice",
			stateHigh: "fowtim_gas",
			category: "random liquids",
			state: "liquid",
			density: 905.92,
			hardness: 0.27,
			viscosity: 547.09,
			breakInto: "fowtim_gas",
			conduct: 0.078,
		};

		elements.fowtim_gas = {
			color: "#b6b6f5",
			behavior: behaviors.GAS,
			tempLow: 11187.06,
			temp: 11207.06,
			stateLow: "fowtim",
			category: "random gases",
			state: "gas",
			density: 1.36,
			hardness: 1,
		};

		elements.shit = {
			color: ["#756674","#554754","#827381"],
			behavior: behaviors.POWDER,
			tempHigh: 975.54,
			category: "random rocks",
			state: "solid",
			density: 2902.23,
			hardness: 0.45,
			breakInto: ["dust","shit_gravel"],
			stateHigh: "molten_shit"
		};

		elements.shit_gravel = {
			color: ["#b9a9b5","#97858d","#6e6068","#57454e"],
			behavior: behaviors.POWDER,
			tempHigh: 975.54,
			stateHigh: "shit",
			category: "random rocks",
			state: "solid",
			density: 1912.06,
			hardness: 0.19,
			breakInto: "dust",
		};

		elements.loona = {
			color: ["#6f7d54","#4f5d34","#7c8a61"],
			behavior: behaviors.POWDER,
			tempHigh: 1031.02,
			category: "random rocks",
			state: "solid",
			density: 2466.73,
			hardness: 0.51,
			breakInto: ["dust","loona_gravel"],
			stateHigh: "molten_loona"
		},
		elements.loona_gravel = {
			color: ["#b3be98","#919a6f","#68744b","#515931"],
			behavior: behaviors.POWDER,
			tempHigh: 1031.02,
			stateHigh: "loona",
			category: "random rocks",
			state: "solid",
			density: 1625.14,
			hardness: 0.20,
			breakInto: "dust",
		};

	//ROSEYIEDE ##

		elements.roseyiede = {
			color: "#686118",
			behavior: behaviors.LIQUID,
			tempHigh: 103,
			stateHigh: "gaseous_roseyiede",
			tempLow: -8,
			stateLow: "solid_roseyiede",
			category: "liquids",
			state: "liquid",
			density: 1000,
		},

		elements.gaseous_roseyiede = {
			color: "#a49e4c",
			behavior: behaviors.GAS,
			tempLow: -8,
			stateLow: "powdered_roseyiede",
			tick: function(pixel) {
			  if((pixelTicks - pixel.start) % 5 == 0) {
				if(pixel.temp < 3 && Math.random() < 0.00135) {
					changePixel(pixel,"roseyiede",false);
				} else if(pixel.temp < 23 && Math.random() < 0.001) {
					changePixel(pixel,"roseyiede",false);
				} else if(pixel.temp < 43 && Math.random() < 0.0007) {
					changePixel(pixel,"roseyiede",false);
				} else if(pixel.temp < 63 && Math.random() < 0.00045) {
					changePixel(pixel,"roseyiede",false);
				} else if(pixel.temp < 83 && Math.random() < 0.00025) {
					changePixel(pixel,"roseyiede",false);
				} else if(pixel.temp < 103 && Math.random() < 0.0001) {
					changePixel(pixel,"roseyiede",false);
				}
			  };
			},
			category: "gases",
			state: "gas",
			density: 0.63,
			temp: 120,
		},

		elements.solid_roseyiede = {
			color: "#685e10",
			behavior: behaviors.WALL,
			tempHigh: 103,
			stateHigh: "gaseous_roseyiede",
			category: "solids",
			state: "solid",
			density: 1121,
			hardness: 0.121,
			breakInto: "powdered_roseyiede",
			temp: -20,
		},

		elements.powdered_roseyiede = {
			color: "#6c641c",
			behavior: behaviors.POWDER,
			tempHigh: -8,
			stateHigh: "roseyiede",
			category: "powders",
			state: "solid",
			density: 182,
			temp: -20,
		}

		//Volatile Roseyiede

		elements.explosive_roseyiede = {
			color: "#986118",
			behavior: behaviors.LIQUID,
			tempHigh: 98,
			stateHigh: "gaseous_explosive_roseyiede",
			tempLow: -6,
			stateLow: "solid_explosive_roseyiede",
			burn: 11,
			burnInto: ["explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","explosive_roseyiede","fire","fire","fire","fire","fire","fire","fire","fire","fire","explosion"],
			burnTime: 312,
			tick: function(pixel) {
			  if((pixelTicks - pixel.start) % 5 == 0) {
				if(pixel.temp < 3 && Math.random() < 0.0001) {
					changePixel(pixel,"explosive_roseyiede_vapor",false)
				} else if(pixel.temp < 23 && Math.random() < 0.0002) {
					changePixel(pixel,"explosive_roseyiede_vapor",false)
				} else if(pixel.temp < 43 && Math.random() < 0.0035) {
					changePixel(pixel,"explosive_roseyiede_vapor",false)
				} else if(pixel.temp < 63 && Math.random() < 0.00055) {
					changePixel(pixel,"explosive_roseyiede_vapor",false)
				} else if(pixel.temp < 83 && Math.random() < 0.0008) {
					changePixel(pixel,"explosive_roseyiede_vapor",false)
				} else if(pixel.temp < 98 && Math.random() < 0.0011) {
					changePixel(pixel,"explosive_roseyiede_vapor",false)
				}
			  }
			},
			category: "liquids",
			state: "liquid",
			density: 1000,
		},

		elements.gaseous_explosive_roseyiede = {
			color: "#c89e4c",
			behavior: behaviors.GAS,
			tempLow: -6,
			stateLow: "powdered_explosive_roseyiede",
			burn: 88,
			burnInto: ["gaseous_explosive_roseyiede","gaseous_explosive_roseyiede","gaseous_explosive_roseyiede","gaseous_explosive_roseyiede","gaseous_explosive_roseyiede","explosion","fire","fire","fire","fire","fire","fire","fire","fire","gaseous_explosive_roseyiede","explosion","fire","fire","fire","explosion"],
			burnTime: 48,
			tick: function(pixel) {
			  if((pixelTicks - pixel.start) % 5 == 0) {
				if(pixel.temp < 3 && Math.random() < 0.00135) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 23 && Math.random() < 0.001) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 43 && Math.random() < 0.0007) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 63 && Math.random() < 0.00045) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 83 && Math.random() < 0.00025) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 98 && Math.random() < 0.0001) {
					changePixel(pixel,"explosive_roseyiede",false);
				}
			  }
			},
			category: "gases",
			state: "gas",
			density: 0.63,
			temp: 120,
		},

		elements.explosive_roseyiede_vapor = {
			color: "#c89449",
			behavior: behaviors.GAS,
			tempHigh: 98,
			stateHigh: "gaseous_explosive_roseyiede",
			tempLow: -6,
			stateLow: "powdered_explosive_roseyiede",
			burn: 88,
			burnInto: ["explosive_roseyiede_vapor","explosive_roseyiede_vapor","explosive_roseyiede_vapor","explosive_roseyiede_vapor","explosive_roseyiede_vapor","explosion","fire","fire","fire","fire","fire","fire","fire","fire","explosive_roseyiede_vapor","fire","fire","fire","fire","explosion"],
			burnTime: 48,
			tick: function(pixel) {
			  if((pixelTicks - pixel.start) % 5 == 0) {
				if(pixel.temp < 3 && Math.random() < 0.0011) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 23 && Math.random() < 0.0008) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 43 && Math.random() < 0.00055) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 63 && Math.random() < 0.00035) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 83 && Math.random() < 0.0002) {
					changePixel(pixel,"explosive_roseyiede",false);
				} else if(pixel.temp < 98 && Math.random() < 0.0001) {
					changePixel(pixel,"explosive_roseyiede",false);
				}
			  }
			},
			category: "gases",
			state: "gas",
			density: 0.63,
			temp: 40,
		},

		elements.solid_explosive_roseyiede = {
			color: "#985e10",
			behavior: behaviors.WALL,
			tempHigh: 98,
			stateHigh: "gaseous_explosive_roseyiede",
			burn: 4,
			burnInto: ["solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","solid_explosive_roseyiede","fire","fire","fire","fire","fire","fire","fire","fire","fire","explosion"],
			burnTime: 662,
			category: "solids",
			state: "solid",
			density: 1121,
			hardness: 0.121,
			breakInto: "powdered_explosive_roseyiede",
			temp: -20,
		},

		elements.powdered_explosive_roseyiede = {
			color: "#98641c",
			behavior: behaviors.POWDER,
			tempHigh: -6,
			stateHigh: "explosive_roseyiede",
			burn: 42,
			burnInto: ["powdered_explosive_roseyiede","powdered_explosive_roseyiede","powdered_explosive_roseyiede","powdered_explosive_roseyiede","powdered_explosive_roseyiede","powdered_explosive_roseyiede","powdered_explosive_roseyiede","powdered_explosive_roseyiede","fire","fire","explosion","fire","fire","fire","fire","fire","fire","fire","fire","explosion"],
			burnTime: 101,
			category: "powders",
			state: "solid",
			density: 182,
			temp: -20,
		},

		elements.boiling_roseyiede = {
			name: "forever- boiling roseyiede",
			color: "#9e942e",
			behavior: [
				"XX|M2%5 AND CR:gaseous_roseyiede%0|XX", //display CR
				"M2|HT:0|M2", //display HT
				"M1|M1|M1",
			],
			reactions: {
				"roseyiede": { elem2: "boiling_roseyiede", chance: 0.006 }
			},
			tick: function(pixel) {
				if((pixelTicks - pixel.start) % 3 == 0) {
					if(pixel.temp > (-206/3)) {
						if(Math.random() < (pixel.temp - 103) / (51500/3) + 0.01) {
							tryCreatePixel("gaseous_roseyiede",pixel.x,pixel.y-1)
						};
					} else if(pixel.temp < 103) {
						pixel.temp += (((pixel.temp - 103) / 100) ** 2) / 2 + 1;
					}
				};
			},
			category: "liquids",
			state: "liquid",
			density: 956,
			temp: 120,
		}

	//MINESWEEPER ##

		msColorArray = ["#a0a0a0", "#0000ff", "#008000", "#ff0000", "#000080", "#800000", "#008080", "#000000", "#808080"]

		elements.msfield = {
			name: "minefield",
			color: "#c0c0c0",
			conduct: 1,
			insulate: true,
			properties: {
				uwu: 0,
				revealed: false,
				revealedAround: false
			},
			tick: function(pixel) {
				pixel.revealed ??= false;
				pixel.uwu ??= 0;
				pixel.revealedAround ??= false;
				if(pixel.charge) {
					if(!pixel.revealed) { pixel.revealed = true };
					delete pixel.charge
					if(pixel.chargeCD) {
						delete pixel.chargeCD
					}
				}
				if(pixel.revealed) {
					//count neighbors
					pixel.uwu = 0
					for (let i = -1; i < 2; i++) {
						for (let j = -1; j < 2; j++) {
							if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
								if (pixelMap[pixel.x+j][pixel.y+i].element == "msmine") {
									pixel.uwu++
								}
							}
						}
					}
					if(typeof(pixel.uwu) === 'number' && isFinite(pixel.uwu) && !isNaN(pixel.uwu)) {
						if(pixel.uwu >= 0 && pixel.uwu <= 8) {
							pixel.color = msColorArray[pixel.uwu];
							pixel.displayText = pixel.uwu.toString()
						}
					} else {
						pixel.color = "#ff00ff"
					}
				} else {
					pixel.color = "#c0c0c0" //I feel bad suppressing the sand effect.
				}
			},
			maxColorOffset: 0,
			category: "special",
			state: "solid",
			hidden: true,
		};

		elements.msmine = {
			name: "minefield",
			color: "#c0c0c0",
			conduct: 1,
			insulate: true,
			properties: {
				uwu: 0,
				revealed: false
			},
			tick: function(pixel) {
				if(pixel.charge) {
					pixel.revealed = true
					delete pixel.charge
					if(pixel.chargeCD) {
						delete pixel.chargeCD
					}
				}
				if(pixel.revealed) {
					pixel.color = ("#" + ((192 + Math.abs((pixelTicks * 4) % 64)).toString(16) + "c0c0").padStart(6, '0'));
					//oldFillStyle = ctx.fillStyle
					//ctx.fillStyle = "#ff0000";
					////ctx.fillRect(pixel.x*pixelSize, pixel.y*pixelSize, pixelSize/2, pixelSize);
					//ctx.fillRect(23*pixelSize, 23*pixelSize, pixelSize/2, pixelSize);
					//ctx.fillStyle = oldFillStyle;
				} else {
					pixel.color = "#c0c0c0"
				}
			},
			category: "special",
			state: "solid",
			hidden: true,
		};

		elements.ms = { //minesweeper = {
			color: ["#c0c0c0", "#c0c0c0", "#ff0000", "#008000", "#ff0000", "#000080", "#800000", "#008080", "#000000", "#808080", "#808080"],
			behavior: [
				"XX|XX|XX",
				"XX|CH:msfield,msfield,msfield,msfield,msfield,msfield,msfield,msfield,msfield,msmine|XX",
				"XX|XX|XX"
			],
			category: "special",
			state: "solid",
		};

	//LIFE-EATER VIRUS ##

		var lifeEaterCategories = ["life","auto creepers","shit","cum","food","fantastic creatures","fey","auto_fey"];
		var lifeEaterBlacklist = ["life_eater_virus","life_eater_slurry","life_eater_infected_dirt"];
		var lifeEaterWhitelist = ["blood","skin","hair","poop","blood_ice","wood","wood_plank","sawdust","straw","paper","birthpool","dried_poop","gloomfly","meat_monster","rotten_ravager","bone_beast","withery","withery_plant","banana","apple","rotten_apple","apioform_player","apioform_bee","apioform","apiodiagoform","sugar_cactus","sugar_cactus_seed","flowering_sugar_cactus","tree_branch","sap","silk","red_velvet","silk_velvet","ketchup", "enchanted_ketchup", "frozen_ketchup", "poisoned_ketchup", "frozen_poisoned_ketchup", "ketchup_spout", "ketchup_cloud", "poisoned_ketchup_cloud", "ketchup_snow", "ketchup_snow_cloud", "poisoned_ketchup_snow", "poisoned_ketchup_snow_cloud", "ketchup_gas", "poisoned_ketchup_gas", "ketchup_powder", "poisoned_ketchup_powder", "eketchup_spout", "ketchup_metal", "antiketchup", "dirty_ketchup", "ketchup_gold", "molten_ketchup_metal", "ketchup_fairy", "ketchup_metal_scrap", "ketchup_gold_scrap", "molten_ketchup_gold", "mycelium","vaccine","antibody","infection","sap","caramel","molasses","melted_chocolate","soda","mustard","fry_sauce","tomato_sauce","sugary_tomato_sauce","bio_ooze","zombie_blood","feather","tooth","decayed_tooth","plaque","tartar","bacteria","replacer_bacteria","pop_rocks"];
		var lifeEaterSubstitutions = {
			"dirt": "life_eater_infected_dirt",
			"crimsoil": "life_eater_infected_dirt",
			"rainbow_dirt": "life_eater_infected_dirt"
		};


		function tryCreatePlus(element,centerX,centerY) {
			var plusCoords = adjacentCoords.concat([[0,0]]);
			var pixels = 0;
			for(let i = 0; i < plusCoords.length; i++) {
				var newX = centerX + plusCoords[i][0];
				var newY = centerY + plusCoords[i][1];
				if(isEmpty(newX,newY)) {
					while(element instanceof Array) { element = element[Math.floor(Math.random() * element.length)] };
					createPixel(element,newX,newY);
					pixels++;
				};
			};
			return pixels;
		};

		function spreadLifeEater(pixel) {
			var convertedPixels = [];
			for(i = 0; i < adjacentCoords.length; i++) { //iterate through neighbor spots
				if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) { //check for adjacentCoords
					var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]]
					var isLifeEaterFairy = (elements[newPixel.element].category == "auto_fey" && newPixel.element.includes("life_eater_"))
					//console.log(newPixel.element,isLifeEaterFairy);
					if(
						(lifeEaterCategories.includes(elements[newPixel.element].category) || lifeEaterWhitelist.includes(newPixel.element) || Object.keys(lifeEaterSubstitutions).includes(newPixel.element)) && 
						!lifeEaterBlacklist.includes(newPixel.element) &&
						!isLifeEaterFairy //exclude fairies which produce life eater
					) {
						if(Object.keys(lifeEaterSubstitutions).includes(newPixel.element)) {
							var data = lifeEaterSubstitutions[newPixel.element];
							while(data instanceof Array) {
								data = data[Math.floor(Math.random() * data.length)];
							};
							if(data === null) {
								if(newPixel) { deletePixel(newPixel.x,newPixel.y) };
							} else {
								changePixel(newPixel,data);
								convertedPixels.push(newPixel);
							};
						} else {
							changePixel(newPixel,"life_eater_slurry");
							convertedPixels.push(newPixel);
						};
					};
				};
			};
			return convertedPixels;
		};

		elements.life_eater_explosion = {
			color: ["#96c785","#f0d654","#ffb47a"],
			behavior: [
				"XX|XX|XX",
				"XX|EX:9>plasma,fire,life_eater_virus|XX",
				"XX|XX|XX",
			],
			temp: 1600,
			category: "energy",
			state: "gas",
			density: 1000,
			excludeRandom: true,
			hidden: true,
		},

		elements.life_eater_virus = {
			color: ["#7bb064", "#aabd60", "#9e9e29"],
			behavior: behaviors.GAS,
			tick: function(pixel) {
				spreadLifeEater(pixel).forEach(infectedPixel => spreadLifeEater(infectedPixel));
			},
			category: "life",
			state: "gas",
			density: airDensity,
			excludeRandom: true,
			tempHigh: 300,
			stateHigh: null,
		};

		elements.life_eater_slurry = {
			color: ["#3d6e29", "#666617", "#7d5716"],
			behavior: behaviors.LIQUID,
			properties: {
				methaned: false
			},
			tick: function(pixel) {
				spreadLifeEater(pixel).forEach(infectedPixel => spreadLifeEater(infectedPixel));

				if(pixelTicks - pixel.start > 6) {
					if(!pixel.methaned && Math.random() < 0.2) {
						changePixel(pixel,Math.random() < 0.2 ? "life_eater_virus" : "methane");
					} else {
						pixel.methaned = true;
					};
					tryCreatePlus(["methane","methane","methane","methane","life_eater_virus"],pixel.x,pixel.y);
					return;
				};
			},
			category: "life",
			state: "liquid",
			density: 1050,
			burn: 100,
			burnTime: 10,
			fireSpawnTemp: 1500,
			burnTempChange: 200,
			burnInto: ["life_eater_virus","plasma","fire","life_eater_explosion"],
			excludeRandom: true,
		};

		var crRule50 = "CR:life_eater_virus,methane,methane,methane%0.5";
		var crRule100 = "CR:life_eater_virus,methane,methane,methane%1";

		elements.life_eater_infected_dirt = {
			behavior: [
				"XX|"+crRule100+"|XX",
				crRule50+"|XX|"+crRule50,
				"M2|M1 AND "+crRule50+"|M2",
			],
			color: ["#757137","#617a35","#66622c","#707538"],
			tick: function(pixel) {
				spreadLifeEater(pixel).forEach(infectedPixel => spreadLifeEater(infectedPixel));
			},
			category: "life",
			state: "liquid",
			density: 1050,
			burn: 70,
			burnTime: 15,
			fireSpawnTemp: 1400,
			burnTempChange: 180,
			burnInto: ["life_eater_virus","fire","plasma","life_eater_explosion"],
			excludeRandom: true,
		};

		for(i = 0; i < 4; i++) {
			elements.life_eater_infected_dirt.burnInto.push(elements.dry_dirt ? "dry_dirt" : "sand");
		};

		elements.virus_bomb = {
			color: "#accc70",
			behavior: [
				"XX|EX:16>life_eater_virus|XX",
				"XX|XX|XX",
				"XX|EX:16>life_eater_virus AND M1|XX"
			],
			density: 3500,
			hardness: 0.95,
			breakInto: "life_eater_virus",
			tempHigh: 2400,
			category: "weapons",
			excludeRandom: true,
			stateHigh: ["molten_iron","molten_aluminum","molten_tin","life_eater_virus","life_eater_virus","life_eater_virus"]
		};

	//PLANET CRACKER ##

		function planetCrackerHeat(pixel,x,y,radius,fire,smoke,power,damage) {
			//console.log(`Radius: ${radius}\nPower: ${power}\nPixel: (${pixel.x},${pixel.y})\nDamage: ${damage}`);
			//console.log(`Expected temperature increase for pixel at (${pixel.x},${pixel.y}): ${800 * ((1 + (7 * damage)) ** 2) * ((power ** 2) * 1.5)}`);
			var reversedCloseness = ((radius / 6) ** 0.5) - 1; //mathematically inaccurate but properly correlated

			pixel.temp += 500 * ((reversedCloseness * 2) + 1);
			if(pixel.vx) {
				pixel.vx *= 2;
			};
			if(pixel.vy) {
				pixel.vy *= 2;
			};
		};

		function planetCrackerFinale(doColorChange=true) {
			var bottomFortyPercent = Math.round(height * 0.6);
			var bottomTwentyPercent = Math.round(height * 0.8);
			var bottomTenPercent = Math.round(height * 0.9);
			for(x = 1; x < width; x++) {
				for(y = bottomFortyPercent; y < height; y++) {
					var chance = y > bottomTwentyPercent ? 0.03 : 0.01
					var radius = y > bottomTwentyPercent ? 8 : 6
					if(!isEmpty(x,y,true)) {
						pixelMap[x][y].vy ??= 0;
						pixelMap[x][y].vy -= 20;
					};			
					if(y > bottomTenPercent && !isEmpty(x,y,true)) {
						pixelMap[x][y].temp += 2000;
					};
					if(Math.random() < chance) {
						explodeAt(x,y,radius,"plasma");
					};
				};
			};
			if(doColorChange) {
				settings.bg = ["#000000","#000000","#000000","#000000","#29180e","#663814","#9e6f19","#f7af2a"];
				settings.bgAngle = 90;
			};
		};

		elements.planet_cracker = {
			color: "#ffc8ba",
			behavior: behaviors.WALL,
			properties: {
				active: true,
				counter: 1,
			},
			tick: function(pixel) {
				if(!pixel?.active) {
					return;
				};
				if(outOfBounds(pixel.x,pixel.y+pixel.counter)) {
					planetCrackerFinale();
					pixel.active = false;
					changePixel(pixel,"metal_scrap");
				};
				if(pixel.active) {
					var pixelDistanceFromBottom = height - pixel.y;
					var counterDistanceFromBottom = height - (pixel.y + pixel.counter);
					var closenessToBottom = 1 - (counterDistanceFromBottom / pixelDistanceFromBottom);
					//console.log(closenessToBottom);

					var finalRadius = Math.round(((1 + closenessToBottom) ** 2) * 6);

					if(typeof(explodeAtPlus) === "function") {
						explodeAtPlus(pixel.x,pixel.y+pixel.counter,finalRadius,"plasma","fire",null,planetCrackerHeat);
					} else {
						explodeAt(pixel.x,pixel.y+pixel.counter,finalRadius,"plasma");
					};
					pixel.counter++;
				};
			},
			state: "solid",
			density: 10000,
			category: "weapons",
			hardness: 1,
		};

	//ASSORTED CELL EDITS AND GOLDEN FAIRY CHAIN ##

		goldObject = {
			gold: 1,
			gold_coin: 1,
			gold_fairy: 3
		};

		goldObjectNameArray = Object.keys(goldObject);

		if(urlParams.get('goldFairyAmount') != null) { //null check
			goldFairyAmount = urlParams.get('goldFairyAmount')
			if(isNaN(goldFairyAmount) || goldFairyAmount === "" || goldFairyAmount === null) { //NaN check
				 goldFairyAmount = 10
			}
			goldFairyAmount = parseInt(goldFairyAmount)
			if(goldFairyAmount > 10000) {
				alert("Maximum amount of additional gold fairies is 10000.\nOnly 10000 fairies were added.")
			} else if(goldFairyAmount < 1) {
				alert("Minimum amount of additional gold fairies is 1.\n1 fairy was added.")
			}
			goldFairyAmount = Math.min(10000,Math.max(goldFairyAmount,1))
		} else {
			goldFairyAmount = 10
		}

		elements.cell_1 = { //heats up
			color: ["#bbee00","#eeee00","#cfee00"],
			behavior: [
				"XX|CL%0.5|XX",
				"CL%0.5|HT:1%2|CL%0.5",
				"M2%10|M1|M2%10",
			],
			uwu: 0,
			properties: {
				uwu: 0
			},
			tick: function(pixel) {
				pixel.uwu = 0;
				for (let i = -1; i < 2; i++) { //neighbor count
					for (let j = -1; j < 2; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++;
							};
						};
					};
				};
				pixel.uwu -= 1; //exclude self

				var tempGrowthDecrease = null;
				if(pixel.temp < 70) {
					tempGrowthDecrease = 0;
				} else if(pixel.temp > 100) {
					tempGrowthDecrease = 70;
				} else {
					tempGrowthDecrease = pixel.temp - 70;
				}

				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					uwu = 0;
				} else {
					var tempGain = pixel.uwu / 48;
					var chanceGain = pixel.uwu / 96;
				};
				if(Math.random() < (  (2 + chanceGain) - (tempGrowthDecrease / 30)  )) {
					pixel.temp += tempGain;
				};
			},
			reactions: {
				"infection": { "elem1":"cancer", "chance":0.01 },
				"blood": { "elem1":"blood", "chance":0.01 },
				"antibody": { "elem1":"antibody", "chance":0.01 },
				"sugar": { "elem2":"cell", "chance":0.03 },
				"sugar_water": { "elem2":"cell", "chance":0.04 },
				"alcohol": { "elem1":[null,"dna"], "chance":0.02 },
				"poison": { "elem1":null, "chance":0.02 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.05 },
				"ammonia": { "elem2":"nitrogen", "chance":0.05 }
			},
			tempHigh: 122,
			stateHigh: "steam",
			tempLow: -6,
			stateLow: "ice",
			state: "solid",
			density: 1000.2,
			category: "life",
			breakInto: ["water","dna","dna","dna"]
		};

		elements.cell_2 = { //Grows just a bit too fast
			color: ["#32280b","#5a4711","#87660c"],
			behavior: [
				"XX|CL%1.8|XX",
				"CL%1.8|XX|CL%1.8",
				"M2%5|M1|M2%5",
			],
			reactions: {
				"cancer": { "elem2":"cell_2", "chance":0.035 },
				"cell": { "elem2":"cell_2", "chance":0.001 },
				"frog": { "elem2":"cell_2", "chance":0.001 },
				"fish": { "elem2":"cell_2", "chance":0.001 },
				"rat": { "elem2":"cell_2", "chance":0.001 },
				"bird": { "elem2":"cell_2", "chance":0.001 },
				"sugar": { "elem2":"cell_2", "chance":0.035 },
				"sugar_water": { "elem2":"cell_2", "chance":0.045 },
				"alcohol": { "elem1":[null,"dna"], "chance":0.012 },
				"poison": { "elem1":[null,null,"poison","poison","poison","dna","dirty_water"], "chance":0.03 },
				"proton": { "elem1":[null,"cancer"], "chance":0.04 }
			},
			tempHigh: 80,
			stateHigh: ["steam","plague"],
			state: "solid",
			density: 1000.17,
			category: "life",
			breakInto: ["dirty_water","dna","dna","dna","dna"]
		};

		elements.cell_3 = { //Likes gold
			color: ["#bbee00","#eeee00","#cfee00"],
			behavior: [
				"XX|CL%0.5|XX",
				"CL%0.5|HT:1%2|CL%0.5",
				"M2%10|M1|M2%10",
			],
			uwu: 0,
			properties: {
				uwu: 0
			},
			tick: function(pixel) {
				pixel.uwu = 0;
				for (let i = -1; i < 2; i++) { //neighbor count
					for (let j = -1; j < 2; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++;
							};
						};
					};
				};
				pixel.uwu -= 1; //exclude self

				var tempGrowthDecrease = null;
				if(pixel.temp < 70) {
					tempGrowthDecrease = 0;
				} else if(pixel.temp > 100) {
					tempGrowthDecrease = 70;
				} else {
					tempGrowthDecrease = pixel.temp - 70;
				}

				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					uwu = 0;
				} else {
					var tempGain = pixel.uwu / 48;
					var chanceGain = pixel.uwu / 96;
				};
				if(Math.random() < (  (2 + chanceGain) - (tempGrowthDecrease / 30)  )) {
					pixel.temp += tempGain;
				};
			},
			reactions: {
				"infection": { "elem1":"cancer", "chance":0.01 },
				"blood": { "elem1":"blood", "chance":0.01 },
				"antibody": { "elem1":"antibody", "chance":0.01 },
				"sugar": { "elem2":"cell", "chance":0.03 },
				"sugar_water": { "elem2":"cell", "chance":0.04 },
				"alcohol": { "elem1":[null,"dna"], "chance":0.02 },
				"poison": { "elem1":null, "chance":0.02 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.05 },
				"ammonia": { "elem2":"nitrogen", "chance":0.05 }
			},
			tempHigh: 122,
			stateHigh: "steam",
			tempLow: -6,
			stateLow: "ice",
			state: "solid",
			density: 1000.2,
			category: "life",
			breakInto: ["water","dna","dna","dna"]
		};

		elements.cell_3 = {
			color: ["#eeee00","#eecc00","#dddd00"],
			behavior: [
				"XX|CL%0|XX", //CL%0 for display purposes
				"CL%0|XX|CL%0",
				"M2%10|M1|M2%10",
			],
			reactions: {
				"infection": { "elem1":"cancer", "chance":0.01 },
				"blood": { "elem1":"blood", "chance":0.01 },
				"antibody": { "elem1":"antibody", "chance":0.01 },
				"sugar": { "elem2":"cell", "chance":0.03 },
				"sugar_water": { "elem2":"cell", "chance":0.04 },
				"alcohol": { "elem1":[null,"dna"], "chance":0.02 },
				"poison": { "elem1":null, "chance":0.02 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.05 },
				"ammonia": { "elem2":"nitrogen", "chance":0.05 }
			},
			tempHigh: 102,
			stateHigh: "steam",
			tempLow: -2,
			stateLow: "ice",
			state: "solid",
			density: 1000.1,
			category: "life",
			breakInto: ["water","dna","dna","dna"],
			gold: 0,
			properties: {
				gold: 0
			},
			tick: function(pixel) {
				var pX = pixel.x
				var pY = pixel.y
				var baseEatRate = 1;
				var eatRate = baseEatRate + (2 * pixel.gold * 0.01);
				if(Math.random() < eatRate/100) {
					var randomNeighborNumber = Math.floor(Math.random() * 4)
					var oX = adjacentCoords[randomNeighborNumber][0];
					var oY = adjacentCoords[randomNeighborNumber][1];
					var checkPosX = pX+oX;
					var checkPosY = pY+oY;

					if(!isEmpty(checkPosX,checkPosY,true)) {
						if(goldObjectNameArray.includes(pixelMap[checkPosX][checkPosY].element)) {
							pixel.gold += goldObject[pixelMap[checkPosX][checkPosY].element];
							deletePixel(checkPosX,checkPosY);
						};
					};
				};

				var baseGrowthRate = 0.5;
				var growthRate = baseGrowthRate + (pixel.gold * 0.025);

				if(pixel.gold > 150) {
					var chance = (pixel.gold - 150) / 20
					var baseRadius = 10
					var radius = Math.max(20,Math.round((baseRadius + (pixel.gold / 25))))
					if(Math.random() < (chance / 100)) {
						explodeAt(pX,pY,10,"gold_coin");
					};
				};

				if(Math.random() < growthRate/100) {
					var randomNeighborNumber = Math.floor(Math.random() * 4)
					var oX = adjacentCoords[randomNeighborNumber][0];
					var oY = adjacentCoords[randomNeighborNumber][1];
					var checkPosX = pX+oX;
					var checkPosY = pY+oY;

					if(isEmpty(checkPosX,checkPosY,false)) {
						var halfGold1 = pixel.gold / 2;
						var halfGold2 = pixel.gold / 2;
						if(halfGold1 % 1 == 0.5) {
							halfGold1 = Math.ceil(halfGold1);
							halfGold2 = Math.floor(halfGold2);
						};
						createPixel(pixel.element,checkPosX,checkPosY);
						newPixel = pixelMap[checkPosX][checkPosY];
						pixel.gold = halfGold1;
						newPixel.gold = halfGold2;
					};
				};

			},
			reactions: {
				"infection": { "elem1":"cancer", "chance":0.01 },
				"blood": { "elem1":"blood", "chance":0.01 },
				"antibody": { "elem1":"antibody", "chance":0.01 },
				"sugar": { "elem2":"cell", "chance":0.03 },
				"sugar_water": { "elem2":"cell", "chance":0.04 },
				"alcohol": { "elem1":[null,"dna"], "chance":0.02 },
				"poison": { "elem1":null, "chance":0.02 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.05 },
				"ammonia": { "elem2":"nitrogen", "chance":0.05 }
			},
			tempHigh: 122,
			stateHigh: "steam",
			tempLow: -6,
			stateLow: "ice",
			state: "solid",
			density: 1000.2,
			category: "life",
			breakInto: ["water","dna","dna","dna"]
		};

		runAfterLoad(function() {
			elements.gold_fairy = {
				name: "gold fairy",
				color: ["#cfa65b","#e6df63","#faf673"],
				behavior: [
					"XX|M1|M1",
					"XX|FX%5|XX",
					"XX|CR:gold_coin%0.25 AND CR:fairy_dust%0.005 AND M1|M1",
				],
				state: "solid",
				category: "fey"
			};

			elements.fairy.reactions.gold = { elem1: "gold_fairy", "elem2": null };
			elements.fairy.reactions.gold_coin = { elem1: "gold_fairy", "elem2": null };
			if(!elements.gold.reactions) {
				elements.gold.reactions = {};
			};
			if(!elements.gold_coin.reactions) {
				elements.gold_coin.reactions = {};
			};
			elements.gold.reactions.fairy = { elem1: null, elem2: "gold_fairy" };
			elements.gold_coin.reactions.fairy = { elem1: null,  elem2: "gold_fairy" };

			for (var i = 2; i <= goldFairyAmount + 1; i++) {
			  nameVar = `gold_${i-1}-fairy`
			  if(i === 2) {
				nameVar = "gold_fairy";
			  };
			  elements[`gold_${i}-fairy`] = {
				name: `gold_${i}-fairy`,
				color: ["#cfa65b","#e6df63","#faf673"],
				behavior: [
				  "XX|M1|M1",
				  "XX|FX%5|XX",
				  "XX|CR:" + nameVar + "%0.25 AND CR:fairy_dust%0.005 AND M1|M1",
				],
				reactions: {
				  "glitter": { "elem1": `gold_${i+1}-fairy`, "elem2": null }
				},
				state: "solid",
				excludeRandom:true,
				category: "fey",
				hidden: true,
			  }
			  goldObject[`gold_${i}-fairy`] = 3 * i;
			  if (i == goldFairyAmount) { elements[`gold_${i}-fairy`]["reactions"] = {}; }
			}

			//eList rebuilding {
				eLists.FAIRY.push("gold_fairy");
				for (var i = 2; i <= goldFairyAmount + 1; i++) {
					eLists.FAIRY.push(`gold_${i}-fairy`);
				};
				elements.iron.behavior = [
					"XX|DL:"+eLists.FAIRY+"|XX",
					"DL:"+eLists.FAIRY+"|XX|DL:"+eLists.FAIRY+"",
					"XX|DL:"+eLists.FAIRY+"|XX"
				];
				elements.silver.behavior = [
					"XX|DL:"+eLists.FAIRY+"|XX",
					"DL:"+eLists.FAIRY+"|XX|DL:"+eLists.FAIRY+"",
					"XX|DL:"+eLists.FAIRY+"|XX"
				];
				elements.tungstensteel.behavior = [
					"XX|DL:"+eLists.FAIRY+"|XX",
					"DL:"+eLists.FAIRY+"|XX|DL:"+eLists.FAIRY+"",
					"XX|DL:"+eLists.FAIRY+"|XX",
				];
				elements.molten_tungstensteel ??= {};
				elements.molten_tungstensteel.behavior = [
					"XX|DL:"+eLists.FAIRY+" AND CR:fire%2.5|XX",
					"DL:"+eLists.FAIRY+" AND M2|XX|DL:"+eLists.FAIRY+" AND M2",
					"M1|DL:"+eLists.FAIRY+"|M1",
				]
			//}

			goldObjectNameArray = Object.keys(goldObject);

			elements.rainbow.reactions.gold_fairy = { "elem1": "gold_2-fairy", "elem2": null }
		});

	//FIRE SLIME ##

		elements.fire_slime = {
				color: ["#e6683e", "#e37636", "#e38f3b", "#e3b039"],
				behavior: [
					"XX|CR:fire%5|XX",
					"M2|XX|M2",
					"M1%1 AND M2|M1|M1%1 AND M2"
				],
				reactions: {
					"bomb": { "elem2":"sticky_bomb", "elem2":null },
				},
				tick: function(pixel) {
					if(Math.random() < 0.01) {
						pixel.temp++;
						pixelTempCheck(pixel);
					};
					if(pixel.temp < 700) {
						if(Math.random() < 0.02) {
							pixel.temp++;
							pixelTempCheck(pixel);
						};
					};
				},
				viscosity: 3000,
				temp: 700,
				tempHigh: 6000,
				stateHigh: "plasma",
				tempLow: -13,
				stateLow: "suppressed_fire_slime",
				category: "liquids",
				state: "liquid",
				burning: true,
				burnTime: Number.MAX_SAFE_INTEGER,
				burn: 85,
				burnInto: "fire_slime",
				density: 1400,
				stain: 0.05
		}

		elements.suppressed_fire_slime = {
				color: "#bf6a4e",
				behavior: [
					"XX|CR:smoke%1|XX",
					"M2|XX|M2",
					"M1%0.5 AND M2|M1|M1%0.5 AND M2"
				],
				reactions: {
					"bomb": { "elem2":"sticky_bomb", "elem2":null },
				},
				tick: function(pixel) {
					if(Math.random() < 0.001) {
						pixel.temp++;
						pixelTempCheck(pixel);
					};
				},
				viscosity: 4000,
				temp: -20,
				tempHigh: -13,
				stateHigh: "fire_slime",
				category: "liquids",
				state: "liquid",
				burning: false,
				burnTime: 1000,
				burn: 1,
				burnInto: "fire_slime",
				density: 1550,
				stain: 0.04,
				hidden: true
		}

	//FWIBBLENS ##

		elements.fwibblen = {
			color: ["#73F092", "#EB7373", "#EDBC7B"],
			behavior: [
				"XX|M2|M1",
				"XX|FX%2|BO",
				"XX|XX|M1",
			],
			reactions: {
				nickel: { elem2: ["fwibblen",null] },
				nickel_scrap: { elem2: ["fwibblen",null] }
			},
			tempHigh: 1100,
			stateHigh: "dead_fwibblen",
			category:"life",
			state: "solid",
			density: 1500,
			conduct: 0.4,
		}

		elements.dead_fwibblen = {
			color: ["#729E7D", "#B58484", "#C4B299"],
			behavior: behaviors.POWDER,
			tempHigh: 1455,
			stateHigh: ["molten_nickel","molten_nickel","molten_nickel","smoke"],
			category:"life",
			hidden: true,
			state: "solid",
			density: 1400,
			conduct: 0.4,
		}

		elements.meffwibblen = {
			color: ["#5CE697", "#F05B60", "#EB9560"],
			behavior: [
				"XX|M2|M1",
				"XX|FX%2|BO",
				"XX|XX|M1",
			],
			reactions: {
				fwibblen: { elem2: ["meffwibblen",null] },
				dead_fwibblen: { elem2: ["meffwibblen",null] }
			},
			tempHigh: 1150,
			stateHigh: "dead_meffwibblen",
			category:"life",
			state: "solid",
			density: 1800,
			conduct: 0.44,
		}

		elements.dead_meffwibblen = {
			color: ["#659E7D", "#B37073", "#B08F7B"],
			behavior: behaviors.POWDER,
			tempHigh: 1455,
			stateHigh: ["molten_nickel","molten_nickel","molten_nickel","molten_nickel","smoke"],
			category:"life",
			hidden: true,
			state: "solid",
			density: 1800,
			conduct: 0.44,
		}

		//Debug world

		/*worldgentypes.nickel = {
			layers: [
				[0, "nickel"],
			]
		};*/

	//EXPERIMENTAL CONTROLLABLE PIXEL ##

		sussyKey = null;
		isShift = false;
		isAlt = false;

		document.addEventListener("keydown", function(modifierDownListener) {
			// User presses shift
			if (modifierDownListener.keyCode == 16) {
				isShift = true;
			}
			// User presses alt
			if (modifierDownListener.keyCode == 18) {
				isAlt = true;
			}
		});

		document.addEventListener("keyup", function(modifierUpListener) {
			// User releases shift
			if (modifierUpListener.keyCode == 16) {
				isShift = false;
			}
			// User releases alt
			if (modifierUpListener.keyCode == 18) {
				isAlt = false;
			}
		});

		document.addEventListener("keyup", function(sussyListener) {
			switch (sussyListener.keyCode) {
				case 87:
					sussyKey = "W";
					break;
				case 65:
					sussyKey = "A";
					break;
				case 83:
					sussyKey = "S";
					break;
				case 68:
					sussyKey = "D";
					break;
				case 81:
					sussyKey = "Q";
					break;
				case 88:
					sussyKey = "X";
					break;
				case 90:
					sussyKey = "Z";
					break;
				case 72:
					sussyKey = "H";
					break;
			};
		});

		function controllablePixelTryCreatePixelNullCheck(element,x,y) {
			if(!elements[element]) { //catch the null
				return false;
			};
			if(isEmpty(x,y)) {
				tryCreatePixel(element,x,y);
				return true;
			} else {
				return false;
			}
		}

		elements.controllable_pixel = {
			color: "#FFFFFF",
			colorOn: "#FFFF00",
			behavior: behaviors.WALL,
			state: "solid",
			density: 2000,
			maxSize: 1,
			conduct: 1,
			hardness: 1,
			tick: function(pixel) {
				var xx = pixel.x;
				var yy = pixel.y;
				userElement = currentElement;
				if(userElement === pixel.element) {
					userElement = null;
				};
				if(isShift && !isAlt) {
					sussyKey === "Z" ? pixel.color = "rgb(255,191,127)" : pixel.color = "rgb(255,127,127)";
				}
				if(isAlt && !isShift) {
					sussyKey === "Z" ? pixel.color = "rgb(191,255,127)" : pixel.color = "rgb(127,255,127)";
				}
				if(isAlt && isShift) {
					sussyKey === "Z" ? pixel.color = "rgb(255,255,0)" : pixel.color = "rgb(255,255,127)";
				}
				if(!isAlt && !isShift) {
					sussyKey === "Z" ? pixel.color = "rgb(255,255,191)" : pixel.color = "rgb(255,255,255)";
				}
				if(sussyKey !== null) {
					switch (sussyKey) {
						case "W":
							isAlt ? controllablePixelTryCreatePixelNullCheck(userElement,xx,yy-1) : tryMove(pixel,xx,yy-1);
							if(!isShift) {
								sussyKey = null;
							}
							break;
						case "A":
							isAlt ? controllablePixelTryCreatePixelNullCheck(userElement,xx-1,yy) : tryMove(pixel,xx-1,yy);
							if(!isShift) {
								sussyKey = null;
							}
							break;
						case "S":
							isAlt ? controllablePixelTryCreatePixelNullCheck(userElement,xx,yy+1) : tryMove(pixel,xx,yy+1);
							if(!isShift) {
								sussyKey = null;
							}
							break;
						case "D":
							tryMove(pixel,xx+1,yy);
							if(!isShift) {
								sussyKey = null;
							}
							break;
						case "H": //Alt+D is something else in some browsers.
							if(isAlt) {
								controllablePixelTryCreatePixelNullCheck(userElement,xx+1,yy);
							};
							if(!isShift) {
								sussyKey = null;
							}
							break;
						case "X":
							explodeAt(xx,yy,5)
							if(!isShift) {
								sussyKey = null;
							}
							break;
						case "Z":
							if (!pixel.charge && !pixel.chargeCD && !isEmpty(pixel.x,pixel.y,true)) {
								pixel.charge = 1;
							}
							if(!isShift === 0) {
								sussyKey = null;
							}
							break;
						case "Q": //Use if a key gets stuck
							sussyKey = null;
							isShift = null;
							isAlt = null;
							break;
					}
				}
			},
		}

		actExcludedElements = ["wall","alt_controllable_pixel"];

		function actTryMove(pixel,x,y) {
			if(!tryMove(pixel,x,y)) {
				if(outOfBounds(x,y)) {
					return false;
				};
				if(!isEmpty(x,y,true)) {
					var newPixel = pixelMap[x][y];
					var newElement = newPixel.element;
					if(actExcludedElements.includes(newElement)) {
						return false;
					};
					if(newElement == pixel.element) { //Copy-paste of "break" code
						swapPixels(pixel,newPixel);
						return true;
					} else {
						breakCircle(newPixel.x,newPixel.y,pixel.breakAroundRadius,false,false,false); //does nothing to elements without breakIntos if defaultBreakIntoDust is false
						swapPixels(pixel,newPixel);
						return true;
					};
				} else {
					return false;
				};
			} else {
				return true;
			};
		};

		function cptaEapFunction(pixel,x,y,radius,fire,smoke,power,damage) {
			var coords = circleCoords(pixel.x,pixel.y,radius);
			for (var i = 0; i < coords.length; i++) {
				var x = coords[i].x;
				var y = coords[i].y;
				if(isEmpty(x,y)) { //if there's space for fire
					if (Array.isArray(fire)) { //this should remain "fire"
						var newfire = fire[Math.floor(Math.random() * fire.length)];
					} else {
						var newfire = fire;
					};
					createPixel(newfire,x,y); //add fire
					var firePixel = pixelMap[x][y];
					firePixel.temp = Math.max(elements[newfire].temp,firePixel.temp);
					firePixel.burning = true;
				};
				if(!isEmpty(x,y,true)) {
					pixel.temp += (400 * ((1 + (5 * damage)) ** 2) * ((power ** 1.5) * 1.5));
					pixelTempCheck(pixel);
				};
				if (!elements[pixel.element].excludeVelocity) {
					var angle = Math.atan2(pixel.y-y,pixel.x-x);
					pixel.vx = Math.round((pixel.vx|0) + Math.cos(angle) * (radius * power/10));
					pixel.vy = Math.round((pixel.vy|0) + Math.sin(angle) * (radius * power/10));
				}
			};
		};

		alt_sussyKey = null;
		isShift = false;
		isAlt = false;

		document.addEventListener("keydown", function(modifierDownListener) {
			// User presses shift
			if (modifierDownListener.keyCode == 16) {
				isShift = true;
			}
			// User presses alt
			if (modifierDownListener.keyCode == 18) {
				isAlt = true;
			}
		});

		document.addEventListener("keyup", function(modifierUpListener) {
			// User releases shift
			if (modifierUpListener.keyCode == 16) {
				isShift = false;
			}
			// User releases alt
			if (modifierUpListener.keyCode == 18) {
				isAlt = false;
			}
		});

		document.addEventListener("keyup", function(alt_sussyListener) {
			switch (alt_sussyListener.keyCode) {
				case 87:
					alt_sussyKey = "W";
					break;
				case 65:
					alt_sussyKey = "A";
					break;
				case 83:
					alt_sussyKey = "S";
					break;
				case 68:
					alt_sussyKey = "D";
					break;
				case 81:
					alt_sussyKey = "Q";
					break;
				case 88:
					alt_sussyKey = "X";
					break;
				case 90:
					alt_sussyKey = "Z";
					break;
				case 72:
					alt_sussyKey = "H";
					break;
				case 76:
					alt_sussyKey = "L";
					break;
			};
		});

		function controllablePixelTryCreatePixelNullCheck(element,x,y) {
			if(!elements[element]) { //catch the null
				return false;
			};
			if(isEmpty(x,y)) {
				tryCreatePixel(element,x,y);
				return true;
			} else {
				return false;
			}
		}

		elements.alt_controllable_pixel = {
			color: "#7F7F7F",
			colorOn: "#FFFF00",
			behavior: behaviors.WALL,
			state: "solid",
			density: 2000,
			maxSize: 1,
			conduct: 1,
			hardness: 1,
			movable: true,
			desc: "it breaks things more<br/>Use the console or enable prop.js to set speed / explosionRadius / circleRadius / breakAroundRadius (though it has an Illogical™ random resetting bug)",
			breakInto: "alt_controllable_pixel",
			properties: {
				speed: 1,
				explosionRadius: 8,
				circleRadius: 8,
				breakAroundRadius: 4
			},
			excludeVelocity: true,
			tick: function(pixel) {
				userElement = currentElement;
				if(userElement === pixel.element) {
					userElement = null;
				};
				if(isShift && !isAlt) {
					alt_sussyKey === "Z" ? pixel.color = "rgb(255,191,127)" : pixel.color = "rgb(255,127,127)";
				}
				if(isAlt && !isShift) {
					alt_sussyKey === "Z" ? pixel.color = "rgb(191,255,127)" : pixel.color = "rgb(127,255,127)";
				}
				if(isAlt && isShift) {
					alt_sussyKey === "Z" ? pixel.color = "rgb(255,255,0)" : pixel.color = "rgb(255,255,127)";
				}
				if(!isAlt && !isShift) {
					alt_sussyKey === "Z" ? pixel.color = "rgb(255,255,191)" : pixel.color = "rgb(255,255,255)";
				}
				if(alt_sussyKey !== null) {
					switch (alt_sussyKey) {
						case "W":
							if(isAlt) { controllablePixelTryCreatePixelNullCheck(userElement,pixel.x,pixel.y-1) } else { for(move = 0; move < pixel.speed; move++) { actTryMove(pixel,pixel.x,pixel.y-1) } };
							if(!isShift) {
								alt_sussyKey = null;
							}
							break;
						case "A":
							if(isAlt) { controllablePixelTryCreatePixelNullCheck(userElement,pixel.x-1,pixel.y) } else { for(move = 0; move < pixel.speed; move++) { actTryMove(pixel,pixel.x-1,pixel.y) } };
							if(!isShift) {
								alt_sussyKey = null;
							}
							break;
						case "S":
							if(isAlt) { controllablePixelTryCreatePixelNullCheck(userElement,pixel.x,pixel.y+1) } else { for(move = 0; move < pixel.speed; move++) { actTryMove(pixel,pixel.x,pixel.y+1) } };
							if(!isShift) {
								alt_sussyKey = null;
							}
							break;
						case "D":
							for(move = 0; move < pixel.speed; move++) { actTryMove(pixel,pixel.x+1,pixel.y) };
							if(!isShift) {
								alt_sussyKey = null;
							}
							break;
						case "H": //Alt+D is something else in some browsers.
							if(isAlt) {
								controllablePixelTryCreatePixelNullCheck(userElement,pixel.x+1,pixel.y);
							};
							if(!isShift) {
								alt_sussyKey = null;
							}
							break;
						case "X": 
							explodeAtPlus(pixel.x,pixel.y,pixel.explosionRadius,"plasma,fire,fire","fire,smoke,smoke",null,cptaEapFunction)
							if(!isShift) {
								alt_sussyKey = null;
							}
							break;
						case "Z":
							if (!pixel.charge && !pixel.chargeCD && !isEmpty(pixel.x,pixel.y,true)) {
								pixel.charge = 1;
							}
							if(!isShift === 0) {
								alt_sussyKey = null;
							}
							break;
						case "L":
							if(userElement !== null) {
								fillCircle(currentElement,pixel.x,pixel.y,pixel.circleRadius,false);
								if(!isShift === 0) {
									alt_sussyKey = null;
								}
							};
							break;
						case "Q": //Use if a key gets stuck
							alt_sussyKey = null;
							isShift = null;
							isAlt = null;
							break;
					}
				}
			}
		}

	//PARTIAL APIOFORM GAME ##

		playerKills = 0;

		aplReceivedKey = null;
		isShift = false;
		isAlt = false;

		document.addEventListener("keydown", function(modifierDownListener) {
			// User presses shift
			if (modifierDownListener.keyCode == 16) {
				isShift = true;
			}
			// User presses alt
			if (modifierDownListener.keyCode == 18) {
				isAlt = true;
			}
		});

		document.addEventListener("keyup", function(modifierUpListener) {
			// User releases shift
			if (modifierUpListener.keyCode == 16) {
				isShift = false;
			}
			// User releases alt
			if (modifierUpListener.keyCode == 18) {
				isAlt = false;
			}
		});

		entities = ["apioform_bee", "apioform", "apiodiagoform", "apiokinetoform", "apiopyroform", "cryoapioform", "apiopariform"]

		function apioTryMove(pixel,nx,ny,leaveBehind=undefined) {
			if(!isEmpty(nx,ny,true) && !outOfBounds(nx.ny)) {
				otherPixel = pixelMap[nx][ny];
				var thisElement = pixel.element;
				var otherElement = otherPixel.element;
				if(thisElement == "apioform_player") {
					if(entities.includes(otherElement)) {
						deletePixel(nx,ny);
						playerKills++;
						movePixel(pixel,nx,ny,leaveBehind)
					};
				} else if(entities.includes(thisElement)) {
					if(otherElement == "apioform_player") {
						deletePixel(nx,ny);
						movePixel(pixel,nx,ny,leaveBehind)
					};
				};
			} else {
				tryMove(pixel,nx,ny,leaveBehind);
			};
		}

		document.addEventListener("keyup", function(apioformPlayerListener) {
			switch (apioformPlayerListener.keyCode) {
				case 38: //arrow up, letter 87:
					aplReceivedKey = "W";
					break;
				case 37: //arrow left, letter 65
					aplReceivedKey = "A";
					break;
				case 40: //arrow down, letter 83
					aplReceivedKey = "S";
					break;
				case 39: //arrow right, letter 68
					aplReceivedKey = "D";
					break;
				case 27: //escape key, letter 81
					aplReceivedKey = "Q";
					break;
			};
		});

		elements.apioform_player = {
			color: "#7F7F7F",
			behavior: behaviors.WALL,
			state: "solid",
			density: 2000,
			maxSize: 1,
			hardness: 0.8,
			burnTime: 10,
			burn: 50,
			category: "apioform game",
			tick: function(pixel) {
				var xx = pixel.x;
				var yy = pixel.y;
				if(isShift) {
					pixel.color = "rgb(255,127,127)";
				} else {
					pixel.color = "rgb(255,255,255)";
				}
				if(aplReceivedKey !== null) {
					switch (aplReceivedKey) {
						case "W":
							apioTryMove(pixel,xx,yy-1);
							if(!isShift) {
								aplReceivedKey = null;
							}
							break;
						case "A":
							apioTryMove(pixel,xx-1,yy);
							if(!isShift) {
								aplReceivedKey = null;
							}
							break;
						case "S":
							apioTryMove(pixel,xx,yy+1);
							if(!isShift) {
								aplReceivedKey = null;
							}
							break;
						case "D":
							apioTryMove(pixel,xx+1,yy);
							if(!isShift) {
								aplReceivedKey = null;
							}
							break;
						case "Q": //Use if a key gets stuck
							aplReceivedKey = null;
							isShift = null;
							break;
					};
				};
			},
		}

		elements.apioform_bee = {
			color: ["#808020", "#A0A050"],
			properties: {
				stage: ((Math.random() < 0.5) ? 0 : 1),
			},
			behavior: behaviors.WALL,
			state: "solid",
			category: "apioform game",
			density: 2000,
			maxSize: 1,
			hardness: 0.8,
			flippableY: true,
			tick: function(pixel) {
				if(isNaN(pixel.stage)) {
					pixel.stage = ((Math.random() < 0.5) ? 0 : 1);
				};
				pixel.stage = Math.min(1,Math.max(0,Math.round(pixel.stage)));	//stage validation

				var moveCoords = (pixel.flipY ? [pixel.x, pixel.y+1] : [pixel.x, pixel.y-1]);

																				//direction is randomized by flippableY
				if(pixel.stage === 0) { //color
					pixel.color = "rgb(128,128,32)";
				} else if(pixel.stage === 1) {
					pixel.color = "rgb(160,160,80)";
				};

				if(pixelTicks - pixel.start > 0) { //don't move on first tick, to allow color to normalize
					if(pixel.stage === 0) { //if in still stage
						pixel.stage = 1; //change to moving stage
					} else if(pixel.stage === 1) { //else if in moving stage
						apioTryMove(pixel,moveCoords[0],moveCoords[1]); //move
						pixel.flipY = !pixel.flipY; //flip that
						pixel.stage = 0; //change to still stage
					};
				};

			},
		}

		elements.apioform = {
			color: "#A08020",
			behavior: behaviors.WALL,
			state: "solid",
			category: "apioform game",
			density: 2000,
			maxSize: 1,
			hardness: 0.8,
			rotatable: true,
			tick: function(pixel) {
				if(isNaN(pixel.r)) {
					pixel.r = Math.floor(Math.random() * 4);
				};
				pixel.r = Math.round(pixel.r) % 4;	//rotation validation, probably redundant since rotation is from the base game

				var moveCoords;
				switch (pixel.r) {
					case 0:
						moveCoords = [pixel.x+1, pixel.y+0];
						break;
					case 1:
						moveCoords = [pixel.x+0, pixel.y-1];
						break;
					case 2:
						moveCoords = [pixel.x-1, pixel.y+0];
						break;
					case 3:
						moveCoords = [pixel.x+0, pixel.y+1];
						break;
				};
				//rotation is randomized by rotatable

				if(pixelTicks - pixel.start > 0) { //maintained for consistency
					apioTryMove(pixel,moveCoords[0],moveCoords[1]); //move
					pixel.r++; //increment rotation
				};
			},
		}

		elements.apiodiagoform = {
			color: "#A080A0",
			behavior: behaviors.WALL,
			state: "solid",
			category: "apioform game",
			density: 2000,
			maxSize: 1,
			hardness: 0.8,
			rotatable: true,
			tick: function(pixel) {
				if(isNaN(pixel.r)) {
					pixel.r = Math.floor(Math.random() * 4);
				};
				pixel.r = Math.round(pixel.r) % 4;	//rotation validation, probably redundant since rotation is from the base game

				var moveCoords;
				switch (pixel.r) {
					case 0:
						moveCoords = [pixel.x-1, pixel.y-1];
						break;
					case 1:
						moveCoords = [pixel.x-1, pixel.y+1];
						break;
					case 2:
						moveCoords = [pixel.x+1, pixel.y+1];
						break;
					case 3:
						moveCoords = [pixel.x+1, pixel.y-1];
						break;
				};
				//rotation is randomized by rotatable

				if(pixelTicks - pixel.start > 0) { //maintained for consistency
					apioTryMove(pixel,moveCoords[0],moveCoords[1]); //move
					pixel.r++; //increment rotation
				};
			},
		}

	//AMOGUS ##

		elements.amogus = {
			name: "Amogus",
			color: "#ffffff",
			cooldown: 6,
			tick: function(pixel) {
			pixel.arr=[["brick",  "brick",  "brick"],
					   ["brick",  "glass",  "glass"],
					   ["brick",  "brick",  "brick"],
					   ["brick",   "air",   "brick"]];
			pixel.carr=[[ "rgb(255,0,0)",	"rgb(255,0,0)",		"rgb(255,0,0)"	],
						[ "rgb(255,0,0)",	"rgb(0,255,255)",	"rgb(0,255,255)"],
						[ "rgb(255,0,0)",	"rgb(255,0,0)",		"rgb(255,0,0)"	],
						[ "rgb(255,0,0)",	"null",				"rgb(255,0,0)"	]];

				aa = (0 - (Math.floor(pixel.arr[0].length / 2))) //Center align code
				na = Math.abs(aa)
				if(pixel.arr[0].length % 2 == 1) {
					bb = ((Math.floor(pixel.arr[0].length / 2)) + 1)
				} else if(pixel.arr[0].length % 2 == 0) {
					bb = (Math.floor(pixel.arr[0].length / 2))
				}

				cc = (0 - (Math.floor(pixel.arr.length / 2)))
				nc = Math.abs(cc)
				if(pixel.arr.length % 2 == 1) {
					dd = ((Math.floor(pixel.arr.length / 2)) + 1)
				} else if(pixel.arr.length % 2 == 0) {
					dd = (Math.floor(pixel.arr.length / 2))
				}
				for (let j = cc; j < dd; j++) { //Iterative placing and coloring of pixels
					for (let i = aa; i < bb; i++) {
						if(!isEmpty(pixel.x+i,pixel.y+j,true)) {
							if(pixel.arr[j+nc][i+na] !== "null") {
								deletePixel(pixel.x+i,pixel.y+j)
							}
						}
						if(pixel.arr[j+nc][i+na]) {
							if(isEmpty(pixel.x+i,pixel.y+j,false) && pixel.arr[j+nc][i+na] !== "null" && pixel.arr[j+nc][i+na] !== "air") {
								createPixel(pixel.arr[j+nc][i+na],pixel.x+i,pixel.y+j)
								if(pixel.carr[j+nc][i+na]) {
									if(!isEmpty(pixel.x+i,pixel.y+j,true) && pixel.carr[j+nc][i+na] != "null") {
										pixelMap[pixel.x+i][pixel.y+j].color = pixel.carr[j+nc][i+na]
									}
								}
							}
						}
					}
				}
			},
			category:"structures",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		},

		elements.amogus_seed = {
			name: "Amogus Seed",
			color: "#df2f47",
			cooldown: 6,
			behavior: [
				"DL:amogus_seed|DL:amogus_seed AND M2|DL:amogus_seed",
				"DL:amogus_seed|C2:amogus|DL:amogus_seed",
				"DL:amogus_seed|SW:amogus_seed AND DL:amogus_seed AND M1|DL:amogus_seed"
			],
			category:"structures",
			insulate: true,
			state: "solid",
			density: 2018,
		}

	//BACTERIA ##

		elements.must = {
			color: ["#0C433B","#2C3E2E","#173E2B","#053D20"],
			behavior: behaviors.SUPPORTPOWDER,
			category: "special",
			state: "solid",
			density: 1200,
			conduct: 0.11
		};

		elements.water.reactions.bread = {
			elem2: "must", chance: 0.005
		};

		elements.water.reactions.toast = {
			elem2: "must", chance: 0.005
		};

		elements.water.reactions.must = {
			elem2: "ripe_must", chance: 0.005
		};

		elements.ripe_must = {
			color: ["#248f6d","#597a57","#3c8054","#1d8b40"],
			behavior: behaviors.SUPPORTPOWDER,
			category: "special",
			state: "solid",
			reactions: {
				must: {elem1: "ripe_must", chance: 0.0004}
			},
			density: 1210,
			conduct: 0.15,
			breakInto: "bacteria_glob", //"the original name "bunch of bacteria" doesn't fit the sandboxels context IMO
			hardness: 0.1
		};

		elements.bacteria_glob = {
			color: ["#e34634","#c24332", "#d1703f"],
			behavior: [
				"XX|XX|XX",
				"M2%25|XX|M2%25",
				"M2|M1|M2"
			],
			reactions: {
				"metal_scrap": {elem1: "bacteria", elem2: ["metal_scrap",null] }, //substituting for redstone torches, which will be implemented later alongside another mod
				"charcoal": {elem1: "replacer_bacteria", elem2: ["charcoal",null] } //substituting for coal, which will also be added later
			},
			category: "special",
			state: "liquid",
			density: 1190,
			conduct: 0.15,
			breakInto: "bacteria_glob" //"the original name "bunch of bacteria" doesn't fit the sandboxels context IMO
		};

		bacteriaBlacklist = ["bacteria","replacer_bacteria","jammer_block"];

		elements.bacteria = {
			color: ["#e6d3f2", "#c098d9", "#6e318f", "#6e318f"],
			behavior: behaviors.WALL,
			tick: function(pixel) {
				pixel.target ??= null;
				if(pixel.charge) { //when shocked
					if(!pixel.target) {
						var elemsAbove = [];
						var pX = pixel.x; var pY = pixel.y;
						for(var counter = pY - 1; counter > 0; counter--) {
							//console.log(pX,pY);
							if(isEmpty(pX,counter,true)) {
								break
							};
							var newPixel = pixelMap[pX][counter];
							var newElement = newPixel.element;
							if(bacteriaBlacklist.includes(newElement)) { break };
							if(!(elemsAbove.includes(newElement))) {
								elemsAbove.push(newElement)
							};
						};
						if(elemsAbove.length == 0) {
							return
						} else {
							pixel.target = elemsAbove
							//console.log(pixel.target);
							pixel.charge = 0;
						}
					};
					if(!isEmpty(pixel.x,pixel.y+1,true)) { //check if a pixel exists below to convert
						if(!pixel.active && !(bacteriaBlacklist.includes(pixelMap[pixel.x][pixel.y+1].element))) { //exclude certain blocks and only fire once
							pixel.active = true
						}
					}
				};
				if(pixel.active) { 
					var pX = pixel.x; var pY = pixel.y;
					if(pixel.target) { //safety
						for(i = 0; i < adjacentCoords.length; i++) { //iterate through neighbor spots
							var nX = pX + adjacentCoords[i][0]; var nY = pY + adjacentCoords[i][1];
							if(!isEmpty(nX,nY,true)) { //check for neighbors
								var newPixel = pixelMap[nX][nY];
								if(pixel.target.includes(newPixel.element)) { //if neighbor element is the target
									changePixel(newPixel,pixel.element) //change neighbors to itself
									newPixel.target = pixel.target //set new bacteria target
									newPixel.active = true //activate new bacteria
								}
							}
						}
					}
					if(Math.random() < 0.02) { //decay
						if(!isEmpty(pixel.x,pixel.y)) { //check if position is empty
							deletePixel(pixel.x,pixel.y)
						}
					}
				}
			},
			category: "special",
			state: "solid",
			density: 1200,
			conduct: elements.water.conduct + 0.1,
		},

		elements.replacer_bacteria = {
			color: ["#fcbbc0", "#f28089", "#f04f5c", "#f04f5c"],
			behavior: behaviors.WALL,
			tick: function(pixel) {
				pixel.target ??= null;
				pixel.replacement ??= null;
				if(pixel.charge) { //when shocked
					if(!pixel.target) {
						if(!isEmpty(pixel.x,pixel.y+1,true)) {
							pixelBelowElement = pixelMap[pixel.x][pixel.y+1].element;
							if(!(bacteriaBlacklist.includes(pixelBelowElement))) {
								pixel.target = pixelBelowElement;
							}
						}
					};
					if(!pixel.replacement) {
						if(!isEmpty(pixel.x,pixel.y-1,true)) {
							pixelAboveElement = pixelMap[pixel.x][pixel.y-1].element
							if(!(bacteriaBlacklist.includes(pixelAboveElement))) {
								pixel.replacement = pixelAboveElement;
							}
						}
					};	
					if(!isEmpty(pixel.x,pixel.y+1,true)) { //check if a pixel exists below to convert
						if((!pixel.active) && pixel.target && pixel.replacement) { //only fire once
							pixel.active = true
						}
					}
				};
				if(pixel.active) { 
					var pX = pixel.x; var pY = pixel.y;
					if(pixel.target) { //safety
						for(i = 0; i < adjacentCoords.length; i++) { //iterate through neighbor spots
							var nX = pX + adjacentCoords[i][0]; var nY = pY + adjacentCoords[i][1];
							if(!isEmpty(nX,nY,true)) { //check for neighbors
								var newPixel = pixelMap[nX][nY];
								if(pixel.target == newPixel.element) { //if neighbor element is the target
									changePixel(newPixel,pixel.element) //change neighbors to itself
									newPixel.target = pixel.target //set new bacteria target
									newPixel.replacement = pixel.replacement //set new bacteria replacement
									newPixel.active = true //activate new bacteria
								}
							}
						}
					}
					if(Math.random() < 0.05) { //decay
						if(!isEmpty(pixel.x,pixel.y)) { //check if position is empty
							changePixel(pixel,pixel.replacement)
						}
					}
				}
			},
			category: "special",
			state: "solid",
			density: 1200,
			conduct: elements.water.conduct + 0.1,
		},

		elements.jammer_block = {
			color: "#c0cf7e",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				neighbors = [[-1,0],[0,-1],[1,0],[0,1]]
				if(pixel.charge) { //when shocked
					for (var i = 0; i < width; i++) {
						for (var j = 0; j < height; j++) {
							if(isEmpty(i,j,true) == false) {
								if(pixelMap[i][j].element == "bacteria") {
									if(isEmpty(i,j,true) == false) { deletePixel(i,j) }
								} else if(pixelMap[i][j].element == "replacer_bacteria") {
									if(pixelMap[i][j].replacement) {
										if(isEmpty(i,j,true) == false) { changePixel(pixelMap[i][j],pixelMap[i][j].replacement) }
									} else {
										if(isEmpty(i,j,true) == false) { deletePixel(i,j) }
									}
								}
							}
						}
					}
				}
			},
			category: "special",
			state: "solid",
			density: 3000,
			conduct: elements.water.conduct + 0.1,
		}

	//REPLACE ALL

		elements.replacer_bacteria.tempHigh = 10000000000;
		elements.replacer_bacteria.stateHigh = "replace_all";

		elements.replace_all = {
			color: "#ef7f3f",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				if(pixel.charge) { //when shocked
				//console.log("ouch")
					if(!outOfBounds(pixel.x,pixel.y+1) && !isEmpty(pixel.x,pixel.y+1) && !outOfBounds(pixel.x,pixel.y-1) && !isEmpty(pixel.x,pixel.y-1)) { //check if pixels exists above and below to store the elements of
						//console.log("elems stored")
						if(pixelMap[pixel.x][pixel.y-1].element != pixel.element) { //exclude self and only fire once
							//console.log("self excluded from replacement")
							pixel.target = pixelMap[pixel.x][pixel.y+1].element 
							//console.log("target set to " + pixel.target)
							pixel.replacement = pixelMap[pixel.x][pixel.y-1].element 
							//console.log("replacement set to " + pixel.replacement)
							pixel.active = true
							//console.log("replacer activated")
						}
					}
				}
				if(pixel.active) {
					//console.log("is this on now?")
					if(pixel.target && pixel.replacement) { //safety
						//console.log("target and replacement exist, iterating...")
						for (var i = 0; i < width; i++) {
							for (var j = 0; j < height; j++) {
								if(isEmpty(i,j,true) == false) {
									//console.log("pixel at (" + i + "," + j + ") exists")
									if(pixelMap[i][j].element == pixel.target) {
									//console.log(pixel.target + " detected, replacing")
										if(isEmpty(i,j,true) == false) { changePixel(pixelMap[i][j],pixel.replacement) }
									}
								}
							}
						}
					}
					pixel.active = false //de-activate
					if(pixel.charge) { delete pixel.charge}
					if(pixel.chargeCD) { delete pixel.chargeCD}
				}
			},
			category: "special",
			state: "solid",
			density: 1,
			conduct: elements.water.conduct + 0.1,
		};

	//CONFIGURABLE PIXEL SIZE ##

		//The "pixelSize" query parameter sets the size of the pixels; this is inversely proportional to the pixel "resolution", so bigger numbers mean less pixels fit on the screen and smaller numbers mean that more pixels will fit.
		//Depending on your screen's size, the default pixelSize is either 5 or 6 (6 on larger screens).
		//Making the pixels twice as big will decrease the pixel capacity by *slightly over* 4, and the reverse is also true. (I don't know why that is.)

		if(urlParams.get('pixelSize') != null) { //null check
			pixelSize = urlParams.get('pixelSize')
			if(isNaN(pixelSize) || pixelSize === "" || pixelSize === null) { //NaN check
				//Vanilla code
				//[Vanilla comment] If the screen size is under 768px, set pixelSize to 5, otherwise 6
				if (window.innerWidth < 700) {
					pixelSize = 5;
				} else {
					pixelSize = 6;
				}
			}
			pixelSize = parseFloat(pixelSize)
			pixelSize = Math.min(194.73749999999999,Math.max(pixelSize,0.05))
		} else {
			//Vanilla code
			//[Vanilla comment] If the screen size is under 768px, set pixelSize to 5, otherwise 6
			if (window.innerWidth < 700) {
				pixelSize = 5;
			} else {
				pixelSize = 6;
			}
		}

	//OLD SAVE SYSTEM MOD ##

		//used for reasons unrelated to dependencies
		try {
			if(typeof(rebuildCurrentPixels) !== "function") {
				rebuildCurrentPixels = function() {
						var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
				for(pmi = 0; pmi < pixelMap.length; pmi++) {
					var pixelMapPart = pixelMap[pmi];
					for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
						var pixelMapUnit = pixelMapPart[pmj];
						if(typeof(pixelMapUnit) === "object") {
							if(pixelMapUnit !== null) {
								currPix.push(pixelMapUnit);
							};
						};
					};
				};
				currentPixels = currPix;
					};
			};

			//https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
			function storageAvailable(type) {
			  let storage;
			  try {
				storage = window[type];
				const x = "__storage_test__";
				storage.setItem(x, x);
				storage.removeItem(x);
				return true;
			  } catch (e) {
				return (
				  e instanceof DOMException &&
				  // everything except Firefox
				  (e.code === 22 ||
					// Firefox
					e.code === 1014 ||
					// test name field too, because code might not be present
					// everything except Firefox
					e.name === "QuotaExceededError" ||
					// Firefox
					e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
				  // acknowledge QuotaExceededError only if there's something already stored
				  storage &&
				  storage.length !== 0
				);
			  }
			}

			function zeroToNull(val) {
				if(val === 0) { return null };
				return val;
			};

			if(!localStorage.slSaveSettings) {
				localStorage.setItem("slSaveSettings", '{"roundTemps":true}');
			};

			slSaveSettings = JSON.parse(localStorage.slSaveSettings);

			function epsilonRound(num,precision) {
				return Math.round((num + Number.EPSILON) * (10 ** precision)) / (10 ** precision);
			};

			function getSimulationState() {
				var simulationState = {
					//currentPixels: currentPixels,
					pixelMap: structuredClone ? structuredClone(pixelMap) : JSON.parse(JSON.stringify(pixelMap)),
					width: width,
					height: height,
					pixelTicks: pixelTicks,
					pixelSize: pixelSize,
					settings: settings,
					version: 1,
					enabledMods: localStorage.enabledMods
				};
				for(i = 0; i < simulationState.pixelMap.length; i++) {
					var column = simulationState.pixelMap[i];
					for(j = 0; j < column.length; j++) {
						if(column[j] === null || typeof(column[j]) === "undefined") {
							column[j] = 0;
						};
					};
				};
				if(slSaveSettings.roundTemps) {
					for(i = 0; i < simulationState.pixelMap.length; i++) {
						var column = simulationState.pixelMap[i];
						for(j = 0; j < column.length; j++) {
							var pixel = column[j];
							if(pixel?.temp) {
								pixel.temp = epsilonRound(pixel.temp,3);
							};
						};
					};
				};
				return simulationState;
			};

			//https://stackoverflow.com/a/46118025
			function copyToClipboard(text) {
				var dummy = document.createElement("textarea");
				// to avoid breaking orgain page when copying more words
				// cant copy when adding below this code
				// dummy.style.display = 'none'
				document.body.appendChild(dummy);
				//Be careful if you use textarea. setAttribute('value', value), which works with "input" does not work with "textarea". – Eduard
				dummy.value = text;
				dummy.select();
				document.execCommand("copy");
				document.body.removeChild(dummy);
			}

			const saveTemplateAsFile = (filename, dataObjToWrite) => { //from https://stackoverflow.com/a/65939108
				const blob = new Blob([JSON.stringify(dataObjToWrite)], { type: "text/json" });
				const link = document.createElement("a");

				link.download = filename;
				link.href = window.URL.createObjectURL(blob);
				link.dataset.downloadurl = ["text/json", link.download, link.href].join(":");

				const evt = new MouseEvent("click", {
					view: window,
					bubbles: true,
					cancelable: true,
				});

				link.dispatchEvent(evt);
				link.remove()
			};

			function formatCurrentDate() { //derived from https://gist.github.com/Ivlyth/c4921735812dd2c0217a
				var d = new Date();
				var year = d.getFullYear().toString();

				var month = (d.getMonth()+1).toString();
				if(month.length == 1) { month = "0" + month };

				var day = d.getDate().toString();
				if(day.length == 1) { day = "0" + day };

				var hour = d.getHours().toString();
				if(hour.length == 1) { hour = "0" + hour };

				var minute = d.getMinutes().toString();
				if(minute.length == 1) { minute = "0" + minute };

				var second = d.getSeconds().toString();
				if(second.length == 1) { second = "0" + second };

				var date_format_str = `${year}-${month}-${day} ${hour}-${minute}-${second}`;
				return date_format_str;
			};

			function savePrompt() {
				var filename = prompt("Please enter the desired filename, without the .json (defaults to current date)");
				if(filename === null) {
					return false;
					};
					if(filename === "") {
						filename = `Sandboxels save ${formatCurrentDate()}`;
				};
				filename += ".json";
				downloadSave(filename)
			};

			function downloadSave(filename=null) {
				if(filename === null) {
					filename = `Sandboxels save ${formatCurrentDate()}.json`;
				};
				saveTemplateAsFile(filename, getSimulationState());
			};

			rebuildCurrentPixels ??= function() {
				var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
				for(pmi = 0; pmi < pixelMap.length; pmi++) {
					var pixelMapPart = pixelMap[pmi];
					for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
						var pixelMapUnit = pixelMapPart[pmj];
						if(typeof(pixelMapUnit) === "object") {
							if(pixelMapUnit !== null) {
								currPix.push(pixelMapUnit);
							};
						};
					};
				};
				currentPixels = currPix;
			};

			function quicksave(doSuccessAlert=true,doFailAlert=true) {
				if(storageAvailable("localStorage")) {
					rebuildCurrentPixels();
					localStorage.setItem("quicksave",JSON.stringify(getSimulationState()));
					if(doSuccessAlert) { alert("Quicksave saved") };
					return true;
				} else {
					if(doFailAlert) { alert("Could not save quicksave in localStorage") };
					throw new Error("Could not save quicksave in localStorage");
				};
			};

			function quickload(pause=true,doSuccessAlert=true,doFailAlert=true) {
				clearAll();
				rebuildCurrentPixels();
				var save = localStorage.getItem("quicksave");
				if(!save) {
					if(doFailAlert) { alert("No save exists") };
					return false;
				} else {
					importJsonState(JSON.parse(save));
					if(doSuccessAlert) { alert("Quicksave loaded") };
					if(pause) {
						paused = true;
						document.getElementById("pauseButton").setAttribute("on","true");
					} else {
						paused = false;
						document.getElementById("pauseButton").setAttribute("on","false");
					};
					return true;
				};
				rebuildCurrentPixels();
			};

			function copySaveJSON(doAlert=true) {
				copyToClipboard(JSON.stringify(getSimulationState()));
				if(doAlert) { alert("Save copied as JSON") };
			};

			function loadFile() {
				//Initialize
				var json;

				//load JSON
				var file = document.getElementById('myfile').files[0];
				if(file === undefined) {
					if(document.getElementById("fileFormStatus") !== null) {
						document.getElementById("fileFormStatus").style.color = "red";
						document.getElementById("fileFormStatus").innerHTML = "No file was uploaded!";
					};
					throw new Error("No file was uploaded");
				};
				var reader = new FileReader();
				reader.readAsText(file, 'UTF-8');
				//after loading
				reader.onload = function(evt) {
					json = evt.target.result;

					//validate
					try {
						json = JSON.parse(json);
					} catch (error) {
						if(document.getElementById("fileFormStatus") !== null) {
							document.getElementById("fileFormStatus").style.color = "red";
							document.getElementById("fileFormStatus").innerHTML = "The file wasn't valid JSON!";
						};
						throw error;
					};

					if(document.getElementById("fileFormStatus") !== null) {
						document.getElementById("fileFormStatus").style.color = "yellow";
						document.getElementById("fileFormStatus").innerHTML = "JSON was parsed successfully";
					};

					//return json;
					return importJsonState(json);
				};
			};

			function loadText() {
				//Initialize
				var json;

				//load JSON
				var json = document.getElementById('mytext').value;
				if(json === "") {
					if(document.getElementById("textFormStatus") !== null) {
						document.getElementById("textFormStatus").style.color = "red";
						document.getElementById("textFormStatus").innerHTML = "No text was present!";
					};
					throw new Error("No text was present");
				};

				//validate
				try {
					json = JSON.parse(json);
				} catch (error) {
					if(document.getElementById("textFormStatus") !== null) {
						document.getElementById("textFormStatus").style.color = "red";
						document.getElementById("textFormStatus").innerHTML = "The text wasn't valid JSON!";
					};
					throw error;
				};

				if(document.getElementById("textFormStatus") !== null) {
					document.getElementById("textFormStatus").style.color = "yellow";
					document.getElementById("textFormStatus").innerHTML = "JSON was parsed successfully";
				};

				//return json;
				return importJsonState(json);
			};

			function importJsonState(json) {
				//check keys
				var jsonKeys = Object.keys(json);
				var requiredKeys = [/*"currentPixels", */"pixelMap", "width", "height", "pixelSize"];
				var hasrequiredKeys = true;
				for(i = 0; i < requiredKeys.length; i++) {
					var key = requiredKeys[i];
					if(!jsonKeys.includes(key)) {
						hasrequiredKeys = false;
						break;
					};
				};
				if(!hasrequiredKeys) {
					if(document.getElementById("fileFormStatus") !== null) {
						document.getElementById("fileFormStatus").style.color = "red";
						document.getElementById("fileFormStatus").innerHTML = "JSON is not a valid save!";
					};
					throw new Error("JSON is missing required keys!");
				};

				//Set values
				width = json.width;
				height = json.height;
				pixelSize = json.pixelSize;
				pixelTicks = (json.pixelTicks ?? 0);
				//currentPixels = json.currentPixels;
				for(i = 0; i < json.pixelMap.length; i++) {
					json.pixelMap[i] = json.pixelMap[i].map(x => zeroToNull(x));
				};
				pixelMap = json.pixelMap;
				if(json.settings) {
					settings = json.settings;
				};

				//enabledMods handling {
					var enMods = "[]";
					if(typeof(json.enabledMods) !== "undefined") {
						enMods = json.enabledMods;
					};
					enMods = JSON.parse(enMods);
					//console.log(enMods);

					var currentEnmods = JSON.parse(localStorage.enabledMods); //should already exist if you're using this mod in the first place
					for(emi = 0; emi < enMods.length; emi++) { //load mods additively to prevent self-disabling and the inconvenience of having to readd your mod list when you get bored
						var mod = enMods[emi];
						if(!currentEnmods.includes(mod)) {
							currentEnmods.push(mod);
						};
					};
					localStorage.setItem("enabledMods",JSON.stringify(currentEnmods));
					if((enMods.length > 0 && enMods[0] !== modName) || enMods.length > 1) {
						alert("Saves with other mods might require a reload (and then importing the save file again).\nIf you see a blank screen, try refreshing and loading the file again before you panic.");
					};
				//}

				var currPix = []; //rebuild currentPixels from pixelMap to try to fix bug
				for(pmi = 0; pmi < pixelMap.length; pmi++) {
					var pixelMapPart = pixelMap[pmi];
					for(pmj = 0; pmj < pixelMapPart.length; pmj++) {
						var pixelMapUnit = pixelMapPart[pmj];
						if(typeof(pixelMapUnit) === "object") {
							if(pixelMapUnit !== null) {
								currPix.push(pixelMapUnit);
							};
						};
					};
				};
				currentPixels = currPix;

				if(document.getElementById("fileFormStatus") !== null) {
					document.getElementById("fileFormStatus").style.color = "green";
					document.getElementById("fileFormStatus").innerHTML = "JSON was loaded successfully.";
				};
				return true;
			};

			function setPixelSize(size=null) {
				if(size === null) {
					if(document.getElementById("pixelSize") !== null) {
						size = document.getElementById("pixelSize").value;
					} else {
						throw new Error("No size could be read");
					};
				};

				size = parseFloat(size);
				if(isNaN(size) || size <= 0) { //NaN check
					if(document.getElementById("pixelSizeStatus") !== null) {
						document.getElementById("pixelSizeStatus").style.color = "red";
						document.getElementById("pixelSizeStatus").innerHTML = "Pixel size is empty or invalid";
					};
					throw new Error("NaN or negative size");
				};

				if(document.getElementById("pixelSizeStatus") !== null) {
					document.getElementById("pixelSizeStatus").style.color = "green";
					document.getElementById("pixelSizeStatus").innerHTML = "Pixel size set successfully";
				};
				pixelSize = size;
				return true;
			};

		var saveLoaderDescription = `<div>
<span id="downloadButton" onclick=savePrompt() style="color: #FF00FF;">Download simulation</span>
<span id="copyButton" onClick=copySaveJSON() style="color: #FF66FF;">Alternatively, copy simulation JSON</span>

<span id="fileFormStatus">No file loader status</span>
One file, please: <input type="file" name="Save upload button" id="myfile">
<button id="loadButton" onclick=loadFile() style="color: #FF00FF;">Load File</button>
<span>Or paste JSON</span>
<span id="textFormStatus">No text loader status</span>
<input name="Text load field" id="mytext">
<button id="textLoadButton" onclick=loadText() style="color: #FF66FF;">Load Text</button>

<span id="pixelSizeStatus">No size setter status</span>
Pixel size (rendering only): <input id="pixelSize"> (Use if the save looks cut off)
<button id="sizeButton" onclick=setPixelSize() style="color: #FF00FF;">Set pixel size</button>
</div>`;

			elements.save_loader = {
				behavior: behaviors.SELFDELETE,
				excludeRandom: true,
				color: "#FFFFFF",
				desc: saveLoaderDescription,
			};

			//Somehow, for some illogical reason, quicksaving causes updateStats to somehow disregard its if-statement and fucking TypeError when you mouse over an empty space; this is an attempt to fix it with overkill-level existence checks.
			function updateStats() {
				var statsDiv = document.getElementById("stats");
				var stats = "<span id='stat-pos' class='stat'>x"+mousePos.x+",y"+mousePos.y+"</span>";
				stats += "<span id='stat-pixels' class='stat'>Pxls:" + currentPixels.length+"</span>";
				stats += "<span id='stat-tps' class='stat'>" + tps+"tps</span>";
				stats += "<span id='stat-ticks' class='stat'>" + pixelTicks+"</span>";
				if ((typeof pixelMap).length === 9) { return; }
				if (pixelMap[mousePos.x] !== undefined) {
					var currentPixel = pixelMap[mousePos.x][mousePos.y];
					if (typeof(currentPixel) !== "undefined" && currentPixel && currentPixel !== undefined && currentPixel.element) {
						var displayName;
						var displayElement = (elements[currentPixel?.element]?.name || currentPixel?.element);
						if(currentPixel?.displayText) {
							displayName = displayElement + ` (${currentPixel?.displayText})`
						} else {
							displayName = displayElement
						};
						stats += "<span id='stat-element' class='stat'>Elem:"+displayName+"</span>";
						stats += "<span id='stat-temperature' class='stat'>Temp:"+formatTemp(currentPixel.temp)+"</span>";
						if (currentPixel.charge) {
							stats += "<span id='stat-charge' class='stat'>C"+currentPixel.charge+"</span>";
						}
						if (currentPixel.burning) {
							stats += "<span id='stat-burning' class='stat'>Burning</span>";
						}
					}
				}
				if (shiftDown) {
					stats += "<span id='stat-shift' class='stat'>"+shiftDownTypes[shiftDown]+"</span>";
				}
				// If the view is not null, show the view in all caps
				if (view !== null) {
					stats += "<span id='stat-view' class='stat'>"+viewKey[view]+"</span>";
				}
				statsDiv.innerHTML = stats;
			}

			quickloadIsPaused = true;
			var qsb = document.createElement("button");
			qsb.setAttribute("id","quicksaveButton");
			qsb.setAttribute("onclick","quicksave()");
			qsb.innerText = "Quicksave";
			document.getElementById("gameDiv").before(qsb);
			qsb.after(document.createTextNode(String.fromCharCode(160)));
			var qlb = document.createElement("button");
			qlb.setAttribute("id","quickloadButton");
			qlb.setAttribute("onclick","clearAll(); quickload(quickloadIsPaused ?? true)");
			qlb.innerText = "Quickload";
			document.getElementById("gameDiv").before(qlb);
			qlb.after(document.createTextNode(String.fromCharCode(160,160)));
			var qpc = document.createElement("input");
			qpc.setAttribute("type","checkbox");
			qpc.setAttribute("id","quickloadPausedInput");
			qpc.setAttribute("onchange","quickloadIsPaused = this.checked;");
			qpc.checked = true;
			var qpcd = document.createElement("span");
			qpcd.setAttribute("id","quickloadPausedInputLabel");
			qpcd.innerText = "Pause after quickloading?";
			document.getElementById("gameDiv").before(qpc);
			qpc.after(document.createTextNode(String.fromCharCode(160)));
			document.getElementById("gameDiv").before(qpcd);
			document.getElementById("gameDiv").before(document.createElement("br"));

			quickSlDetectorLastKeys = [];
			justPromptedQuickSL = false;

			document.addEventListener("keydown", function(e) { //prop prompt listener
				quickSlDetectorLastKeys.push(e.key);
				if(quickSlDetectorLastKeys.length > 3) {
					quickSlDetectorLastKeys.shift();
				};
				justPromptedQuickSL = false;
			});

			document.addEventListener("keydown", function(e) { //prop prompt listener
				if (quickSlDetectorLastKeys.join(",") == "(,(,L") {
					e.preventDefault();
					var confirm = prompt("Are you sure you want to quickLOAD? (Type 'yes' to confirm)");
					if(confirm == "yes") {
						clearAll();
						quickload(true,false,true);
					};
					justPromptedQuickSL = true;
					quickSlDetectorLastKeys = [];
				} else if (quickSlDetectorLastKeys.join(",") == "(,(,S") {
					e.preventDefault();
					var confirm = prompt("Are you sure you want to quickSAVE? (Type 'yes' to confirm)");
					if(confirm == "yes") {
						quicksave(true,true);
					};
					justPromptedQuickSL = true;
					quickSlDetectorLastKeys = [];
				};
			});

		} catch (error) {
			alert(`save_loading error: ${error.toString()}`);
		};

	//ASSORTED EXPERIMENTS

		function radialTest(centerX,centerY,integer,chosenElement,createPixels=true,replacePixels=true,radialColor=false) {
			if(!elements[chosenElement]) {
				alert("Element " + chosenElement + " doesn't exist!");
				return false;
			};
			integer = Math.round(integer);
			for(i = 1; i < width; i++) {
				for(j = 1; j < height; j++) {
					var distance = pyth(centerX,centerY,i,j);
					if(Math.round(distance) % integer == 0) {
						if(isEmpty(i,j,false) && createPixels) {
							createPixel(chosenElement,i,j);
						} else if(!isEmpty(i,j,true) && !outOfBounds(i,j) && replacePixels) {
							changePixel(pixelMap[i][j],chosenElement);
						};
					};
				};
			};
			return true;
		};

		function rctest(centerX,centerY,integer,chosenElement,createPixels=true,replacePixels=true,distanceScale=1,saturation=50,luminance=50) {
			saturation = Math.max(0,Math.min(100,saturation))
			luminance = Math.max(0,Math.min(100,luminance))
			if(!elements[chosenElement]) {
				alert("Element " + chosenElement + " doesn't exist!");
				return false;
			};
			integer = Math.round(integer);
			for(i = 1; i < width; i++) {
				for(j = 1; j < height; j++) {
					var distance = pyth(centerX,centerY,i,j);
					if(Math.round(distance) % integer == 0) {
						if(isEmpty(i,j,false) && createPixels) {
							createPixel(chosenElement,i,j);
							var distancePrevariable = Math.round(distance * distanceScale) % 360
							pixelMap[i][j].color = `hsl(${distancePrevariable},${saturation}%,${luminance}%)`;
						} else if(!isEmpty(i,j,true) && !outOfBounds(i,j) && replacePixels) {
							changePixel(pixelMap[i][j],chosenElement);
							var distancePrevariable = Math.round(distance * distanceScale) % 360
							pixelMap[i][j].color = `hsl(${distancePrevariable},${saturation}%,${luminance}%)`;
						};
					};
				};
			};
			return true;
		};

		var canSupportWithEdge = function(x,y) {
			if(outOfBounds(x,y)) { //count edges
				return true;
			} else {
				if(!isEmpty(x,y,true)) { //if there is a pixel
					if(elements[pixelMap[x][y].element].state === "solid") {
						return true;
					} else {
						return false;
					};
				};
			};
		};

		/*var csweCharacter = function(x,y) { //Debug function
			if(canSupportWithEdge(x,y)) {
				return "X";
			} else {
				return ".";
			};
		};*/

		var powderMovementSnippet = function(pixel) { //Unused
			if (!tryMove(pixel, pixel.x, pixel.y+1)) {
				if (Math.random() < 0.5) {
					if (!tryMove(pixel, pixel.x+1, pixel.y+1)) {
						tryMove(pixel, pixel.x-1, pixel.y+1);
					};
				} else {
					if (!tryMove(pixel, pixel.x-1, pixel.y+1)) {
						tryMove(pixel, pixel.x+1, pixel.y+1);
					};
				};
			};
		};

		var sturdyMovementSnippet = function(pixel) { //readability wrapper
			tryMove(pixel, pixel.x, pixel.y+1);
		};

		if(typeof(includesArray) === "undefined") {
			function includesArray(parentArray, testArray) { //from portals.js
				for (let i = 0; i < parentArray.length; i++) {
					if (parentArray[i].every(function(value, index) { return value === testArray[index]})) {
						return true;
					};
				};
				return false;
			};
		};

		ddAnchorArray = [];

		distanceScale = 15;

		elements.hsl_tool_test = { //with help from ryan
			color: ["#cf3030","cf7f30","#cfcf30"],
			tool: function(pixel) {
				pixel.color = "hsl("+(pixelTicks%360)+",50%,50%)"
			},
			customColor: true,
			category: "color tools", //the toolbar is getting cluttered
			excludeRandom: true, //the toolbar is getting cluttered
		};

		elements.temporal_wall_test = {
			color: ["#8f8f8f","3f3f3f"],
			behavior: behaviors.WALL,
			properties: {
				counter: 1,
				active: true
			},
			tick: function(pixel) {
				for(i = 0; i < 1; i++) { //dummy for
					if(!pixel) {
						break;
					};
					if(pixel.active) {
						if(pixel.counter == width) {
							pixel.active = false;
						};
						if(!isEmpty(pixel.counter,pixel.y) || outOfBounds(pixel.counter,pixel.y)) {
							pixel.counter++;
						} else {
							createPixel("wall",pixel.counter,pixel.y);
							pixel.counter++;
						};
					};
				};
			},
			state: "solid",
			density: 1000,
			category: "special",
		};

		elements.steel_silk = {
			color: ["#DCDEDF", "#C7C9CA", "#B9BBBC"],
			tick: function(pixel) {
				var px = pixel.x;
				var py = pixel.y;

				var supportCondition1 = (canSupportWithEdge(px-1,py-1) && canSupportWithEdge(px+1,py-1))	// V shape
				var supportCondition2 = (canSupportWithEdge(px-1,py) && canSupportWithEdge(px+1,py)) 		// - shape
				var supportCondition3 = (canSupportWithEdge(px-1,py+1) && canSupportWithEdge(px+1,py+1))	// Λ shape
				var supportCondition4 = (canSupportWithEdge(px-1,py+1) && canSupportWithEdge(px+1,py-1))	// / shape
				var supportCondition5 = (canSupportWithEdge(px-1,py-1) && canSupportWithEdge(px+1,py+1))	// \ shape
				var supportCondition6 = (canSupportWithEdge(px-1,py-1) && canSupportWithEdge(px+1,py))		// '- shape
				var supportCondition7 = (canSupportWithEdge(px-1,py+1) && canSupportWithEdge(px+1,py))		// ,- shape
				var supportCondition8 = (canSupportWithEdge(px+1,py-1) && canSupportWithEdge(px-1,py))		// -' shape
				var supportCondition9 = (canSupportWithEdge(px+1,py+1) && canSupportWithEdge(px-1,py))		// -, shape
				//var supportCondition6 = xor(canSupportWithEdge(px-1,py-1),canSupportWithEdge(px+1,py+1))	// one-side support
				var supports = (supportCondition1 || supportCondition2 || supportCondition3 || supportCondition4 || supportCondition5 || supportCondition6 || supportCondition7 || supportCondition8 || supportCondition9);
				/*if(pixelTicks % 10 == 0) {
					console.log(`Pixel at (${px},${py})`);
					console.log(`> ${csweCharacter(px-1,py-1)} ${csweCharacter(px+1,py-1)}\n> ${csweCharacter(px-1,py*1)} ${csweCharacter(px+1,py*1)}\n> ${csweCharacter(px-1,py+1)} ${csweCharacter(px+1,py+1)}`);
				};*/

				if (pixel.start === pixelTicks) {return}
				if (pixel.charge && elements[pixel.element].behaviorOn) {
					pixelTick(pixel)
				};

				if(!supports) {
					powderMovementSnippet(pixel);
				};
			},
			tempHigh: 1455.5,
			stateHigh: ["molten_steel", "molten_steel", "molten_steel", "molten_steel", "molten_steel", "molten_steel", "molten_steel", "molten_steel", "molten_steel", null],
			category: "solids",
			conduct: 0.48,
			hardness: 0.79,
			movable: true,
			category: "solids",
			state: "solid",
			density: 6850,
			breakInto: "metal_scrap",
		};

		function distanceScalePrompt() {
			var _distanceScale = prompt("Enter the value you want to use");

			//value check
			if(isNaN(parseFloat(_distanceScale))) {
				//empty string
				if(_distanceScale === "" || _distanceScale === null) {
					alert("No value was specified! Defaulting to 15");
					_distanceScale = 15;
				} else {
					alert("Invalid value! The value must be a number (defaulting to 15)");
					_distanceScale = 15;
				};
			};
			_distanceScale = parseFloat(_distanceScale);

			distanceScale = _distanceScale;
			updateDistanceDisplayDescription();
		};

		elements.distance_display = {
			color: "#00FFFF",
			properties: {
				distanceGetter: null
			},
			tick: function(pixel) {
				var distance = Infinity;
				var oldDistance = Infinity;

				//if(!ddAnchorArray) { ddAnchorArray = [] }
				/*if(!Array.isArray(ddAnchorArray)) { ddAnchorArray = [] }
				for (var i = 1; i < width; i++) { //Find and store all anchor pixels
					for (var j = 1; j < height; j++) {
					};
				};*/

				var px = pixel.x;
				var py = pixel.y;

				if(ddAnchorArray.length > 0) {
					for(i = 0; i < ddAnchorArray.length; i++) {
						var newX = ddAnchorArray[i][0];
						var newY = ddAnchorArray[i][1];
						if(isEmpty(newX,newY)) {
							ddAnchorArray.splice(i,1);
						} else {
							var checkPixel = pixelMap[newX][newY];
							if(checkPixel.element !== "distance_display_anchor") {
								ddAnchorArray.splice(i,1);
							} else {
								distanceCandidate = pyth(px,py,newX,newY);
								if(distanceCandidate < distance) {
									distance = pyth(px,py,newX,newY);
								};
							};
						};
					};
				} else {
					distance = null;
				};

				pixel.distanceGetter = distance;

				if(distance !== null) {
					var processedDistance = Math.min(255,Math.max(0,Math.round(distance * distanceScale)));
					pixel.color = `rgb(0,${processedDistance},255)`;
				} else {
					pixel.color = `rgb(0,255,255)`;
				};
			},
			category: "machines",
			state: "solid",
			desc: `It gets more blue the closer it gets to a distance display anchor. The current scale factor is ${distanceScale} (bigger number = smaller blue radius). <span onclick=distanceScalePrompt() style=\"color: #ff00ff;\";>Click here</span> to open the scale prompt.<br/><em>Note: Info pages do not update automatically and must be closed and reopened to show the changed scale.</em>`,
		};

		elements.distance_display_anchor = {
			color: "#0000FF",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var px = pixel.x;
				var py = pixel.y;
				if(!includesArray(ddAnchorArray,[px,py])) {
					ddAnchorArray.push([px,py]);
				};
				pixel.color = "rgb(0,0,255)";
			},
			category: "machines",
			state: "solid",
			desc: `Distance display pixels get blue in its distance.`,
		};

		/*
		blackObject = {r: 0, g: 0, b: 0};
		pinkObject = {r: 255, g: 148, b: 255};

		elements.black_pink_test = {
			color: ["#000000","#FF94FF"],
			behavior: behaviors.WALL,
			properties: {
				offset: Math.floor(Math.random() * (Math.random() > 0.5 ? -1 : 1) * Math.random() * 15)
			},
			tick: function(pixel) {
				if(typeof(pixel.offset) !== "number") {
					pixel.offset = Math.floor(Math.random() * (Math.random() > 0.5 ? -1 : 1) * Math.random() * 15);
				};

				var fraction = Math.min(1.0,Math.max(0.0,scale(pixel.y,1,height-1,0.0,1.0)));

				var color = averageColorObjects(pinkObject,blackObject,fraction);

				var offsettedColor = lightenColor(color,pixel.offset,"rgb");

				pixel.color = offsettedColor;
			},
			category: "machines",
			state: "solid",
			desc: "blackpink in your area",
		};
		*/

		function updateDistanceDisplayDescription() {
			elements.distance_display.desc = `It gets more blue the closer it gets to a distance display anchor. The current scale factor is ${distanceScale} (bigger number = smaller blue radius). <span onclick=distanceScalePrompt() style=\"color: #ff00ff;\";>Click here</span> to open the scale prompt.<br/><em>Note: Info pages do not update automatically and must be closed and reopened to show the changed scale.</em>`;
		};

		function createDownAtFirstAvailableSpace(element,x) {
			//Get the Y of the first empty pixel on a row which is on a full pixel or the bottom of the canvas
			//1. map(x => !x) coerces empty pixels' `undefined` values to !false = true, while full pixels are coerced to !true = false
			//2. spread with false adds a sentinel value for the bottom of the canvas
			//3. slice(1) removes empty (OOB) position at y=0
			//4. indexOf(false) always shows the first matching item
			//5. an offset I don't understand (probably from that slice) shifts the first match to the empty spot above the first full pixel
			var firstEmptyY = [...pixelMap[x].map(obj => !obj),false].slice(1).indexOf(false);

			if(firstEmptyY == -1) {
				return false;
			};

			createPixel(element,x,firstEmptyY);
			return true;
		};

		function createReplacingGases(element,x,y) {
			if(isEmpty(x,y,false)) {
				createPixel(element,x,y);
				return true;
			};
			if(!isEmpty(x,y,true)) {
				var isGas = (elements[pixelMap[x][y].element].state == "gas");
				if(isGas) {
					deletePixel(x,y);
					createPixel(element,x,y);
				};
				return isGas;
			};
		};

		function cdafasIgnoringGas(element,x) {
			//Get the Y of the first empty pixel on a row which is on a full pixel or the bottom of the canvas
			//1. map(x => !x) coerces empty pixels' `undefined` values to !false = true, while full pixels are coerced to !true = false
			//2. spread with false adds a sentinel value for the bottom of the canvas
			//3. slice(1) removes empty (OOB) position at y=0
			//4. indexOf(false) always shows the first matching item
			//5. an offset I don't understand (probably from that slice) shifts the first match to the empty spot above the first full pixel
			var firstEmptyY = [...pixelMap[x].map(obj =>!obj || elements[obj.element].state == "gas"),false].slice(1).indexOf(false);

			if(firstEmptyY == -1) {
				return false;
			};

			createReplacingGases(element,x,firstEmptyY);
			return true;
		};

		elements.temporal_fire_test = {
			color: ["#8f8f8f","3f3f3f"],
			behavior: behaviors.WALL,
			properties: {
				direction: 1,
				counter: 1,
				active: true,
				fromX: null
			},
			tick: function(pixel) {
				if(pixel.fromX == null) {
					pixel.fromX = pixel.x;
				};
				if(!pixel) {
					return;
				};
				if(!pixel.active) {
					return;
				};
				var newX = pixel.fromX + pixel.counter;
				if(outOfBounds(newX,1)) {
					pixel.active = false;
					newX = pixel.fromX + pixel.counter; //reset
					pixel.counter = 1;
					return;
				};
				cdafasIgnoringGas("fire",newX);
				pixel.counter += pixel.direction;
			},
			state: "gas",
			category: "special",
		};

	//ASSORTED ORGANIC COMPOUNDS ##

		/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\
		%TODO: Pentyl line physical properties%
		\%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/


		//Most hydrocarbons are fantastically colored for convenience
		//Benzene ring
			//Benzene is actually yellowish
				//For combinations, it will represent a + shift in hue
		//Isomerism
			//Isomers like isopropane have slightly increased hue and sat
			//L/S (left) isomers are darkened and hued up, while D/R (right) isomers are lightened and hued down
			//Cis- isomers are darkened and trans- isomers are lightened
		//Chain length
			//Methyl line is purple
				//plus benzene = pink
			//Ethyl line is rose
				//plus benzene = red
			//Propyl line is green
				//plus benzene = mint
			//Butyl line is cyan
				//plus benzene = blue
			//Pentyl line is vermillion
				//plus benzene = orange
		//Alcohols
			//Alcohols are paler
		//Bond type
			//Alkanes are lightest
			//Alkenes are darker
			//Alkynes are darkest
			//Benzene is non-ternary with respect to single vs double bond

		//Benzene

			elements.benzene = {
				color: "#edf099",
				behavior: behaviors.LIQUID,
				state: "liquid",
				category: "liquids",
				density: 876,
				burn: 80,
				burnTime: 20,
				burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","dirty_water","dirty_water"],
				reactions: {
					"head": { elem2: "cancer", chance: 0.0017},
					"body": { elem2: "cancer", chance: 0.0017},
				},
				tempLow: 5.53,
				tempHigh: 80.1,
			};

			elements.benzene_gas = {
				density: 2.77 * airDensity,
			};

			elements.benzene_ice = {
				density: 1012,
			};

		//Alk*nes and their substituted benzenes

			//Single carbon line

				//Lowest bond order
					//1 carbon = purple
					elements.methane.color = "#bfabc9";

					elements.liquid_methane ??= {};
					elements.liquid_methane.density = 423;

				//Methene and methyne don't make sense

				//Methanol
					elements.methanol = {
						color: "#d5ced9",
						behavior: behaviors.LIQUID,
						reactions: {
							"virus": { "elem2":null },
							"plague": { "elem2":null },
							"head": { "elem2":"rotten_meat", "chance": 0.8 },
							"body": { "elem2":"rotten_meat", "chance": 0.8 },
						},
						viscosity: 0.56,
						//tempHigh: 64.7,
						burn: 100,
						burnTime: 2,
						fireColor: "#b2c5d1",
						category: "liquids",
						state: "liquid",
						density: 792,
						stain: -0.25,
					}

				//Benzene ver.
					elements.toluene = {
						//meth- purple + benzene hue up = pink
						//liquid initial = more vivid
						color: "#de76cf",
						behavior: behaviors.LIQUID,
						state: "liquid",
						category: "liquids",
						density: 862,
						burn: 80,
						burnTime: 20,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam"],
						reactions: {
							"head": { elem2: "cancer", chance: 0.001 }, //unknown/unclassifiable carcinogenicity
							"body": { elem2: "cancer", chance: 0.001 },
						},
						tempHigh: 110.6,
						tempLow: -95,
					};

					elements.toluene_gas = {
						density: 3.1 * airDensity,
					};

			//Double carbon line

				//Lowest bond order
					//Rose
					elements.ethane = {
						color: "#cfa3bb",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 872, //artifically raised by 400 degrees to prevent interference with ethylbenzene dehydrogenation
						stateHigh: "fire",
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.00015},
							"body": { elem2: "rotten_meat", chance: 0.00015},
						},
						tempLow: -88.5,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 1.3562, //absolute density
					};

					elements.liquid_ethane = {
						tempLow: -182.8,
						density: 544,
					};

				//Double bonds
					//ethylene = ethene
					elements.ethylene = {
						color: "#c991b0", 
						behavior: behaviors.GAS,
						state: "gas",
						category: "gases",
						tempHigh: 425,
						stateHigh: "fire",
						density: 1.18,
						burn: 80,
						burnTime: 20,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","steam","steam"],
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.0001}, //no mechanism for prolonged exposure causing harm
							"body": { elem2: "rotten_meat", chance: 0.0001},
							"benzene_gas": { tempMin: 220, elem1: null, elem2: "ethylbenzene_gas" },
						},
						tempLow: -103.7
					};

					elements.liquid_ethylene = {
						tempLow: -169.2,
						density: 577, //unknown solid density
					};

				//Triple bonds
					//acetylene = ethyne
					elements.acetylene = {
						color: "#b8819f", 
						behavior: behaviors.GAS,
						state: "gas",
						category: "gases",
						reactions: {
							oxygen: { elem1: ["acetylene","oxy_fuel"], elem2: null },
						},
						tick: function(pixel) { //tick-based autoignition point to trigger acetylene fire properties
							if(pixel.temp > 325 && !pixel.burning) {
								pixel.burning = true;
							};
						},
						density: 1.1772, //absolute
						burn: 100,
						burnTime: 10,
						burnTempChange: 20,
						fireSpawnTemp: 2700, //overheat by 500 degrees to compensate for natural cooling effects
						fireSpawnChance: 5, //reduce own flame to reduce said effects from smoke
						fireColor: "#5da8fc",
						burnInto: ["fire","plasma","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam"],
						tempLow: -84,
						stateLow: "acetylene_ice",
					};

					elements.acetylene_ice = {
						color: "#ffa8d8",
						behavior: behaviors.POWDER,
						state: "solid",
						category: "states",
						reactions: {
							liquid_oxygen: { elem1: ["acetylene_ice","oxy_fuel_slush"], elem2: null, changeTemp: false },
							oxygen_ice: { elem1: ["acetylene_ice","oxy_fuel_snow"], elem2: null, changeTemp: false },
						},
						tick: function(pixel) {
							if(pixel.temp > 325 && !pixel.burning) {
								pixel.burning = true;
							};
						},
						density: 720,
						burn: 25, //cold
						burnTime: 5,
						burnTempChange: 20,
						fireSpawnTemp: 2700,
						fireSpawnChance: 5,
						fireColor: "#5da8fc",
						burnInto: ["fire","plasma","fire","fire","fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam"],
						"temp": -100,
						"tempHigh": -84,
						"stateHigh": "acetylene",
						hidden: true,
					};

				//Ethanol (Vanilla)
					elements.alcohol.name = "ethanol";
					elements.alcohol.color = "#d4b9c8";
					elements.alcohol.viscosity = 1.144;
					elements.water.viscosity = 1; //define reference viscosity of 1


				//Benzene ver.
					elements.ethylbenzene = {
						color: "#de7676",
						//ethene's rose plus benzene's yellow = red
						behavior: behaviors.LIQUID,
						state: "liquid",
						category: "liquids",
						density: 867,
						burn: 75,
						burnTime: 25,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
						tempHigh: 136,
						tempLow: -95,
					};

					elements.ethylbenzene_gas = {
						density: 3.7 * airDensity,
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
							"steam": { tempMin: 600, elem1: ["styrene","hydrogen","styrene","hydrogen","styrene","hydrogen","styrene","hydrogen","styrene","hydrogen","styrene","hydrogen","styrene","hydrogen","styrene","hydrogen","toluene","benzene","methane","ethane"], elem2: "steam", temp1: -3, temp2: -3 },
						},
					};

			//Triple carbon line

				//Single bond
					elements.propane.color = "#b8d4a5";
					elements.propane.tempHigh = 493;

				//Double bond
					elements.propylene = { //propene
						color: "#a4c48d",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 458,
						stateHigh: "fire",
						tempLow: -47.6,
						burn: 100,
						burnTime: 5,
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 1.745, //abs. at 25*C
					};

					elements.liquid_propylene = {
						tempLow: -185.2,
						density: 613.9,
					};

				//Triple bond
					elements.propyne = { //also methylacetylene
						color: "#8bad72",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 340,
						stateHigh: "fire",
						tempLow: -25.15,
						burn: 85,
						burnTime: 5,
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 1.6656, //abs. at 25*C
					};

					elements.liquid_propylene = {
						tempLow: -102.7,
						density: 671.963,
					};

				//Propanol
					//Linear
						elements.propanol = {
							color: "#d4e0cc",
							behavior: behaviors.LIQUID,
							reactions: {
								"virus": { "elem2":null },
								"plague": { "elem2":null },
							},
							viscosity: 2.23, //EXERCISE 8: VISCOSITY OF PURE LIQUIDS AND SOLUTIONS
							//tempHigh: 97,
							burn: 100,
							burnTime: 3,
							fireColor: "#ced8de",
							category: "liquids",
							state: "liquid",
							density: 803,
							stain: -0.25,
						}

					//Triangular
						elements.isopropanol = {
							color: "#d1e4c8",
							behavior: behaviors.LIQUID,
							reactions: {
								"virus": { "elem2":null },
								"plague": { "elem2":null },
							},
							viscosity: 2.38, //http://www.ddbst.com/en/EED/PCP/VIS_C95.php (293K is close enough)
							//tempHigh: 82.5,
							burn: 100,
							burnTime: 3,
							fireColor: "#d1c958",
							category: "liquids",
							state: "liquid",
							density: 786,
							stain: -0.25,
						}

				//Benzene ver.
					//more obscure organic compound
					elements.propylbenzene = {
						color: "#92debd",
						behavior: behaviors.LIQUID,
						state: "liquid",
						category: "liquids",
						density: 862,
						burn: 75,
						burnTime: 25,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						reactions: {
							//we can probably still assume that it's carcinogenic because it has the phenyl group
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
						tempHigh: 159.2,
						tempLow: -99.5,
					};

					elements.propylbenzene_gas = {
						density: 4.14 * airDensity,
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
					};

			//Quadruple carbon line

				//Single bond
					elements.butane = {
						color: "#a7dbd9",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 287,
						stateHigh: "fire",
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.00015},
							"body": { elem2: "rotten_meat", chance: 0.00015},
						},
						tempLow: 1,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.076 * airDensity,
					};

					elements.liquid_butane = {
						tempLow: -134,
						density: 604,
					};

					elements.isobutane = {
						color: "#9cbddb",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 460,
						stateHigh: "fire",
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.00015},
							"body": { elem2: "rotten_meat", chance: 0.00015},
						},
						tempLow: -11.7,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.51,
					};

					elements.liquid_isobutane = {
						tempLow: -159.42,
						density: 563,
					};

				//Double bond
					elements.butylene = { //butene
						name: "1-butylene",
						color: "#95cfcd",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 385,
						stateHigh: "fire",
						tempLow: -6.47,
						burn: 100,
						burnTime: 5,
						fireColor: ["#00ffff","#00ffdd"],
						burnInto: ["fire","carbon_dioxide","carbon_dioxide","steam","steam"],
						state: "gas",
						density: 1.93 * airDensity,
					};

					elements.liquid_butylene = {
						tempLow: -185.3,
						density: 625.63,
					};
					elements.water.reactions.butylene = {
						elem1: ["l_secbutanol","r_secbutanol"], elem2: ["l_secbutanol","r_secbutanol"]
					}; elements.water.reactions.liquid_butylene = elements.water.reactions.butylene;


					elements.trans_2_butylene = {
						name: "t-butylene-2",
						color: "#a1c9d4",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 324,
						stateHigh: "fire",
						tempLow: 0.8,
						burn: 85,
						burnTime: 5,
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2 * airDensity,
					};

					elements.liquid_trans_2_butylene = {
						tempLow: -105.5,
						density: 626,
					};

					elements.water.reactions.trans_2_butylene = {
						elem1: ["l_secbutanol","r_secbutanol"], elem2: ["l_secbutanol","r_secbutanol"]
					}; elements.water.reactions.liquid_trans_2_butylene = elements.water.reactions.trans_2_butylene;

					elements.cis_2_butylene = {
						name: "c-butylene-2",
						color: "#8cbcca",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 324,
						stateHigh: "fire",
						tempLow: 3.7,
						burn: 85,
						burnTime: 5,
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2 * airDensity,
					};

					elements.liquid_cis_2_butylene = {
						tempLow: -138.9,
						density: 641,
					};

					elements.water.reactions.cis_2_butylene = {
						elem1: ["l_secbutanol","r_secbutanol"], elem2: ["l_secbutanol","r_secbutanol"]
					}; elements.water.reactions.liquid_cis_2_butylene = elements.water.reactions.cis_2_butylene;

				//Triple bond
					elements.butyne = {
						color: "#81a2b3",
						behavior: behaviors.GAS,
						category: "gases",			
						tempHigh: 444, //Unknown autoignition
						stateHigh: "fire",
						tempLow: 8.08,
						burn: 100,
						burnTime: 5,
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.12 * airDensity, //made-up due to also unknown vapor density
					};

					elements.liquid_butyne = {
						tempLow: -125.7,
						density: 678.3,
					};

				//Alcohol
					//Line
					elements.butanol = {
						color: "#d0e7e6",
						behavior: behaviors.LIQUID,
						reactions: {
							"virus": { "elem2":null },
							"plague": { "elem2":null },
						},
						viscosity: 3.0011, //https://www.sciencedirect.com/science/article/abs/pii/S0021961416301446?via%3Dihub
						tempHigh: 117.7,
						tempLow: -89.8,
						burn: 100,
						burnTime: 3,
						category: "liquids",
						state: "liquid",
						density: 810,
						stain: -0.25,
					}

					//Hydroxyl in internal carbon (like isopropanol)
						//Left
							elements.l_secbutanol = {
								color: "#b9dbe4",
								behavior: behaviors.LIQUID,
								reactions: {
									"virus": { "elem2":null },
									"plague": { "elem2":null },
								},
								viscosity: 3.0011, //https://www.sciencedirect.com/science/article/abs/pii/S0021961416301446?via%3Dihub
								tempHigh: 100,
								tempLow: -115,
								burn: 100,
								burnTime: 3,
								category: "liquids",
								state: "liquid",
								density: 810,
								stain: -0.25,
							}

						//Right
							elements.r_secbutanol = {
								color: "#def1f2",
								behavior: behaviors.LIQUID,
								reactions: {
									"virus": { "elem2":null },
									"plague": { "elem2":null },
								},
								viscosity: 3.0011, //https://www.sciencedirect.com/science/article/abs/pii/S0021961416301446?via%3Dihub
								tempHigh: 100,
								tempLow: -115,
								burn: 100,
								burnTime: 3,
								category: "liquids",
								state: "liquid",
								density: 810,
								stain: -0.25,
							};

						//Racemic
							elements.secbutanol = {
								color: "#cce7ea",
								behavior: [
									"XX|XX|XX",
									"M2|CH:r_secbutanol,l_secbutanol|M2",
									"M1|M1|M1"
								],
								reactions: {
									"virus": { "elem2":null },
									"plague": { "elem2":null },
								},
								viscosity: 3.0011, //https://www.sciencedirect.com/science/article/abs/pii/S0021961416301446?via%3Dihub
								tempHigh: 100,
								stateHigh: ["l_secbutanol_gas","r_secbutanol_gas"],
								tempLow: -115,
								stateLow: ["l_secbutanol_ice","r_secbutanol_ice"],
								burn: 100,
								burnTime: 3,
								category: "liquids",
								state: "liquid",
								density: 810,
								stain: -0.25,
							};

					//Branched chain
					elements.isobutanol = {
						color: "#c9e3ee",
						behavior: behaviors.LIQUID,
						reactions: {
							"virus": { "elem2":null },
							"plague": { "elem2":null },
						},
						viscosity: 3.0011, //https://www.sciencedirect.com/science/article/abs/pii/S0021961416301446?via%3Dihub
						tempHigh: 107.89,
						tempLow: -108,
						burn: 100,
						burnTime: 3,
						category: "liquids",
						state: "liquid",
						density: 810,
						stain: -0.25,
					}

					//Branched with internal hydroxyl
					elements.tertbutanol = {
						color: "#c5def1",
						behavior: behaviors.LIQUID,
						reactions: {
							"virus": { "elem2":null },
							"plague": { "elem2":null },
						},
						viscosity: 3.0011, //https://www.sciencedirect.com/science/article/abs/pii/S0021961416301446?via%3Dihub
						tempHigh: 83,
						temp: 40,
						tempLow: 25,
						burn: 100,
						burnTime: 3,
						category: "liquids",
						state: "liquid",
						density: 810,
						stain: -0.25,
					}

				//Benzene ver.
					elements.butylbenzene = {
						color: "#7b8ae0",
						behavior: behaviors.LIQUID,
						state: "liquid",
						category: "liquids",
						density: 860.1,
						burn: 75,
						burnTime: 25,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
						tempHigh: 183.3,
						tempLow: -87.9,
					};

					elements.butylbenzene_gas = {
						density: 4.6 * airDensity,
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
					};

					elements.cumene = {
						color: "#8873e6",
						behavior: behaviors.LIQUID,
						state: "liquid",
						category: "liquids",
						density: 862,
						burn: 75,
						density: 0.777,
						burnTime: 25,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam"],
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
						tempHigh: 152,
						tempLow: -96,
					};

					elements.cumene_gas = {
						density: 4.1 * airDensity,
						reactions: {
							"head": { elem2: "cancer", chance: 0.0017 },
							"body": { elem2: "cancer", chance: 0.0017 },
						},
					};

			//Quintuple carbon line

				//Single bond
					elements.pentane = {
						color: "#b5685b",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 533,
						stateHigh: "fire",
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.00015},
							"body": { elem2: "rotten_meat", chance: 0.00015},
						},
						tempLow: 36.1,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.48 * airDensity,
					};

					elements.liquid_pentane = {
						color: "#a62711",
						tempLow: -130.2,
						density: 626,
					};

					elements.isopentane = {
						color: "#bb6c54",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 427,
						stateHigh: "fire",
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.00015},
							"body": { elem2: "rotten_meat", chance: 0.00015},
						},
						tempLow: -11.7,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.48 * airDensity,
					};

					elements.liquid_isopentane = {
						color: "#ab320d",
						tempLow: -160,
						density: 616,
					};

					elements.neopentane = {
						color: "#c1724e",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 427,
						stateHigh: "fire",
						reactions: {
							"head": { elem2: "rotten_meat", chance: 0.00015},
							"body": { elem2: "rotten_meat", chance: 0.00015},
						},
						tempLow: 9.5,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 3.255,
					};

					elements.liquid_neopentane = {
						color: "#af3d08",
						tempLow: -16.5,
						density: 601.172,
					};

				//Double bond
					elements.pentylene = { //pentene
						name: "1-pentylene",
						color: "#af5a4b",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 527,
						stateHigh: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						tempLow: 30,
						temp: 40,
						burn: 100,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.4 * airDensity,
					};

					elements.liquid_pentylene = {
						tempLow: -165.2,
						density: 640,
					};

					elements.trans_2_pentylene = {
						name: "t-pentylene-2",
						color: "#924b3f",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 324, //Unknown
						stateHigh: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						tempLow: 36.3,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.4 * airDensity,
					};

					elements.liquid_trans_2_pentylene = {
						tempLow: -140.2,
						density: 643.1,
					};

					elements.cis_2_pentylene = {
						name: "c-pentylene-2",
						color: "#9d5143",
						behavior: behaviors.GAS,
						category: "gases",
						tempHigh: 324, //Unknown
						stateHigh: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						tempLow: 36.9,
						burn: 85,
						burnTime: 5,
						burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","steam","steam"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2 * airDensity,
					};

					elements.liquid_cis_2_pentylene = {
						tempLow: -151.4,
						density: 655.6,
					};

				//Triple bond
					elements.pentyne = {
						name: "1-pentyne",
						color: "#9d5143",
						behavior: behaviors.GAS,
						category: "gases",			
						tempHigh: 454, //Unknown
						stateHigh: ["fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","oxygen","oxygen","oxygen"],
						tempLow: 40.2,
						temp: 55,
						burn: 100,
						burnTime: 5,
						burnInto: ["fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","oxygen","oxygen","oxygen"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.6 * airDensity, //made-up due to also unknown vapor density
					};

					elements.liquid_1_pentyne = {
						tempLow: -105.5,
						density: 691,
					};

					elements.pentyne_2 = {
						name: "2-pentyne",
						color: "#9d5143",
						behavior: behaviors.GAS,
						category: "gases",			
						tempHigh: 454, //Unknown
						stateHigh: ["fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","oxygen","oxygen","oxygen"],
						tempLow: 56.5,
						temp: 65,
						burn: 70,
						burnTime: 5,
						burnInto: ["fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","oxygen","oxygen","oxygen"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.6 * airDensity, //made-up due to also unknown vapor density
					};

					elements.liquid_pentyne_2 = {
						tempLow: -109,
						density: 710,
					};

					elements.isopentyne = {
						color: "#a6533a",
						behavior: behaviors.GAS,
						category: "gases",			
						tempHigh: 454, //Unknown
						stateHigh: ["fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","oxygen","oxygen","oxygen"],
						tempLow: 29.5,
						temp: 40,
						burn: 70,
						burnTime: 5,
						burnInto: ["fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_dioxide","oxygen","oxygen","oxygen"],
						fireColor: ["#00ffff","#00ffdd"],
						state: "gas",
						density: 2.6 * airDensity, //made-up due to also unknown vapor density
					};

					elements.liquid_isopentyne = {
						tempLow: -89.7,
						density: 666,
					};

				//Alcohol
					//Fuck no I'm not doing 8 isomers

				//Benzene ver.
					//i'm tired

			//Vodka

				elements.alcohol.reactions.water = { //50% is close enough to the standard 40%
					elem1: "vodka",
					elem2: "vodka",
				}

				elements.vodka = {
					color: "#9FAEC5",
					behavior: behaviors.LIQUID,
					reactions: {
						"virus": { "elem2":null, chance: 0.05 }, //it still adds up over ticks
						"plague": { "elem2":null, chance: 0.05 },
					},
					tick: function(pixel) {
						//Thermal split
						if(pixel.temp > elements.alcohol.tempHigh && exposedToAir(pixel)) {
							var randomNeighbor = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)]
							var rnx = randomNeighbor[0]
							var rny = randomNeighbor[1]
							if(isEmpty(pixel.x+rnx, pixel.y+rny, false)) {
								createPixel("alcohol_gas", pixel.x+rnx, pixel.y+rny);
								changePixel(pixel,pixel.temp >= 100 ? "steam" : "water",false);
							}
						}
					},
					tempLow: -36,
					category: "liquids",
					state: "liquid",
					density: 914,
					stain: -0.25,
				}

		//Other organic compounds

			//Oxygen plus acetylene mixture

					elements.oxy_fuel = {
						color: "#ff5eb4", 
						behavior: behaviors.GAS,
						state: "gas",
						category: "gases",
						tick: function(pixel) { //tick-based autoignition point to trigger acetylene fire properties
							if(pixel.temp > 325 && !pixel.burning) {
								pixel.burning = true;
							};
						},
						density: 1.25,
						burn: 100,
						burnTime: 10,
						burnTempChange: 330,
						fireSpawnTemp: 3100,
						fireSpawnChance: 5, //reduce own flame to reduce said effects from smoke
						fireElement: ["fire","plasma"],
						fireColor: "#5e91ff",
						burnInto: ["fire","plasma"],
						tempLow: -84,
						stateLow: ["oxygen","oxygen","acetylene_ice"],
						hidden: true,
					};

					elements.oxy_fuel_slush = {
						color: "#d85fed", 
						behavior: behaviors.LIQUID,
						viscosity: 100,
						state: "liquid",
						category: "liquids",
						tick: function(pixel) { //tick-based autoignition point to trigger acetylene fire properties
							if(pixel.temp > 325 && !pixel.burning) {
								pixel.burning = true;
							};
						},
						density: 873, //made-up
						burn: 100, 
						burnTime: 10,
						burnTempChange: 330,
						fireSpawnTemp: 3100,
						fireSpawnChance: 5, //reduce own flame to reduce said effects from smoke
						fireElement: ["oxy_fuel","fire","plasma"],
						fireColor: "#5e91ff",
						burnInto: "oxy_fuel",
						temp: -200,
						tempLow: -218.8,
						stateLow: "oxy_fuel_snow",
						tempHigh: -183.94,
						stateHigh: ["oxygen","oxygen","acetylene_ice"],
						hidden: true,
					};

					elements.oxy_fuel_snow = {
						color: "#dd9afc", 
						behavior: behaviors.POWDER,
						state: "solid",
						category: "powders",
						tick: function(pixel) { //tick-based autoignition point to trigger acetylene fire properties
							if(pixel.temp > 325 && !pixel.burning) {
								pixel.burning = true;
							};
						},
						density: 912, //made-up
						burn: 100, 
						temp: -250,
						burnTime: 10,
						burnTempChange: 330,
						fireSpawnTemp: 3100,
						fireSpawnChance: 5, //reduce own flame to reduce said effects from smoke
						fireElement: ["fire","plasma"],
						fireColor: "#5e91ff",
						burnInto: "oxy_fuel",
						tempHigh: -218.8,
						stateHigh: "oxy_fuel_slush",
						hidden: true,
					};

			//Styrene and its polymer

				elements.styrene = {
					color: "#d9d6c3",
					behavior: behaviors.LIQUID,
					state: "liquid",
					category: "liquids",
					density: 909,
					burn: 80,
					burnTime: 25,
					burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","steam"],
					reactions: {
						"head": { elem2: "cancer", chance: 0.0017 },
						"body": { elem2: "cancer", chance: 0.0017 },
						"hydrogen": { elem1: "ethylbenzene", elem2: null, chance: 0.005 },
						"benzoyl_peroxide": { elem1: "polystyrene", elem2: "benzoic_acid" },
						"polystyrene": { elem1: "polystyrene" },
						"molten_polystyrene": { elem1: "polystyrene" },
					},
					tempLow: -30,
					tempHigh: 145,
				};

				elements.styrene_gas = {
					density: 3.6 * airDensity,
				};

				elements.polystyrene = {
					color: "#f5f5f5",
					behavior: behaviors.WALL,
					state: "solid",
					category: "solids",
					density: 965,
					tempHigh: 100,
					//above this it does thermoplastic things
				};

				elements.molten_polystyrene = {
					color: "#e3e3e3",
					tempLow: 100,
					behavior: behaviors.LIQUID,
					viscosity: 1000,
					reactions: {
						"foam": { elem1: "styrofoam", elem2: ["styrofoam","molten_polystyrene","molten_polystyrene","molten_polystyrene"] },
					},
				};

				elements.styrofoam = {
					color: "#f5f5f5",
					behavior: behaviors.WALL,
					state: "solid",
					category: "solids",
					density: 50,
					tempHigh: 160, //reaction grace period
					stateHigh: [null,null,null,"molten_polystyrene"],
				};

			//Benzoyl peroxide

				elements.benzoyl_peroxide = {
					color: "#ededed",
					behavior: behaviors.POWDER,
					state: "solid",
					category: "powders",
					density: 1.334,
					tempHigh: 103,
					stateHigh: ["benzoic_acid","benzoic_acid","benzoic_acid","fire","explosion"],
				};

			//Benzoic acid

				elements.benzoic_acid = {
					color: "#c9c9c9",
					behavior: behaviors.POWDER,
					state: "solid",
					category: "powders",	
					density: 1.2659,
					tempHigh: 122,
				};

				elements.molten_benzoic_acid = {
					behavior: behaviors.LIQUID,
					color: "#b5b2b0",
					tempHigh: 250,
					density: 1074.9,
					reactions: {
						"molten_copper_sulfate": { tempMin: 200, elem1: ["phenol","phenol","carbon_dioxide"] }, //using air oxygen
					},
				};

				elements.benzoic_acid_gas = {
					density: 4.21 * airDensity,
					reactions: {
						"oxygen": { tempMin: 350, elem1: "phenol", elem2: "carbon_dioxide" },
						"molten_copper_sulfate": { tempMin: 200, elem1: ["phenol","phenol","carbon_dioxide"] },
					},
				};

			//Phenol

				elements.phenol = {
					color: "#dbd3d3",
					behavior: behaviors.POWDER,
					state: "solid",
					category: "powders",
					density: 1070,
					burn: 40,
					burnTime: 70,
					burnInto: ["fire","fire","carbon_dioxide","carbon_dioxide","carbon_dioxide","steam","steam","steam","dioxin"],
					reactions: {
						"head": { elem2: "rotten_meat", chance: 0.003 },
						"body": { elem2: "rotten_meat", chance: 0.003 },
					},
					tempHigh: 40.5,
					tempLow: -95,
				};

				elements.molten_phenol = {
					color: "#cfc2c2",
					behavior: behaviors.LIQUID,
					viscosity: 8,
					reactions: {
						"head": { elem2: "rotten_meat", chance: 0.003 },
						"body": { elem2: "rotten_meat", chance: 0.003 },
					},
					tempHigh: 181.7,
				};

				elements.phenol_gas = {
					reactions: {
						"head": { elem2: "rotten_meat", chance: 0.003 },
						"body": { elem2: "rotten_meat", chance: 0.003 },
					},
					density: 3.24,
				};

		//Inorganic compounds

			runAfterAutogen(function() {
				var waters = searchElements("water").filter(function(name) { return getState(name) == "liquid" });
				var whitelistWaters = ["hail","rime","cloud","rain_cloud","thunder_cloud","snow_cloud","hail_cloud","rain_cloud_cloud","snow_cloud_cloud","hail_cloud_cloud","cloud_cloud","heaviest_water_cloud","heaviest_snow_cloud","snow_cloud_floater","jinsoulite","jinsoulite_gas","jinsoulite_powder","molten_jinsoulite"];
				var jvoWaters = Object.keys(jinsouliteValueObject).filter(function(name) { return !(name.includes("bomb")) });
				var coldWaters = (getStateLow(waters,true) ?? []).filter(
					function(name) { return typeof(name) == "string" }
				);
				var colderWaters = (getStateLow(coldWaters,true) ?? []).filter(
					function(name) { return typeof(name) == "string" }
				);
				var hotWaters = (getStateHigh(waters,true) ?? []).filter(
					function(name) { return typeof(name) == "string" }
				);
				var brokenColdWaters = (getBreakInto(coldWaters,true) ?? []).filter(
					function(name) { return typeof(name) == "string" }
				);
				var coldBrokenColdWaters = (getStateLow(brokenColdWaters,true) ?? []).filter(
					function(name) { return typeof(name) == "string" }
				);
				wateroids = [waters,coldWaters,colderWaters,hotWaters,brokenColdWaters,coldBrokenColdWaters,whitelistWaters,jvoWaters].flat()

				//moved vivite-related code to where it happens
				for(var i = 0; i < wateroids.length; i++) {
					if(elements[wateroids[i]]) { elements[wateroids[i]].noViviteSlag = true };
				};
			});

			//Hydrogen sulfide (in chem.js)
				_h_2s = ["hydrogen_sulfide","liquid_hydrogen_sulfide","hydrogen_sulfide_ice"];

				runAfterLoad(function() {
					elements.hydrogen_sulfide.density = 1.19 * airDensity;
					elements.hydrogen_sulfide.reactions ??= {};
					elements.hydrogen_sulfide.reactions.head = { elem2: "rotten_meat", chance: 0.4};
					elements.hydrogen_sulfide.fireColor = { elem2: "rotten_meat", chance: 0.4};
					elements.hydrogen_sulfide.tempHigh = 1200;
					elements.hydrogen_sulfide.stateHigh = ["sulfur_gas","steam"];				
					delete elements.sulfur_dioxide.reactions.water;
					delete elements.sulfur_dioxide.reactions.steam;
					delete elements.water.reactions.sulfur;
					elements.sulfur_dioxide.tick = function(pixel) {
						var neighbors = adjacentCoords.map(offsetPair => pixelMap[pixel.x+offsetPair[0]]?.[pixel.y+offsetPair[1]]).filter(function(pixelOrUndefined) { return typeof(pixelOrUndefined) == "object" });
						if(neighbors.length < 2) { return };
						var neighboringElements = neighbors.filter(function(px) { return !!px }).map(x => x.element);
						var waterNeighbor = null;
						var sulfideNeighbor = null; 
						for(var i = 0; i < neighboringElements.length; i++) {
							if(_h_2s.includes(neighboringElements[i])) {
								sulfideNeighbor = adjacentCoords[i];
							};
							if(wateroids.includes(neighboringElements[i])) {
								waterNeighbor = adjacentCoords[i];
							};
							if(sulfideNeighbor && waterNeighbor) {
								if(!sulfideNeighbor || isEmpty(sulfideNeighbor.x,sulfideNeighbor.y,true)) { return };
								changePixel(sulfideNeighbor,getStateAtTemp("sulfur",pixel.temp));
								changePixel(pixel,getStateAtTemp("water",pixel.temp));
							}
						}
					}
				});

			//Carbon monoxide

				elements.carbon_monoxide = {
					color: "#8f8f8f",
					behavior: behaviors.GAS,
					state: "gas",
					category: "gases",
					density: 1.145,
					reactions: {
						"head": { elem2: "rotten_meat", chance: 0.0017},
						"body": { elem2: "rotten_meat", chance: 0.0017},
					},
					tempLow: -191.5,
				};

				elements.liquid_carbon_monoxide = {
					tempLow: -205.02,
					density: 789, //unknown solid density
				};

			//Water

				elements.steam.reactions ??= {};
				elements.steam.reactions.charcoal = { tempMin: 680, elem1: "hydrogen", elem2: "carbon_monoxide" };
				elements.steam.reactions.diamond = { tempMin: 680, elem1: "hydrogen", elem2: "carbon_monoxide" };

			//Oil refining

				delete elements.oil.tempHigh;

				function liquidMoveCustomViscosity(pixel,viscosity) {
					if (pixel.start === pixelTicks) {return}
					if (pixel.charge && elements[pixel.element].behaviorOn) {
						pixelTick(pixel)
					}
					var viscosityPass = ((Math.random()*100) < 100 / Math.pow(viscosity, 0.25));
					if (!viscosityPass) {
						var move1Spots = [
							[pixel.x, pixel.y+1]
						]
					}
					else {
						var move1Spots = [
							[pixel.x+1, pixel.y+1],
							[pixel.x, pixel.y+1],
							[pixel.x-1, pixel.y+1],
						]
					}
					var moved = false;
					for (var i = 0; i < move1Spots.length; i++) {
						var coords = move1Spots[Math.floor(Math.random()*move1Spots.length)];
						if (tryMove(pixel, coords[0], coords[1])) { moved = true; break; }
						else { move1Spots.splice(move1Spots.indexOf(coords), 1); }
					}
					if (!moved) {
						if (viscosityPass) {
							if (Math.random() < 0.5) {
								if (!tryMove(pixel, pixel.x+1, pixel.y)) {
									tryMove(pixel, pixel.x-1, pixel.y);
								}
							} else {
								if (!tryMove(pixel, pixel.x-1, pixel.y)) {
									tryMove(pixel, pixel.x+1, pixel.y);
								}
							}
						}
					}
					doDefaults(pixel);
				};

				elements.light_petroleum_fuel_gas = { //it's not liquified, and sandboxels doesn't even have a pressure system, and there is no generic name for uncompressed, gaseous "L"PG, so we need a different name
					burn: 100,
					color: "#b5b5b3",
					density: 3.5,
					tempLow: -44,
					tick: function(pixel) {
						if (pixel.temp >= 495 && !pixel.burning) {
							pixel.burning = true;
							pixel.burnStart = pixelTicks;
						}
					},
					burnInto: "explosion,explosion,fire,fire,fire,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(","),
					state: "gas",
					behavior: behaviors.GAS,
				};

				elements.lamp_oil.tempHigh = 170;
				elements.lamp_oil.density = 810;
				elements.lamp_oil.name = "kerosene";
				elements.lamp_oil.forceAutoGen = true;
				elements.lamp_oil.burnInto = "smoke,smoke,fire,fire,fire,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",");
				elements.lamp_oil_gas = {
					name: "kerosene gas",
					burn: 100,
					density: 4.5,
					tick: elements.lamp_oil.tick,
					burnInto: "explosion,smoke,smoke,fire,fire,fire,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",")
				};

				elements.gasoline = {
					color: "#d1cf9d",
					behavior: behaviors.LIQUID,
					tick: function(pixel) {
						if (pixel.temp > 263 && !pixel.burning) {
							pixel.burning = true;
							pixel.burnStart = pixelTicks;
						}
					},
					reactions: {
						"styrofoam": { elem1: ["gasoline","gasoline","gasoline","gasoline","napalm"], elem2: null }, //the joke
						"polystyrene": { elem1: "napalm", elem2: ["polystyrene","polystyrene",null] },
						"molten_polystyrene": { elem1: "napalm", elem2: ["molten_polystyrene","molten_polystyrene",null] },
						"glue": {elem2:null, chance:0.05},
						"wax": {elem2:null, chance:0.005},
						"melted_wax": {elem2:null, chance:0.025},
					},
					forceAutoGen: true,
					category: "liquids",
					tempHigh: 70,
					tempLow: -60,
					burn: 20,
					burnTime: 500,
					burnInto: "fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(","),
					viscosity: 7.04,
					state: "liquid",
					density: 755,
					alias: "petrol"
				};

				elements.gasoline_gas = {
					burn: 100,
					burnTime: 10,
					density: 3.5,
					tick: elements.gasoline.tick,
					burnInto: "explosion,fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",")
				};

				elements.naphtha = {
					color: "#d1d1d1",
					behavior: behaviors.LIQUID,
					tick: function(pixel) {
						if (pixel.temp > 270 && !pixel.burning) {
							pixel.burning = true;
							pixel.burnStart = pixelTicks;
						}
					},
					reactions: {
						"styrofoam": { elem1: ["naphtha","naphtha","naphtha","naphtha","napalm"], elem2: null },
						"polystyrene": { elem1: "napalm", elem2: ["polystyrene","polystyrene",null] },
						"molten_polystyrene": { elem1: "napalm", elem2: ["molten_polystyrene","molten_polystyrene",null] },
						"glue": {elem2:null, chance:0.05},
						"wax": {elem2:null, chance:0.005},
						"melted_wax": {elem2:null, chance:0.025},
					},
					category: "liquids",
					tempHigh: 120,
					tempLow: -30,
					forceAutoGen: true,
					burn: 80,
					burnTime: 500,
					burnInto: "fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(","),
					viscosity: 5.77,
					state: "liquid",
					density: 740
				};

				elements.naphtha_gas = {
					burn: 100,
					burnTime: 10,
					density: 3.5,
					tick: elements.naphtha.tick,
					burnInto: "explosion,fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",")
				};

				elements.diesel = {
					color: "#d3d9b4",
					behavior: behaviors.LIQUID,
					tick: function(pixel) {
						if (pixel.temp > 300 && !pixel.burning) {
							pixel.burning = true;
							pixel.burnStart = pixelTicks;
						}
					},
					reactions: {
						"glue": {elem2:null, chance:0.05},
						"wax": {elem2:null, chance:0.005},
						"melted_wax": {elem2:null, chance:0.025},
					},
					category: "liquids",
					tempHigh: 260,
					forceAutoGen: true,
					tempLow: -25,
					burn: 20,
					burnTime: 500,
					burnInto: "fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(","),
					viscosity: 7.04,
					state: "liquid",
					density: 755,
				};

				elements.diesel_gas = {
					burn: 100,
					burnTime: 12,
					density: 3.5,
					tick: elements.diesel.tick,
					burnInto: "explosion,fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",")
				};

				elements.lubricating_oil = {
					color: "#d3d9b4",
					behavior: behaviors.LIQUID,
					tick: function(pixel) {
						if (pixel.temp > 450 && !pixel.burning) {
							pixel.burning = true;
							pixel.burnStart = pixelTicks;
						}
					},
					category: "liquids",
					tempHigh: 350,
					tempLow: -40,
					burn: 20,
					burnTime: 600,
					forceAutoGen: true,
					burnInto: "fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(","),
					viscosity: 7.04,
					state: "liquid",
					density: 800,
				};

				elements.lubricating_oil_gas = {
					burn: 100,
					burnTime: 13,
					density: 3.5,
					tick: elements.lubricating_oil.tick,
					burnInto: "explosion,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",")
				};

				elements.heavy_fuel_oil = {
					color: "#1c1a18",
					stain: 0.3,
					behavior: behaviors.LIQUID,
					tick: function(pixel) {
						if (pixel.temp > 407 && !pixel.burning) {
							pixel.burning = true;
							pixel.burnStart = pixelTicks;
						};
						if(pixel.burning && Math.random() < 0.01) {
							var emptyNeighbors = [];
							for(i = 0; i < adjacentCoords.length; i++) {
								if(isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],false)) {
									emptyNeighbors.push(adjacentCoords[i]);
								};
							};
							if(emptyNeighbors.length > 0) {
								var randomEmptyNeighbor = emptyNeighbors[Math.floor(Math.random() * emptyNeighbors.length)];
								createPixelReturn(["smoke","carbon_dioxide"],pixel.x+randomEmptyNeighbor[0],pixel.y+randomEmptyNeighbor[1]).temp = pixel.temp
							};
						}
					},
					reactions: {
						"polystyrene": { elem1: "napalm", elem2: "napalm", chance:0.05 }, //the joke
						"glue": {elem2:null, chance:0.05},
						"wax": {elem2:null, chance:0.005},
						"melted_wax": {elem2:null, chance:0.025},
					},
					category: "liquids",
					tempHigh: 300,
					forceAutoGen: true,
					tempLow: 0,
					burn: 10,
					viscosity: 700,
					burnTime: 800,
					fireElement: ["fire","fire","fire","smoke","smoke","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_monoxide","carbon_monoxide","carbon_monoxide","sulfur_dioxide","sulfur_trioxide_gas","poison_gas"],
					burnInto: "fire,fire,fire,fire,fire,fire,ash,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,poison_gas".split(","),
					viscosity: 7.04,
					state: "liquid",
					density: 755,
					alias: "petrol"
				};

				elements.heavy_fuel_oil_gas = {
					burn: 80,
					burnTime: 60,
					density: 2.5,
					tick: elements.heavy_fuel_oil.tick,
					fireElement: ["explosion","fire","fire","fire","smoke","smoke","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_monoxide","carbon_monoxide","carbon_monoxide","sulfur_dioxide","sulfur_trioxide_gas","poison_gas"],
					burnInto: "fire,fire,fire,fire,fire,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,steam,steam".split(",")
				};

				elements.bitumen = {
					color: "#0d0c0c",
					maxColorOffset: 5,
					tick: function(pixel) {
						var viscosity = 1e16 / (1.09 ** pixel.temp);
						liquidMoveCustomViscosity(pixel,viscosity)
					},
					reactions: {
						"polystyrene": { elem1: "napalm", elem2: "napalm", chance:0.05 }, //the joke
						"glue": {elem2:null, chance:0.05},
						"wax": {elem2:null, chance:0.005},
						"melted_wax": {elem2:null, chance:0.025},
					},
					category: "liquids",
					tempHigh: 750,
					stateHigh: ["bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","bitumen","fire","fire","fire","smoke","smoke","smoke","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_monoxide","carbon_monoxide","carbon_monoxide","sulfur_dioxide","sulfur_trioxide_gas","poison_gas"],
					burn: 2,
					burnTime: 800,
					fireElement: ["fire","fire","fire","smoke","smoke","smoke","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_monoxide","carbon_monoxide","carbon_monoxide","sulfur_dioxide","sulfur_trioxide_gas","poison_gas"],
					burnInto: "fire,fire,fire,fire,fire,fire,ash,ash,ash,carbon_monoxide,carbon_monoxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,carbon_dioxide,steam,steam,steam,poison_gas".split(","),
					viscosity: 7.04,
					state: "liquid",
					density: 1050,
					reactions: {
						gravel: { elem1: "asphalt", elem2: "asphalt" }
					}
				};

				elements.asphalt ={
					color: "#191919",
					behavior: behaviors.STURDYPOWDER,
					tempHigh: 750,
					stateHigh: ["asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","asphalt","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel",,"fire","fire","fire","smoke","smoke","smoke","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_monoxide","carbon_monoxide","carbon_monoxide","sulfur_dioxide","sulfur_trioxide_gas","poison_gas"],
					category: "land",
					state: "solid",
					density: 2322,
					burn: 0.5,
					burnTime: 5000,
					burnInto: ["gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel","gravel",,"fire","fire","fire","smoke","smoke","smoke","carbon_dioxide","carbon_dioxide","carbon_dioxide","carbon_monoxide","carbon_monoxide","carbon_monoxide","sulfur_dioxide","sulfur_trioxide_gas","poison_gas"],
					fireElement: ["smoke","smoke","smoke","carbon_dioxide","carbon_monoxide","fire","fire","fire","fire","fire","fire"],
					fireChance: 2,
					hardness: 0.5,
					breakInto: ["bitumen","gravel"],
					reactions: {
						light: { temp1: 0.25, elem2: null }
					}
				};

				elements.oil.tick = function(pixel) {
					if(!pixel.role) {
						var value = Math.random()
						if(value <= 0.03) {
							pixel.role = "lpg";
						} else if(value <= 0.45) { //42%
							pixel.role = "gasoline";
						} else if(value <= 0.60) { //15%
							pixel.role = "naphtha";
						} else if(value <= 0.70) { //10%
							pixel.role = "kerosene"; //kerosene/lamp oil/jet fuel are apparently the same funny bunch of hydrocarbons
						} else if(value <= 0.9) { //20%
							pixel.role = "diesel";
						} else if(value <= 0.91) { //1%
							pixel.role = "lubricant";
						} else if(value <= 0.97) { //6%
							pixel.role = "heavy_fuel_oil"; //700 cP
						} else if(value < 1) { //3%
							pixel.role = "bitumen";
						};
					};

					if(pixel.temp > 30 && pixel.role == "lpg") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						changePixel(pixel,"light_petroleum_fuel_gas")
					} else if(pixel.temp > 70 && pixel.role == "gasoline") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						changePixel(pixel,"gasoline_gas")
					} else if(pixel.temp > 120 && pixel.role == "naphtha") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						changePixel(pixel,"naphtha_gas")
					} else if(pixel.temp > 170 && pixel.role == "kerosene") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						changePixel(pixel,"lamp_oil_gas")
					} else if(pixel.temp > 270 && pixel.role == "diesel") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						changePixel(pixel,"diesel_gas")
					} else if(pixel.temp > 300 && pixel.role == "heavy_fuel_oil") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						changePixel(pixel,"heavy_fuel_oil_gas")
					} else if(pixel.temp > 350 && pixel.role == "lubricant") { //https://www.crownoil.co.uk/guides/crude-oil-fractional-distillation/: Butane and propane and other petroleum gases are formed right at the top of the distillation tower, where it is coolest, a very mild 25°C: the temperature range that forms these gases is between 25°C and 50°C. These gases are the lightest products formed in crude oil distillation and are flammable gases.
						if(pixel.role == "lubricant") {
							changePixel(pixel,"lubricating_oil_gas")
						} else {
							changePixel(pixel,"bitumen")
						}
					}
				};

	//UREA ##

		elements.urea = {
			color: "#fef7ee", //once again mapping UV absorbances to the visible range
							  //https://www.researchgate.net/publication/266458879
							  //http://depts.washington.edu/cmditr/modules/lum/color.html
			behavior: behaviors.POWDER,
			state: "solid",
			density: 1320,
			tempHigh: 133,
			category: "powders",
		},

		elements.molten_urea = {
			tempHigh: 350, //https://pubs.acs.org/doi/pdf/10.1021/ie034052j
			stateHigh: ["ammonia","vaporized_isocyanic_acid"],
		},

		elements.liquid_isocyanic_acid = {
			color: "#ffe5f0", //now it's an IR spectrum
							  //https://www.researchgate.net/publication/231057584
			behavior: behaviors.LIQUID,
			reactions: {
				"water": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
				"steam": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
				"ice": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
			},
			state: "liquid",
			density: 1140,
			tempHigh: 23,
			stateHigh: "vaporized_isocyanic_acid",
			tempLow: -86,
			stateLow: "frozen_isocyanic_acid",
			category: "liquids",
		},

		elements.frozen_isocyanic_acid = {
			color: "#ffe5f0",
			behavior: behaviors.WALL,
			reactions: {
				"water": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
				"steam": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
				"ice": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
			},
			state: "solid",
			density: 1267,
			tempHigh: -86,
			stateHigh: "liquid_isocyanic_acid",
			category: "powders",
		},

		elements.vaporized_isocyanic_acid = {
			color: "#ffe5f0",
			behavior: behaviors.GAS,
			reactions: {
				"water": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
				"steam": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
				"ice": { "elem1": "carbon_dioxide", "elem2": "ammonia" },
			},
			state: "gas",
			density: 1026,
			tempLow: 23,
			stateLow: "liquid_isocyanic_acid",
			category: "gases",
		}

	//INVISIBLE WALL AND DYE ##

		if(!settings) {
			settings = {}
		}

		settings.bg ??= "#000000";

		function getBackgroundColorOrAverageAsJSON() {
			if(!(settings?.bg)) {
				return {r: 0, g: 0, b: 0};
			} else if(!(settings.bg instanceof Array)) {
				return convertColorFormats(settings.bg,"json")
			} else {
				return convertColorFormats(averageRgbPrefixedColorArray(settings.bg.map(color => convertColorFormats(color,"rgb"))),"json");
			};
		};

		function makePixelInvisible(pixel) {
			var backgroundColor = getBackgroundColorOrAverageAsJSON();
			pixel.color = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},0)`;
		};

		elements.invisible_wall = {
			color: settings.bg,
			behavior: behaviors.WALL,
			tick: function(pixel) { makePixelInvisible(pixel) },
			insulate: true,
			hardness: 1,
			category: "special",
			state: "solid",
		};

		elements.invisible_dye = {
			color: settings.bg,
			behavior: behaviors.LIQUID,
			tick: function(pixel) { makePixelInvisible(pixel) },
			hardness: 0.8,
			breakInto: "invisible_dye_gas",
			tempHigh: 110,
			stateHigh: "invisible_dye_gas",
			category: "special",
			state: "liquid",
			density: 1,
			stain: elements.dye.stain,
		};

		elements.invisible_dye_gas = {
			color: settings.bg,
			behavior: behaviors.GAS,
			tick: function(pixel) { makePixelInvisible(pixel) },
			hardness: 0.5,
			breakInto: "invisible_dye_gas",
			tempLow: 109,
			stateLow: "invisible_dye",
			category: "special",
			state: "liquid",
			density: 1,
			stain: elements.spray_paint.stain,
		};

		temp = {
			invisible_wall: "asdfg",
			invisible_dye: 2,
			invisible_dye_gas: false
		};

		for(var elemName in temp) {
			elements[elemName].desc = "Note: Invisible dyes do not work (and are not supported) with gradient backgrouds";
		};

	//BANANAS AND BANANA PLANTS ##

		randomNumberFromOneToThree = function() {
			return 1 + Math.floor(Math.random() * 3)
		};

		bananaDebugSpeedGrowth = false;
		logLeaves = false;
		bananaAttachWhitelist = ["banana_pseudostem","banana_peduncle_1","banana_peduncle_2","petal","banana_leaf","banana_plant_top","banana"];

		bananaDirtElements = ["dirt","mud","sand","wet_sand","clay_soil","mycelium","grass"];

		function logPixelCoords(pixel) {
			return `(${pixel.x}, ${pixel.y})`
		};

		function hasPixel(x,y,elementInput) {
			if(isEmpty(x,y,true)) { //if empty, it can't have a pixel
				return false;
			} else {
				if(elementInput.includes(",")) { //CSTA
					elementInput = elementInput.split(",");
				};
				if(Array.isArray(elementInput)) { //if element list
					return elementInput.includes(pixelMap[x][y].element);
				} else { //if single element
					return pixelMap[x][y].element === elementInput;
				};
			};		
		};

		elements.banana_seed = {
			color: "#3b3b2e",
			tick: function(pixel) {
				if(pixel.bananaRange === null) {
					pixel.bananaRange = randomNumberFromOneToThree();
				};

				if (isEmpty(pixel.x,pixel.y+1)) {
					movePixel(pixel,pixel.x,pixel.y+1);
				} else {
					if (Math.random() < (bananaDebugSpeedGrowth ? 0.09 : 0.03) && pixel.age > (bananaDebugSpeedGrowth ? 20 : 50) && pixel.temp < 100) {
						if (!outOfBounds(pixel.x,pixel.y+1)) {
							var dirtPixel = pixelMap[pixel.x][pixel.y+1];
							if (bananaDirtElements.includes(dirtPixel.element)) {
								changePixel(dirtPixel,"root");
							};
						};
						if (isEmpty(pixel.x,pixel.y-1)) {
							movePixel(pixel,pixel.x,pixel.y-1);
							createPixel("banana_pseudostem",pixel.x,pixel.y+1);
							pixelMap[pixel.x][pixel.y+1].bananaRange = pixel.bananaRange; //pass banana range down to pseudostem
						};
					} else if (pixel.age > (bananaDebugSpeedGrowth ? 500 : 1000)) {
						changePixel(pixel,"banana_plant_top");
					};
					pixel.age++;
				};
				if(Math.random() < 0.01 && pixel.age > 200) {
					changePixel(pixel,"banana_plant_top");
				};
				doDefaults(pixel);
			},
			properties: {
				"age": 0,
				"bananaRange": null
			},
			tempHigh: 100,
			stateHigh: "dead_plant",
			tempLow: -2,
			stateLow: "frozen_plant",
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			category: "life",
			state: "solid",
			density: 1500,
			cooldown: defaultCooldown,
		};

		elements.banana_pseudostem = {
			hidden: true,
			color: "#d5e39f",
			tick: function(pixel) {
				if(pixel.bananaRange === null) {
					pixel.bananaRange = randomNumberFromOneToThree();
				};

				if (pixel.age > 60 && pixel.temp < 100 && !pixel.grewPeduncle) {
					var peduncleOffsets = [-1, 1]; //placed to the left, placed to the right
					for(i = 0; i < peduncleOffsets.length; i++) {
						if (isEmpty(pixel.x+peduncleOffsets[i],pixel.y,false)) {
							if (Math.random() < 0.005) {
								createPixel("banana_peduncle_1",pixel.x+peduncleOffsets[i],pixel.y);
								pixelMap[pixel.x+peduncleOffsets[i]][pixel.y].dir = Math.sign(peduncleOffsets[i]);
								pixelMap[pixel.x+peduncleOffsets[i]][pixel.y].bananaRange = pixel.bananaRange; //pass banana range down to peduncle
								if(Math.random() < 0.8) { pixel.grewPeduncle = true; } //20% chance to not mark as true, allowing for a chance to try another peduncle
							};
						};
					};
				};
				pixel.age++;
				doDefaults(pixel);
			},
			properties: {
				"age": 0,
				"grewPeduncle": false,
				"bananaRange": null
			},
			tempHigh: 100,
			stateHigh: "dead_plant",
			tempLow: -2,
			stateLow: "frozen_plant",
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			category: "life",
			state: "solid",
			density: 1500,
		};

		elements.banana = {
			color: "#ede84c",
			isFood: true,
			tick: function(pixel) {
				if(pixel.attached) {
					var attachCoords = [pixel.x+Math.sign(pixel.attachDirection), pixel.y];
					if(isEmpty(attachCoords[0],attachCoords[1],false)) {
						pixel.attached = false;
					};
				} else { //Move if not attached
					if (!tryMove(pixel, pixel.x, pixel.y+1)) {
						if(Math.random() < 0.9) {
							if (Math.random() < 0.5) {
								if (!tryMove(pixel, pixel.x+1, pixel.y+1)) {
									tryMove(pixel, pixel.x-1, pixel.y+1);
								};
							} else {
								if (!tryMove(pixel, pixel.x-1, pixel.y+1)) {
									tryMove(pixel, pixel.x+1, pixel.y+1);
								};
							};
						};
					};
				};
				doDefaults(pixel);
				var shouldSpoil = true; //spoil by default
				if(pixel.attached) { //if it's attached
					if(!isEmpty(attachCoords[0],attachCoords[1],true)) { //if the attachment coords are a pixel and not OOB
						var attachPixel = pixelMap[attachCoords[0]][attachCoords[1]];
						var attachElement = attachPixel.element;
						if(bananaAttachWhitelist.includes(attachElement)) {//if the element is a whitelisted "don't spoil" element
							shouldSpoil = false; //then don't spoil
						};
					};
				};
				if(shouldSpoil) { //spoil if not attached
					if(pixel.temp > -14 && pixel.temp <= 4) { //(no spoiling below 14C)
						pixel.spoilage += Math.max(Math.min(scale(pixel.temp,-14,4,0,9),9),0)
					} else if(pixel.temp > 4) {
						pixel.spoilage += Math.max(Math.min(scale(pixel.temp,4,20,9,30),40),0)
					};
				};
				if(pixel.spoilage > 14400) { //3600 = 120 ticks at 20C 
					if(Math.random() < 0.05) {
						changePixel(pixel,"spoiled_banana");
					};
				};
			},
			properties: {
				"spoilage":0,
				"attached": false,
				"attachDirection": (!Math.floor(Math.random() * 2)) ? 1 : -1
			},
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			tempHigh: 200,
			stateHigh: ["steam", "ash"],
			onTryMoveInto: function(pixel,otherPixel) {
				var otherInfo = elements[otherPixel.element]
				if(typeof(otherInfo.state) === "string" && otherInfo.state !== "gas") {
					pixel.attached = false;
				};
			},
		};

		elements.banana_peduncle_1 = {
			hidden: true,
			name: "banana peduncle (offshoot)",
			color: "#acb55b",
			tick: function(pixel) {
				if(pixel.bananaRange === null) {
					pixel.bananaRange = randomNumberFromOneToThree();
				};

				if (pixel.age > 20 && pixel.temp < 100) {
					var peduncleCoords1 = [pixel.x + pixel.dir, pixel.y];
					var peduncleCoords2 = [pixel.x + pixel.dir, pixel.y + 1];
					if(isEmpty(peduncleCoords1[0],peduncleCoords1[1],false) && isEmpty(peduncleCoords2[0],peduncleCoords2[1],false)) { 
						if(Math.random() < 0.5) {
							createPixel(pixel.element,peduncleCoords1[0],peduncleCoords1[1]);
							pixelMap[peduncleCoords1[0]][peduncleCoords1[1]].dir = pixel.dir;
							pixelMap[peduncleCoords1[0]][peduncleCoords1[1]].bananaRange = pixel.bananaRange; //pass banana range down to next pixel of peduncle horizontal
						} else {
							createPixel("banana_peduncle_2",peduncleCoords2[0],peduncleCoords2[1]);
							pixelMap[peduncleCoords2[0]][peduncleCoords2[1]].bananaRange = pixel.bananaRange; //pass banana range down to diagonal offshoot
						};
					};
				};
				pixel.age++;
				doDefaults(pixel);
			},
			properties: {
				"dir": (!Math.floor(Math.random() * 2)) ? 1 : -1,
				"age": 0,
				//"bananaRange": (1 + (Math.floor(Math.random() * 3))), //1-3
				"bananaRange": null
			},
			tempHigh: 100,
			stateHigh: "dead_plant",
			tempLow: -2,
			stateLow: "frozen_plant",
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			category: "life",
			state: "solid",
			density: 1500,
		};

		elements.banana_peduncle_2 = {
			hidden: true,
			name: "banana peduncle (hanging)",
			color: "#9bad51",
			tick: function(pixel) {
				if(pixel.bananaRange === null) {
					pixel.bananaRange = randomNumberFromOneToThree();
				};

				// Grow/Flower
				if (pixel.age > 20 && pixel.temp < 100) {
					var growthCoords = [pixel.x, pixel.y + 1];
					if(isEmpty(...growthCoords)) { 
						if(Math.random() < 0.9) {
							createPixel(pixel.element,...growthCoords);
							pixelMap[growthCoords[0]][growthCoords[1]].bananaRange = pixel.bananaRange; //pass banana range down to next pixel of peduncle vertical
						} else {
							createPixel("petal",...growthCoords); //the sexual dimorphism of the banana plant has zonked me
						};
					};
				};

				//Make bananas
				if (pixel.age > 40 && pixel.temp < 100) {
					var bananaOffsets = [-1, 1]; //placed to the left, placed to the right
					for(i = 0; i < bananaOffsets.length; i++) {
						//console.log(`Looping through left and right positions: ${bananaOffsets}`);
						for(j = 1; j < pixel.bananaRange + 1; j++) { //for max banana distance, using the banana range
							//console.log(`Looping through banana offset multipliers: ${j}`);
							if (isEmpty(pixel.x+(j * bananaOffsets[i]),pixel.y,false)) { //if there's an empty space
								//console.log(`Banana position is empty: [${j * bananaOffsets[i]}, 0]\nTrying banana at (${pixel.x+(j * bananaOffsets[i])},${pixel.y})`);
								if (Math.random() < (bananaDebugSpeedGrowth ? 0.05 : 0.005)) { //try to place the banana
									//console.log(`Placing banana`);
									createPixel("banana",pixel.x+(j * bananaOffsets[i]),pixel.y);
									pixelMap[pixel.x+(j * bananaOffsets[i])][pixel.y].attached = true;
									pixelMap[pixel.x+(j * bananaOffsets[i])][pixel.y].attachDirection = -1 * Math.sign(bananaOffsets[i]); //attach dir is the opposite of placement dir so it attaches towards the stem
								} else {
									//console.log(`NOT placing banana`);
								};
								//console.log(`Banana tried, stopping iteration`);
								break; //and then stop iteration
							} else {
								//console.log(`Banana position is NOT empty: [${j * bananaOffsets[i]}, 0]\nSkipping this offset`);
								continue; //if not empty, skip that pixel and move on the next distance
							};
							//console.log(`====End of side try====`);
						};					
						//console.log(`%%%%End of side iterator%%%%`);
					};
					//console.log(`>>>>End of banana iterator<<<<`);
				};
				pixel.age++;
				doDefaults(pixel);
				//console.log(`\nEnd of peduncle tick\n`);
			},
			properties: {
				"age": 0, 
				//"bananaRange": (1 + (Math.floor(Math.random() * 3))), //1-3
				"bananaRange": null
			},
			tempHigh: 100,
			stateHigh: "dead_plant",
			tempLow: -2,
			stateLow: "frozen_plant",
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			category: "life",
			state: "solid",
			density: 1500,
		};

		elements.spoiled_banana = {
			hidden: true,
			color: "#594b29",
			behavior: [
				"XX|CR:stench,fly%0.1|XX",
				"M2%0.5|CH:dirty_water,fly,fly%0.007|M2%0.5",
				"M2|M1|M2"
			],
			stain: 0.01,
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			tempHigh: 200,
			stateHigh: ["steam", "ash"],
		};

		elements.fly.reactions.spoiled_banana = { "elem2":null, chance:0.15, func:behaviors.FEEDPIXEL };

		elements.banana_leaf = {
			hidden: true,
			color: "#9df24e",
			tick: function(pixel) {
				if(pixel.bananaRange === null) {
					pixel.bananaRange = randomNumberFromOneToThree();
				};

				if(pixel.attached) {
					var attachCoords = [pixel.x + pixel.attachOffsets[0], pixel.y + pixel.attachOffsets[1]];
					if(isEmpty(attachCoords[0],attachCoords[1],false)) { //consider OOB full
						pixel.attached = false;
					};
				} else { //Move if not attached
					if(Math.random() < 0.2) {
						if (!tryMove(pixel, pixel.x, pixel.y+1)) {
							if(Math.random() < 0.4) {
								if (Math.random() < 0.5) {
									if (!tryMove(pixel, pixel.x+1, pixel.y+1)) {
										tryMove(pixel, pixel.x-1, pixel.y+1);
									};
								} else {
									if (!tryMove(pixel, pixel.x-1, pixel.y+1)) {
										tryMove(pixel, pixel.x+1, pixel.y+1);
									};
								};
							};
						};
					};
				};
				doDefaults(pixel);
			},
			properties: {
				"attached": false,
				"attachOffsets": [(!Math.floor(Math.random() * 2)) ? 1 : -1, 0],
				"bananaRange": null
			},
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			tempHigh: 200,
			stateHigh: ["steam", "ash"],
			onTryMoveInto: function(pixel,otherPixel) { //Move through
				var otherElement = otherPixel.element; //var element for readability

				var otherInfo = elements[otherElement]; //var info

				var otherState = "solid"; //consider things solid by default
				if(typeof(otherInfo.state) === "string") {
					otherState = otherInfo.state; //get actual state if it exists
				};

				var otherDensity = 1000; //consider density 1000 by default
				if(typeof(otherInfo.density) === "number") {
					otherDensity = otherInfo.density; //get actual density if it exists
				};

				var react = false; //default to no reaction
				if(typeof(otherInfo.reactions) === "object") { //look for reactions
					if(typeof(otherInfo.reactions[pixel.element]) === "object") { //look for reactions involving this element
						react = true; //if there are any, set reaction flag to true
					};
				};

				if(otherElement.endsWith("head") || otherElement.endsWith("body")) {
					//i don't want to make general MPL handling so I'll just try to exclude them;
					if(otherElement !== "antibody") {
						//exclude antibody from exclusion
						return false;
					};
				};

				if(otherElement !== pixel.element) { //allow this element from piling on itself
					if(logLeaves) { console.log("Other element is not banana leaves") }; //yes, this code is for banana leaves
					if(react) { //if there was a reaction in that previous step
						if(logLeaves) { console.log("Reacting pixels") };
						reactPixels(otherPixel,pixel); //react
					} else { //if no such reaction existed, move through
						if(logLeaves) { console.log("Moving pixels") };
						if((otherState !== "solid") || (otherState === "solid" && otherDensity > 100)) { //admit any non-solid, or any solid with a density over 100
							var pX = pixel.x; //var pixel coords for no particular reason
							var pY = pixel.y;
							var oX = otherPixel.x; //var other pixel's coords for no particular reason
							var oY = otherPixel.y;
							if(logLeaves) { console.log(`${otherElement} pixel (${oX},${oY}) trying to move info leaf block (${pX},${pY})`) };
							var dX = oX - pX; //get the difference between this's X and other's X; if the other pixel is moving from the space immediately to the right, this dX value should be 1
							var dY = oY - pY;
							var iDX = -1 * dX; //get the additive inverse; if we want to move such a pixel from the right to the left, we would change its +1 X offset to a -1 X offset for the coord sto move it to
							var iDY = -1 * dY;
							if(logLeaves) { console.log(`Old offset (relative to leaf): [${dX},${dY}], new offset [${iDX},${iDY}]`) };
							var fX = pX + iDX; //combine this pixel's X with the inverted offset we just made;
											   //assuming this pixel is (23,31) and the other pixel is trying to move in to the left into this from (24,31),
											   //the dX would be [1, 0], signifying that the other pixel is 1 pixel to the right of this
											   //the space to the left of this, where it would go, is (22,31), and the offset for that pixel relative to this is [-1, 0]
											   //to get the [-1, 0], we'd need to flip that [1, 0] offset (lmao flip that the song by loona), hence the inverse
							var fY = pY + iDY;
							if(logLeaves) { console.log(`Calculated final position: (${fX},${fY}), moving other pixel from (${oX},${oY})`) };
							tryMove(otherPixel,fX,fY);
						};
					};
				};
			},
		};

		/*if(!elements.diamond.reactions) { //test reaction
			elements.diamond.reactions = {};
		};

		elements.diamond.reactions.banana_leaf = { "elem2": "dead_plant" };*/

		elements.banana_plant_top = {
			hidden: true,
			color: "#d5e39f",
			tick: function(pixel) {
				if(pixel.bananaRange === null) {
					pixel.bananaRange = randomNumberFromOneToThree();
				};

				if (pixel.age > 30 && pixel.temp < 100) {
					if(!pixel.grewLeftLeaves) {
						for(i = (0 - pixel.leafRange); i < 0; i++) { //left half
							if(i == 0) {
								continue;
							};

							var leafOffset = i; //readability
							var leafX = pixel.x + leafOffset; //set X to banana_plant_top pixel's X + offset/index
							var leafAttachOffset = [1, 0]; //difference 1: attaches rightwards (+) for leaves left (-) of center
							var leafY = pixel.y; //set Y to default banana_plant_top pixel's Y
							if(Math.abs(leafOffset) == pixel.leafRange) {
								leafY++; //place edge leaves 1 pixel downwards;
								leafAttachOffset[1] = -1; //compensate by subtracting 1 from Y attach offset (less Y = higher position, so they attach diagonally up-right or up-left)
							};

							if(outOfBounds(leafX,leafY)) {
								continue;
							};

							if (isEmpty(leafX,leafY,false)) {
								createPixel("banana_leaf",leafX,leafY);
								pixelMap[leafX][leafY].attached = true; //set leaf's attached to true
								pixelMap[leafX][leafY].attachOffsets = leafAttachOffset; //array of 2 numbers
								pixelMap[leafX][leafY].bananaRange = pixel.bananaRange;
								pixel.grewLeftLeaves = true; //difference 2: separate flag for left side
							} else {
								break;
							};
						};
					};

					if(!pixel.grewRightLeaves) {
						for(i = 1; i < (pixel.leafRange + 1); i++) { //right half
							if(i == 0) {
								continue;
							};

							var leafOffset = i; //readability
							var leafX = pixel.x + leafOffset; //set X to banana_plant_top pixel's X + offset/index
							var leafAttachOffset = [-1, 0]; //difference 1: attaches leftwards (-) for leaves right (+) of center
							var leafY = pixel.y; //set Y to default banana_plant_top pixel's Y
							if(Math.abs(leafOffset) == pixel.leafRange) {
								leafY++; //place edge leaves 1 pixel downwards;
								leafAttachOffset[1] = -1; //compensate by subtracting 1 from Y attach offset (less Y = higher position, so they attach diagonally up-right or up-left)
							};

							if(outOfBounds(leafX,leafY)) {
								continue;
							};

							if (isEmpty(leafX,leafY,false)) {
								createPixel("banana_leaf",leafX,leafY);
								pixelMap[leafX][leafY].attached = true; //set leaf's attached to true
								pixelMap[leafX][leafY].attachOffsets = leafAttachOffset; //array of 2 numbers
								pixelMap[leafX][leafY].bananaRange = pixel.bananaRange;
								pixel.grewRightLeaves = true; //difference 2: separate flag for right side
							} else {
								break;
							};
						};
					};
				};
				pixel.age++;
				doDefaults(pixel);
			},
			properties: {
				"age": 0,
				"leafRange": 2 + (Math.floor(Math.random() * 3)), //2-4
				"grewLeftLeaves": false,
				"grewRightLeaves": false,
				"bananaRange": null
			},
			tempHigh: 100,
			stateHigh: "dead_plant",
			tempLow: -2,
			stateLow: "frozen_plant",
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			category: "life",
			state: "solid",
			density: 1500,
		};


		/*elements.cocoa_bean = {
			color: ["#f2ede9", "#f0dfce", "#e8cfb5"],
			behavior: behaviors.SOLID,
			category: "liquids",
			viscosity: 100000,
			state: "liquid",
			density: 593,
			tick: functi
		};*/

	//THERMAL FOUNDATION LIQUIDS, FU BIO-OOZE, AND SOME PYROGENIC MATERIALS

		nonAdjacentCoords = [
			[1, 1],
			[1, -1],
			[-1, 1],
			[-1, -1]
		];

		//pyrotheum

		elements.blazing_pyrotheum = {
			color: "#ffdd55",
			behavior: behaviors.LIQUID,
			tick: function(pixel) {
				if(pixel.temp >= -273 && pixel.temp <= 3707) { //temperature minimum of 3727
					pixel.temp += 50
				} else if(pixel.temp > 3677 && pixel.temp < 3727) {
					pixel.temp = 3727
				}
				var pX = pixel.x;
				var pY = pixel.y;
				for(i = 0; i < adjacentCoords.length; i++) {
					var oX = adjacentCoords[i][0];
					var oY = adjacentCoords[i][1];
					var fX = pX+oX;
					var fY = pY+oY;
					if(!isEmpty(fX,fY,true)) {
						var checkPixel = pixelMap[fX][fY];
						var thisElementName = pixel.element;
						var otherElementName = checkPixel.element;
						var thisElement = elements[pixel.element];
						var otherElement = elements[checkPixel.element];
						thisElementName === otherElementName ? checkPixel.temp+=0.1 : checkPixel.temp==10;
					} else if(isEmpty(fX,fY,false)) {
						if(Math.random() < 0.05) {
							createPixel("fire",fX,fY);
							var checkPixel = pixelMap[fX][fY];
							checkPixel.temp = pixel.temp;
						}
					};
				};
			},
			viscosity: 1.2**4,
			category: "liquids",
			state: "liquid",
			density:1994,
			insulate:false,
			temp: 3727,
		},

		elements.gelid_cryotheum = {
			color: "#00ddff",
			behavior: behaviors.LIQUID,
			tick: function(pixel) {
				if(pixel.temp >= -223) { //temperature maximum of -223
					pixel.temp -= 50
				} else if(pixel.temp > -223 && pixel.temp < -273) {
					pixel.temp = -223
				}
				var pX = pixel.x;
				var pY = pixel.y;
				for(i = 0; i < adjacentCoords.length; i++) {
					var oX = adjacentCoords[i][0];
					var oY = adjacentCoords[i][1];
					var fX = pX+oX;
					var fY = pY+oY;
					if(!isEmpty(fX,fY,true)) {
						var checkPixel = pixelMap[fX][fY];
						var thisElementName = pixel.element;
						var otherElementName = checkPixel.element;
						var thisElement = elements[pixel.element];
						var otherElement = elements[checkPixel.element];
						thisElementName === otherElementName ? checkPixel.temp-=0.1 : checkPixel.temp-=10;
					};
				};
				/*for(i = 0; i < nonAdjacentCoords.length; i++) {
					var oX = nonAdjacentCoords[i][0];
					var oY = nonAdjacentCoords[i][1];
					var fX = pX+oX;
					var fY = pY+oY;
					if(isEmpty(fX,fY,false)) {
						if(Math.random() < 0.025) {
							createPixel("snow",fX,fY);
							var checkPixel = pixelMap[fX][fY];
							checkPixel.temp = pixel.temp;
						};
					};
				};*/ //It should create snow, but the snow freezes into ice and it leaves unsightly floating ice everywhere.
			},
			viscosity: 3**4,
			category: "liquids",
			state: "liquid",
			density:3988,
			insulate:false,
			temp: -223,
		},

		elements.tectonic_petrotheum = {
			color: ["#342414","#3C2414","#2C1C14","#543424","#643C28","#74442C"],
			behavior: [
				"XX|XX|XX",
				"M2|XX|M2",
				"M1|SW:dust AND M1|M1",
			],
			tick: function(pixel) { //Code from R74n/vanilla "smash" tool
				var pX = pixel.x;
				var pY = pixel.y;
				for(i = 0; i < adjacentCoords.length; i++) {
					var oX = adjacentCoords[i][0];
					var oY = adjacentCoords[i][1];
					var fX = pX+oX;
					var fY = pY+oY;
					if(!isEmpty(fX,fY,true)) {
						var checkPixel = pixelMap[fX][fY];
						var otherElement = elements[checkPixel.element];
						if (typeof(otherElement.breakInto) !== "undefined") {
							var hardness = otherElement.hardness ?? 0;
							if (Math.random() < (1 - hardness)) {
								var breakInto = otherElement.breakInto;
								// if breakInto is an array, pick one
								if (Array.isArray(breakInto)) {
									breakInto = breakInto[Math.floor(Math.random() * breakInto.length)];
								};
								changePixel(checkPixel,breakInto);
							}
						}
					};
				};
			},
			temp: 120,
			viscosity: 1.5**4,
			category: "liquids",
			state: "liquid",
			density:3988,
			insulate:false,
		};

		elements.zephyrean_aerotheum = {
			color: ["#FFFCD9","#FEFFFC","#FDFFDB","#FFFFE8","#FBF6D3","#F1EDD0"],
			behavior: behaviors.AGLIQUID,
			viscosity: 0.1**4,
			category: "liquids",
			state: "liquid",
			density:-800,
			insulate:false,
			tick: function(pixel) {
				//"Projectiles that come into contact with zephyrean aerotheum are sent flying in a random direction away from the fluid."
				var pX = pixel.x;
				var pY = pixel.y;
				for(i = 0; i < adjacentCoords.length; i++) {
					var oX = adjacentCoords[i][0];
					var oY = adjacentCoords[i][1];
					var fX = pX+oX;
					var fY = pY+oY;
					if(!isEmpty(fX,fY,true)) {
						var checkPixel = pixelMap[fX][fY];
						var thisElementName = pixel.element;
						var otherElementName = checkPixel.element;
						var thisElement = elements[pixel.element];
						var otherElement = elements[checkPixel.element];
						if(otherElement.movable) {
							if(typeof(checkPixel.vx) === "undefined") {
								checkPixel.vx = 0;
							};
							if(typeof(checkPixel.vy) === "undefined") {
								checkPixel.vy = 0;
							};
							if(Math.random() < 1/3) {
								var randomVxChange = Math.floor(Math.random() * 9) - 4; //random value from -3 to 3
								var randomVyChange = Math.floor(Math.random() * 9) - 4; //different random value from -3 to 3
								//Notes
								/*
									Positive vx = right
									Positive vy = down
									adjacentCoords[0]: [0, 1]	is downward; when it is detected, the pixel there should be sent farther down (positive vy).
									adjacentCoords[1]: [0, -1]	is upward; when it is detected, the pixel there should be sent farther up (negative vy).
									adjacentCoords[2]: [1, 0]	is rightward; when it is detected, the pixel there should be sent farther right (positive vx).
									adjacentCoords[3]: [-1, 0]	is leftward; when it is detected, the pixel there should be sent farther left (negative vx).
								*/
								switch(i) {
									case 0:
										randomVyChange = Math.abs(randomVyChange);
										break;
									case 1:
										randomVyChange = (Math.abs(randomVyChange) * -1) - 1;
										break;
									case 2:
										randomVxChange = Math.abs(randomVxChange);
										break;
									case 3:
										randomVxChange = Math.abs(randomVxChange) * -1;
										break;
									default:
										console.log("Uh-oh, i was somehow above 3!")
								};						
								if(otherElementName !== thisElementName) {
									checkPixel.vx += randomVxChange;
									checkPixel.vy += randomVyChange;
								}
							}
						}
					}
				}
			}
		};

		elements.energized_glowstone = {
			color: ["#fbb204", "#fcf605", "#fce704", "#f8c414", "#f8e814"],
			behavior: [
				"M1 AND SW:light|M1 AND CR:light%40 AND SW:light|M1 AND SW:light",
				"M2 AND CR:light%40|XX|M2 AND CR:light%40",
				"XX|CR:light%40|XX",
			],
			viscosity: 0.1**4,
			category: "liquids",
			state: "liquid",
			density:-500,
			insulate:false, //TODO: > Energized glowstone source blocks will gradually float upwards if there are no blocks above them. If they float at high levels (layers 120 and above by default) they will condense back into solid glowstone. They will also condense at 80% of this height if the fluid has no space to flow.
		},

		elements.resonant_ender = {
			color: ["#062c2c", "#062c2c", "#19a8a8", "#0a4646", "#1f8c8e", "#0c5c54", "#0c5c54"],
			behavior: behaviors.LIQUID,
			tick: function(pixel) {
				for (let i = -2; i < 3; i++) {
					for (let j = -2; j < 3; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (lifeArray.includes(pixelMap[pixel.x+j][pixel.y+i].element)) {
								pixel.eeex = pixel.x + Math.floor(Math.random() * ((2 * 8) + 1)) - 8
								pixel.eeey = pixel.y + Math.floor(Math.random() * ((2 * 8) + 1)) - 8
								//if human
								//handle heads
								if(pixelMap[pixel.x+j][pixel.y+i].element == "head") {
									if(isEmpty(pixel.eeex,pixel.eeey) && !outOfBounds(pixel.eeex,pixel.eeey) && isEmpty(pixel.eeex,pixel.eeey+1) && !outOfBounds(pixel.eeex,pixel.eeey+1)) {
										tryMove(pixelMap[pixel.x+j][pixel.y+i],pixel.eeex,pixel.eeey)
										tryMove(pixelMap[pixel.x+j][pixel.y+i+1],pixel.eeex,pixel.eeey+1)
									}
								} else if(pixelMap[pixel.x+j][pixel.y+i].element == "body") {

								} else {
									if(isEmpty(pixel.eeex,pixel.eeey) && !outOfBounds(pixel.eeex,pixel.eeey)) {
										tryMove(pixelMap[pixel.x+j][pixel.y+i],pixel.eeex,pixel.eeey)
									}
								}
							}
						}
					}
				}
			},
			category: "liquids",
			density: 3000,
			state: "liquid",
			viscosity: 3**4,
		}

		elements.redstone_dust.tempHigh = 2500;

		elements.redstone_dust.stateHigh = "destabilized_redstone";

		elements.redstone_dust.conduct = 0.9;

		elements.redstone_dust.colorOn = ["#FF2424","#FF0000","#FF1200"];
		elements.redstone_dust.color = ["#7f0000","#5f0000","#5f0500"];

		elements.destabilized_redstone = {
			color: ["#9e0303", "#98061a", "#b80704", "#c4020c", "#f70008", "#9e0303", "#98061a", "#b80704", "#e3020a", "#8c0303", "#8c0303"],
			behavior: [
				"XX|SH|XX",
				"M2 AND SH|XX|M2 AND SH",
				"M1|M1 AND SH|M1",
			],
			viscosity: 1.5**4,
			category: "liquids",
			state: "liquid",
			density:1200,
		}

		elements.signalum = {
			color: "#ff9321",
			behavior: behaviors.WALL,
			category: "solids",
			density: 10500,
			conduct: 1,
			tempHigh: 1550,
			stateHigh: "molten_signalum",
			state: "solid",
		}

		elements.molten_sterling ??= {};
		elements.molten_sterling.reactions ??= {};
		elements.molten_sterling.reactions.destabilized_redstone = { "elem1": null, "elem2": "molten_signalum" }

		elements.molten_signalum = {
			color: "#f17414",
			behavior: behaviors.MOLTEN,
			density: 10500*0.9,
			conduct: 0.30,
			temp:600,
			tempLow: 550,
			stateLow: "signalum",
			category: "liquids",
			state: "liquid",
			hidden: true,
		}

		elements.energized_glowstone.reactions ??= {};
		elements.energized_glowstone.reactions.gelid_cryotheum = { "elem1":"glowstone_dust" };

		elements.glowstone_dust.behavior = [ //it should emit light, right?
			"CR:light%0.025|CR:light%0.025|CR:light%0.025",
			"CR:light%0.025|XX|CR:light%0.025",
			"CR:light%0.025|CR:light%0.025|CR:light%0.025"
		];

		runAfterLoad(function() {
			lifeArray = Object.keys(elements).filter(function(e) {
				return elements[e].category == "life";
			})
		});

		elements.bioooze = {
			color: ["#53FF4F", "#53FF4F", "#06DE00", "#04A600", "#036E00"],
			behavior: behaviors.LIQUID,
			tempHigh: 100,
			stateHigh: ["plague","slime","steam","poison"],
			//tempLow: -4,
			//stateLow: "bioooze_ice",
			category: "liquids",
			heatCapacity: 3.52, //unimplemented feature
			name: "bio-ooze",
			reactions: {
				"water": { "elem1":"slime", "elem2":"slime" }, //balance
				"poison": { "elem1":"slime", "elem2":"slime" }, //balance
				//"acid": { "elem1":"wastestone" }, //acid should be sulfuric acid and product should be wastestone
				//"elder_fluid": { "elem1":"corrupt_slime" }, //acid should be sulfuric acid and product should be wastestone
				//"mercury": { "elem1":"liquid_protocite" }, //acid should be sulfuric acid and product should be wastestone
				//"blue_grav_liquid": { "elem1":"blue_grav_liquid" }, //bgl would set gravity to upwards gravity
				"blood": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "infection" },
				"soap": { "elem1": "slime", "chance": 0.02 },
				"plant": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"grass": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"algae": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"mushroom_spore": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"lichen": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"rat": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"frog": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"fish": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"bird": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"head": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"body": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"ant": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "dust" },
				"worm": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "dust" },
				"fly": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "dust" },
				"firefly": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "dust" },
				"bee": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "dust" },
				"slug": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "dust" },
				"snail": { "elem1": ["bioooze","bioooze","bioooze","bioooze","poison","slime",null], "elem2": "calcium" },
				"sapling": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"root": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"flower_seed": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"pistil": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"petal": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"grass_seed": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "dead_plant" },
				"meat": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "rotten_meat" },
				"wood": { "elem1": ["bioooze","bioooze","poison","slime",null], "elem2": "sawdust", "chance": 0.25 }
			},
			/*reactions: {
				"dirt": { // React with (water reacts with dirt to make mud)
					"elem1": null, // First element transforms into; in this case, water deletes itself
					"elem2": "mud", // Second element transforms into; in this case, dirt turns to mud
				},
				"sand": { "elem1": null, "elem2": "wet_sand", },
				"salt": { "elem1": "salt_water", "elem2": null },
				"sugar": { "elem1": "sugar_water", "elem2": null, },
				"dust": { "elem1": "dirty_water", "elem2": null, },
				"ash": { "elem1": "dirty_water", "elem2": null, },
				"cyanide": { "elem1": "dirty_water", "elem2": null, },
				"carbon_dioxide": { "elem1": "seltzer", "elem2": null, "oneway":true },
				"sulfur": { "elem1": "dirty_water", "elem2": null, },
				"rat": { "elem1": "dirty_water", chance:0.005 },
				"plague": { "elem1": "dirty_water", "elem2": null, },
				"rust": { "elem1": "dirty_water", chance:0.005 },
				"fallout": { "elem1": "dirty_water", chance:0.25 },
				"radiation": { "elem1": "dirty_water", chance:0.25 },
				"uranium": { "elem1": "dirty_water", chance:0.25 },
				"quicklime": { "elem1": null, "elem2": "slaked_lime", },
				"rock": { "elem2": "wet_sand", "chance": 0.00035 },
				"ruins": { "elem2": "rock", "chance": 0.00035 },
				"mudstone": { "elem2": "mud", "chance": 0.00035 },
				"methane": { "elem1":"primordial_soup", "elem2":"primordial_soup", tempMin:60, charged:true },
				"ammonia": { "elem1":"primordial_soup", "elem2":"primordial_soup", tempMin:60, charged:true }
			},*/
			state: "liquid",
			density: 1.03,
			conduct: 0.0008,
			stain: 0.2,
			viscosity: 60,
			description: "A particularly potent toxic sludge loaded with parasites and ickiness.",
		};

		function threshholdedPyrogen(pixel,threshholdTemp,baseHeat,divisor) {
			if(pixel.temp < threshholdTemp) {
				pixel.temp += Math.max(baseHeat,(threshholdTemp - pixel.temp) / divisor);
			} else {
				pixel.temp += baseHeat;
			};
		};

		function tpHeatCalc(startTemp,threshholdTemp,baseHeat,divisor) {
			if(startTemp < threshholdTemp) {
				return Math.max(baseHeat,(threshholdTemp - startTemp) / divisor);
			} else {
				return baseHeat;
			};
		};

		function itfChanceCalc(baseHeat,divisor,chanceLimit) {
			return Math.min(chanceLimit,(baseHeat / divisor))
		};

		function inferniumTempFire(pixel,divisor,chanceLimit) {
			if(Math.random() < Math.min(chanceLimit,(pixel.temp / divisor))) { //fire depending on temp
				var randomCoord = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)]; //random place
				var nx = pixel.x+randomCoord[0];
				var ny = pixel.y+randomCoord[1];
				if(isEmpty(nx,ny)) { //if empty
					createPixel("fire",nx,ny); //create fire at coord coords
					pixelMap[nx][ny].temp = pixel.temp; //set temp
				};
			};
		};

		elements.pyreite = {
			color: ["#cc674b","#e06e41","#f77320","#f77320","#fa9b28","#fac228"],
			behavior: behaviors.WALL,
			tempHigh: 5500, //Instead of making my own headcanons, I'm using Omniblog Of Starbound's headcanons :eggTF:
			category: "solids",
			density: 14600,
			conduct: 0.66,
			hardness: 0.79,
			tick: function(pixel) {
				threshholdedPyrogen(pixel,1722,0.25,512);
			},
		};

		elements.infernium = {
			color: ["#bf4b39","#e68453","#f7994d","#f7994d","#ffa154","#ffe875"],
			behavior: [
				"XX|CR:fire%0.01|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			tempHigh: 5526, //I have no headcanon to turn to for this mod so have multiples of 2763
			category: "solids",
			density: 13815,
			conduct: 0.691,
			hardness: 0.79,
			tick: function(pixel) {
				threshholdedPyrogen(pixel,1763,0.32,512);
				inferniumTempFire(pixel,40000,0.2);
			},
		};

		elements.molten_pyreite = {
			tick: function(pixel) {
				pixel.temp += 0.25;
			},
			reactions: {
				molten_infernium: { elem1: "molten_infernyrite", elem2: "molten_infernyrite", temp1: 304, temp2: 304 }
			},
		};

		elements.molten_infernium = {
			tick: function(pixel) {
				pixel.temp += 0.32;
				inferniumTempFire(pixel,40000,0.2);
			},
		};

		elements.infernyrite = {
			color: ["#d45f2c","#f59449","#f7994d","#fcaa4c","#fab973","#ffea8c"],
			behavior: [
				"XX|CR:fire%0.01|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			tempHigh: 9901,
			category: "solids",
			density: 14197,
			conduct: 0.63,
			hardness: 0.8,
			tick: function(pixel) {
				threshholdedPyrogen(pixel,2501,0.79,480);
				inferniumTempFire(pixel,40000,0.2);
			},
		};

		elements.molten_infernyrite = {
			tick: function(pixel) {
				pixel.temp += 0.79;
				inferniumTempFire(pixel,40000,0.2); //"canonically", the dilution and the pyrogenic synergy or whatever you want to call it just so happen to cancel out
			},
			reactions: {
				blazing_pyrotheum: {
					elem1: "molten_infernyreitheum",
					elem2: ["blazing_pyrotheum","molten_infernyreitheum"],
					temp1: 1043,
					temp2: 1043
				}
			},
		};

		elements.infernyreitheum = {
			color: ["#f2a863","#faaf4d","#ffb547","#fcaa4c","#fcd64c","#fff6ba"],
			behavior: [
				"XX|CR:fire%0.08|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			tempHigh: 11052,
			category: "solids",
			density: 15064,
			conduct: 0.52,
			hardness: 0.8,
			tick: function(pixel) {
				threshholdedPyrogen(pixel,8932,2.03,416);
				inferniumTempFire(pixel,25000,0.4); //pyrotheum makes a lot of fire
			},
		};

		function pyrestoneInfernyreitheumReaction(pyrestoneName) {
			return {
				elem1: "molten_pyrinfernyreitheum",
				elem2: [pyrestoneName,pyrestoneName,"molten_pyrinfernyreitheum"],
				temp1: 3085,
				temp2: 3085
			};
		};

		elements.molten_infernyreitheum = {
			tick: function(pixel) {
				pixel.temp += 2.03;
				inferniumTempFire(pixel,25000,0.4);
			},
			reactions: {
				unignited_pyrestone: pyrestoneInfernyreitheumReaction("unignited_pyrestone"),
				ignited_pyrestone: pyrestoneInfernyreitheumReaction("ignited_pyrestone"),
				heated_pyrestone: pyrestoneInfernyreitheumReaction("heated_pyrestone"),
				burning_pyrestone: pyrestoneInfernyreitheumReaction("burning_pyrestone"),
				blazing_pyrestone: pyrestoneInfernyreitheumReaction("blazing_pyrestone"),
				fiery_pyrestone: pyrestoneInfernyreitheumReaction("fiery_pyrestone")
			}
		};

		elements.pyrinfernyreitheum = {
			color: ["#e6c087","#f7c76d","#ffd79c","#ffd79c","#ffe387","#ffffd9"],
			behavior: [
				"XX|CR:fire%2|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			tempHigh: 18254,
			category: "solids",
			density: 17042,
			conduct: 0.25,
			hardness: 0.8,
			tick: function(pixel) {
				threshholdedPyrogen(pixel,15944,7.01,352);
				inferniumTempFire(pixel,15000,0.8); //pyrotheum makes a lot of fire
			},
		};

		elements.molten_pyrinfernyreitheum = {
			tick: function(pixel) {
				pixel.temp += 7.01;
				inferniumTempFire(pixel,15000,0.8); //pyrotheum makes a lot of fire
			}
		};

	//MORE MORTAL PLANTS ##

		var killerArray = ["radiation", "alcohol", "soap", "acid", "ammonia", "acid_gas", "bleach", "poison", "ice_nine", "methanol", "propanol", "butanol", "isopropanol", "phenol"]; //https://pubmed.ncbi.nlm.nih.gov/16986801/
		var plantArray = ["plant", "frozen_plant", "grass", "algae", "sapling", "seeds", "grass_seed", "wheat_seed", "wheat", "flower_seed", "pistil", "petal", "vine", "bamboo", "bamboo_plant", "corn_seed", "potato_seed", "root", "berry_seed", "old_berry_leaf", "berry_leaf", "berry", "banana_pseudostem", "banana_peduncle_1", "banana_peduncle_2", "petal", "banana_leaf", "banana_plant_top"];

		runAfterAutogen(function() {
			//Try to detect radioactive elements (it's not perfect, but it's better than nothing)
			var rads = Object.keys(elements).filter(function(name) {
				return !!((elements[name].behavior ?? "undefined").toString().match(/C[RH]:[A-Za-z0-9_,]*radiation/))
			});
			killerArray = killerArray.concat(rads);
			for(i = 0; i < killerArray.length; i++) {
				if(!elements[killerArray[i]].reactions) {
					elements[killerArray[i]].reactions = {}
				}
				for(j = 0; j < plantArray.length; j++) {
					elements[killerArray[i]].reactions[plantArray[j]] = { "elem1":null, "elem2":"dead_plant" }
				};
			};
		});

	//PAINT EVENT ##

		randomEvents.paint = function() {
			// set the color of a random circle to a random color
			var x = Math.floor(Math.random()*(width-1))+1;
			var y = Math.floor(Math.random()*(height-1))+1;
			var randomR = Math.floor(Math.random() * 256);
			var randomG = Math.floor(Math.random() * 256);
			var randomB = Math.floor(Math.random() * 256);
			var radius = Math.floor(Math.random()*5)+2;
			var rColor = "rgb(" + randomR + "," + randomG + "," + randomB + ")";
			var coords = circleCoords(x,y,radius);
			for (var i = 0; i < coords.length; i++) {
				var coord = coords[i];
				if (!outOfBounds(coord.x,coord.y) && !isEmpty(coord.x,coord.y)) {
					pixelMap[coord.x][coord.y].color = rColor;
				};
			};
		};

	//CONFIGURABLE PAGE BACKGROUND COLOR ##

		var backgroundUseStrings = ["bg","background","settings.bg"]

		if(urlParams.get('pageColor') != null) { //null check
			//Old method (as a query parameter)
			color = urlParams.get('pageColor');
			if(color === "" || color === null) { //NaN check
				color = "black";
			};
			if(backgroundUseStrings.includes(color.toLowerCase())) {
			!settings.bg ? color = "black" : color = settings.bg;
				color = settings.bg;
			};

			color_Would_Be_A_Triplet_If_It_Started_With_An_Octothorpe = null;
			color_Is_Supported_As_A_Background_By_The_Browser = null;

			if( /^#([0-9A-F]{3}){1,2}$/i.test("#" + color) ) {
				color_Would_Be_A_Triplet_If_It_Started_With_An_Octothorpe = true
			} else {
				color_Would_Be_A_Triplet_If_It_Started_With_An_Octothorpe = false
			}

			if( CSS.supports('background',color) ) {
				color_Is_Supported_As_A_Background_By_The_Browser = true
			} else {
				color_Is_Supported_As_A_Background_By_The_Browser = false
			}

			if(color_Is_Supported_As_A_Background_By_The_Browser == false && color_Would_Be_A_Triplet_If_It_Started_With_An_Octothorpe == true) {
				color = "#" + color
			}

			document.body.style.background = color;
		} else {
			//As a setting
			runAfterLoad(function() {
				var settingsMenu = document.getElementById("settingsMenu").getElementsByClassName("menuText")[0];

				var settingNodes = [...settingsMenu.childNodes].filter(function(node) { return node.nodeType == 1 });
				var lastSetting = settingNodes[settingNodes.length - 1];
				//console.log(lastSetting);
				//console.log(lastSetting.getAttribute("style"));
				//console.log(lastSetting.getAttribute("style"));

				//Shape setting
				var bgSettingSpan = document.createElement("span");
				bgSettingSpan.setAttribute("setting","pageBG");
				bgSettingSpan.setAttribute("class","setting-span");
				bgSettingSpan.textContent = "Page Background ";
					var settingPicker = document.createElement("input");
					settingPicker.setAttribute("type","color");
					settingPicker.setAttribute("value",settings.pageBG ?? "#000000");
					settingPicker.setAttribute("onchange","settings.pageBG = this.value; document.body.style.background = this.value; saveSettings();");
					bgSettingSpan.appendChild(settingPicker);
				settingsMenu.appendChild(bgSettingSpan);
			});

			settings.pageBG ??= "#000000"; saveSettings();
			document.body.style["background-color"] = settings.pageBG;
		};

	//GASEOUS FORMS AND BOILING OF SOME ELEMENTS ##

		//glass {

			elements.molten_glass = {
				tempHigh: 2200,
				stateHigh: "vaporized_glass",
			}

			elements.vaporized_glass = {
				color: ["#D6B049","#E8D957","#E8AE57"],
				behavior: [
					"M2|M1|M2",
					"M1|XX|M1",
					"M2|M1|M2",
				],
				reactions: {
					"vaporized_glass": { "elem1": null, "elem2": "hot_glass_cloud", "chance":0.3, "y":[0,15] },
					"hot_glass_cloud": { "elem1": "hot_glass_cloud", "chance":0.4, "y":[0,15] },
				},
				density: 2, //very rough approximation based on https://nvlpubs.nist.gov/nistpubs/jres/46/jresv46n3p176_A1b.pdf
				temp: 2300, //https://www.sciencealert.com/did-this-piece-of-glass-really-break-a-law-of-thermodynamics
				tempLow: 2200,
				stateLow: "molten_glass",
				category: "gases",
				state: "gas",
				hidden: true
			},

			elements.hot_glass_cloud = {
				color: ["#B69089","#C8B997","#C88E77"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:molten_glass%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 2,
				temp: 2300,
				tempLow: 2200,
				stateLow: "cold_glass_cloud",
				category: "gases",
				state: "gas"
			},

			elements.cold_glass_cloud = {
				color: ["#967089","#A89997","#A86E77"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:glass_shard%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 2,
				temp: 2000,
				tempHigh: 2200,
				stateHigh: "hot_glass_cloud",
				category: "gases",
				state: "gas"
			},

		//}

		// nitroglycerin {

			elements.nitro_gas = {
				color: "#89d162",
				behavior: behaviors.GAS,
				behaviorOn: [
					"XX|XX|XX",
					"XX|EX:10|XX",
					"XX|XX|XX",
				],
				conduct: 1,
				category: "weapons",
				tempHigh: 218,
				stateHigh: "explosion",
				tempLow: 50,
				stateLow: "nitro",
				burn: 100,
				burnTime: 1,
				burnInto: "explosion",
				breakInto: "explosion",
				viscosity: 36,
				state: "liquid",
				density: 1600,
				excludeRandom: true,
				alias: "nitroglycerin gas"
			};

			elements.nitro.tempHigh = 50;
			elements.nitro.stateHigh = "nitro_gas";

		//}

		// ash {

			elements.ash.tempHigh = 1200		  //https://www.quora.com/Can-you-melt-ashes
			elements.ash.stateHigh = "molten_ash" //https://www.sciencedirect.com/science/article/pii/S1877705817326772

			elements.molten_ash = {
				color: ["#df6f30","#df8c30","#df4d30"],
				behavior: behaviors.MOLTEN,
				temp: 1300,
				tempLow: 1200,
				stateLow: "ash",
				tempHigh: 1700, //https://authors.library.caltech.edu/58447/1/018-Senior.pdf
								//https://pubs.acs.org/doi/10.1021/ef049693l
				stateHigh: "vaporized_ash",
				viscosity: 10000,
				category: "liquids",
				state: "liquid",
				density: 2725
			},

			elements.vaporized_ash = {
				color: ["#df9f50","#dfbc50","#df7d50"],
				behavior: [
					"M2|M1|M2",
					"M1|XX|M1",
					"M2|M1|M2",
				],
				reactions: {
					"vaporized_ash": { "elem1": null, "elem2": "hot_ash_cloud", "chance":0.3, "y":[0,15] },
					"hot_ash_cloud": { "elem1": "hot_ash_cloud", "chance":0.4, "y":[0,15] },
				},
				temp: 1800,
				tempLow: 1700,
				stateLow: "molten_ash",
				category: "gases",
				state: "gas",
				hidden: true,
				density: 3
			},

			elements.hot_ash_cloud = {
				color: ["#bf8f50","#bfac50","#bf7d50"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:molten_ash%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 0.7,
				temp: 1800,
				tempLow: 1700,
				stateLow: "cold_ash_cloud",
				category: "gases",
				state: "gas"
			},

			elements.cold_ash_cloud = {
				color: ["#af8f50","#ab9c50","#af6d50"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:ash%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 0.7,
				temp: 1600,
				tempHigh: 1700,
				stateHigh: "hot_ash_cloud",
				category: "gases",
				state: "gas"
			},

		//}

		// charcoal {

			elements.charcoal.tempHigh = 800
			elements.charcoal.stateHigh = "carbon_dioxide"

		//}

		//carbon dioxide {

			/*fuck this, i can't work out the offset-infested math
			function carbonDioxideDecompRatio(temp) {
				//
				//	K is the ratio of O_2 to CO_2
				//	If K = 100, there is 100 times more O_2
				//	If K = 1, there is a 1:1 ratio
				//
				return Math.E**(((1110190+(13.083*(temp-298)))-(temp*(149.498+(13.083*(Math.log(temp/298))))))/(-8.31446*temp))
			}

			function carbonDioxideDecompChance(temp) {
				//Expected 0.5 at 6275.6434478747902
				if(typeof(temp) === "undefined") {
					throw new Error("Temp must be specified~");
				};
				if(typeof(temp) == "string") {
					temp = parseFloat(temp);
				};
				if(isNaN(temp)) {
					throw new TypeError(typeof(temp) == "number" ? "Temp cannot be NaN~" : "Temp must be a number~");
				};
				if(temp == Infinity) {
					return 1;
				};
				if(temp <= 0) {
					return 0;
				};
				var K = carbonDioxideDecompRatio(temp);
				return 1-(1/(K+1));
			};
			*/

			//Mass given is the molar mass of O_2 molecule (31.999 g)
			//O_2 bond energy is 495 kJ/mol
			//Heat capacity is 0.918 J/(g*K)
			//in case the link goes down: c = Q/(m * delta-T); c = capacity, m = mass, delta-T = temp change, energy = Q
			//https://www.calctool.org/thermodynamics/specific-heat
			tupleAdverbs = ['Nullly', 'Singly', 'Doubly', 'Triply', 'Quadruply', 'Quintuply', 'Sextuply', 'Septuply', 'Octuply', 'Nonuply', 'Decuply', 'Undecuply', 'Duodecuply', 'Tredecuply', 'Quattuordecuply', 'Quindecuply', 'Sexdecuply', 'Septendecuply', 'Octodecuply', 'Novemdecuply', 'Vigintuply', 'Unvigintuply', 'Duovigintuply', 'Trevigintuply', 'Quattuorvigintuply', 'Quinvigintuply', 'Sexvigintuply', 'Septenvigintuply', 'Octovigintuply', 'Novemvigintuply', 'Trigintuply'].map(x => x.toLowerCase());

		//}

		// baking soda {

			elements.baking_soda.tempHigh = 150,
			elements.baking_soda.stateHigh = ["water","carbon_dioxide","calcined_soda"]

			// decomposition result {

				elements.calcined_soda = { //TODO: decomposition?
					color: "#ededed",
					behavior: behaviors.POWDER,
					reactions: {
						"water": { "elem1": "washing_soda", "elem2": null } //should be 10x water
						//"carbon_dioxide": not possible: Na_{2}CO_{3} + CO_{2} + H_{2}O → 2NaHCO_{3}
					},
					category: "powders",
					state: "solid",
					density: 2540,
					tempHigh: 851,
				}

				if(!elements.molten_calcined_soda) {
					elements.molten_calcined_soda = {}
				}

				elements.molten_calcined_soda.temp = 1700
				elements.molten_calcined_soda.tempHigh = 1600
				elements.molten_calcined_soda.stateHigh = "vaporized_calcined_soda"
				elements.molten_calcined_soda.density = 1920

				elements.vaporized_calcined_soda = {
					color: ["#ffbf60","#ffdc60","#ff9d60"],
					behavior: [
						"M2|M1|M2",
						"M1|XX|M1",
						"M2|M1|M2",
					],
					reactions: {
						"vaporized_calcined_soda": { "elem1": null, "elem2": "hot_calcined_soda_cloud", "chance":0.3, "y":[0,15] },
						"hot_calcined_soda_cloud": { "elem1": "hot_calcined_soda_cloud", "chance":0.4, "y":[0,15] },
					},
					temp: 1700,
					tempLow: 1600,
					stateLow: "molten_calcined_soda",
					category: "gases",
					state: "gas",
					hidden: true,
					density: 1.5, //bs
				},

				elements.hot_calcined_soda_cloud = {
					color: ["#cfbf70","#cfcc70","#cf9d70"],
					behavior: [
						"XX|XX|XX",
						"M1%7|CH:molten_calcined_soda%0.05|M1%7",
						"XX|XX|XX",
					],
					density: 0.7,
					temp: 1700,
					tempLow: 1600,
					stateLow: "cold_calcined_soda_cloud",
					category: "gases",
					state: "gas",
				},

				elements.cold_calcined_soda_cloud = {
					color: ["#afaf70","#afac70","#af8d70"],
					behavior: [
						"XX|XX|XX",
						"M1%7|CH:calcined_soda%0.05|M1%7",
						"XX|XX|XX",
					],
					density: 0.7,
					temp: 1500,
					tempHigh: 1600,
					stateHigh: "hot_calcined_soda_cloud",
					category: "gases",
					state: "gas",
				},

			//}

			// decomp hydrate {

				elements.washing_soda = {
					color: "#ededed",
					behavior: behaviors.POWDER,
					//no reactions because it always requires ******* water
					category: "powders",
					state: "solid",
					density: 1460,
					tempHigh: 400,
					stateHigh: ["water","calcined_soda"],
				}

			//}

			//alkalinities {

				elements.acid.reactions.baking_soda = { "elem1":"neutral_acid", "elem2":null }
				elements.acid.reactions.calcined_soda = { "elem1":"neutral_acid", "elem2":null }
				elements.acid.reactions.washing_soda = { "elem1":"neutral_acid", "elem2":null }

			//}

		//}

		// calcium {

			elements.molten_calcium = {
				tempHigh: 2200,
				stateHigh: "vaporized_calcium",
			}

			elements.vaporized_calcium = {
				color: ["#ffc94a", "#fcd34c", "#ffae36", "#ff9c40","#ffcd90","#cf8d50"],
				behavior: [
					"M2|M1|M2",
					"M1|XX|M1",
					"M2|M1|M2",
				],
				reactions: {
					"vaporized_calcium": { "elem1": null, "elem2": "hot_calcium_cloud", "chance":0.3, "y":[0,15] },
					"hot_calcium_cloud": { "elem1": "hot_calcium_cloud", "chance":0.4, "y":[0,15] },
				},
				density: 1.5, //most of these density values are complete bullshit due to a lack of research
				temp: 1550,
				tempLow: 1484,
				stateLow: "molten_calcium",
				category: "gases",
				state: "gas",
				hidden: true
			},

			elements.hot_calcium_cloud = {
				color: ["#dfa98a", "#dcb38c", "#df8e76", "#ef8c60","#efbdb0","#af8d70"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:molten_calcium%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 1.5,
				temp: 1550,
				tempLow: 842,
				stateLow: "cold_calcium_cloud",
				category: "gases",
				state: "gas"
			},

			elements.cold_calcium_cloud = {
				color: ["#bf998a", "#bca38c", "#bf8e76", "#cf8c60","#cfadb0","#9f8d70"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:calcium%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 2,
				temp: 800,
				tempHigh: 842,
				stateHigh: "hot_calcium_cloud",
				category: "gases",
				state: "gas",
			}

		//}

		// clay {

			if(!elements.baked_clay) {
				elements.baked_clay = {}
			}

			elements.baked_clay.tempHigh = 1600 //the range of melting points online is so fucking wide
			elements.baked_clay.stateHigh = "molten_clay"

			elements.molten_clay = {
				color: ["#ff6d23","#ff5723","#ff4100"],
				behavior: [
					"XX|CR:fire%2.5|XX",
					"M2|XX|M2",
					"M1|M1|M1",
				],
				temp: 1700,
				tempLow: 1600,
				stateLow: "baked_clay",
				viscosity: 10000,
				hidden: true,
				state: "liquid",
				density: 1800,
				tempHigh: 2980,
				stateHigh: "vaporized_clay",
				category: "liquids",
			}

			elements.vaporized_clay = {
				color: ["#ff8d43","#ff7743","#ff6120"],
				behavior: [
					"M2|M1|M2",
					"M1|XX|M1",
					"M2|M1|M2",
				],
				reactions: {
					"vaporized_clay": { "elem1": null, "elem2": "hot_clay_cloud", "chance":0.3, "y":[0,15] },
					"hot_clay_cloud": { "elem1": "hot_clay_cloud", "chance":0.4, "y":[0,15] },
				},
				density: 1.6,
				temp: 1700,
				tempLow: 1600,
				stateLow: "molten_clay",
				category: "gases",
				state: "gas",
				hidden: true
			},

			elements.hot_clay_cloud = {
				color: ["#ff9945", "#fca347", "#ff7e31"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:molten_clay%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 1.5,
				temp: 1550,
				tempLow: 842,
				stateLow: "cold_clay_cloud",
				category: "gases",
				state: "gas"
			},

			elements.cold_clay_cloud = {
				color: ["#ef7945", "#ec8347", "#ef5e31"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:baked_clay%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 2,
				temp: 800,
				tempHigh: 842,
				stateHigh: "hot_clay_cloud",
				category: "gases",
				state: "gas"
			},

		//}

		// salt {

			elements.molten_salt = {
				tempHigh: 1465,
				stateHigh: "vaporized_salt",
			}

			elements.vaporized_salt = {
				color: ["#ff9f60","#ffbc60","#ff7d60"],
				behavior: [
					"M2|M1|M2",
					"M1|XX|M1",
					"M2|M1|M2",
				],
				reactions: {
					"vaporized_salt": { "elem1": null, "elem2": "hot_salt_cloud", "chance":0.3, "y":[0,15] },
					"hot_salt_cloud": { "elem1": "hot_salt_cloud", "chance":0.4, "y":[0,15] },
				},
				density: 1946,
				temp: 1550,
				tempLow: 1465,
				stateLow: "molten_salt",
				category: "gases",
				state: "gas",
				hidden: true
			},

			elements.hot_salt_cloud = {
				color: ["#ef8f30","#efac60","#ef6d60"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:molten_salt%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 2.2,
				temp: 1550,
				tempLow: 801,
				stateLow: "cold_salt_cloud",
				category: "gases",
				state: "gas"
			},

			elements.cold_salt_cloud = {
				color: ["#cf7f60","#cf9c60","#cf7d60"],
				behavior: [
					"XX|XX|XX",
					"M1%7|CH:salt%0.05|M1%7",
					"XX|XX|XX",
				],
				density: 2.2,
				temp: 700,
				tempHigh: 801,
				stateHigh: "hot_salt_cloud",
				category: "gases",
				state: "gas",
			}

		//}

		runAfterLoad(function() {
			if(elements.acid_gas.tempHigh) {
				delete elements.acid_gas.tempHigh
			}
			if(elements.acid_gas.stateHigh) {
				delete elements.acid_gas.stateHigh
			}
			elements.acid.stateHigh = "acid_gas"
			elements.acid_gas.tempLow = 400
			elements.acid_gas.stateLow = "acid"
			elements.yogurt.tempHigh = 400
			elements.yogurt.stateHigh = "ash"
			elements.dust.tempHigh = 400
			elements.dust.stateHigh = "fire"

			elements.concoction.reactions.vaporized_glass = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.hot_glass_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.cold_glass_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.molten_ash = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.vaporized_ash = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.hot_ash_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.cold_ash_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.calcined_soda = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.molten_calcined_soda = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.vaporized_calcined_soda = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.hot_calcined_soda_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.cold_calcined_soda_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.washing_soda = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.vaporized_calcium = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.hot_calcium_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.cold_calcium_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.molten_clay = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.vaporized_clay = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.hot_clay_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.cold_clay_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.vaporized_salt = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.hot_salt_cloud = { "elem1": "mistake", "elem2": null }
			elements.concoction.reactions.cold_salt_cloud = { "elem1": "mistake", "elem2": null }

		});

	//CHLORINE TRIFLUORIDE ##

		function finishBurn(pixel) {
			var info = elements[pixel.element];

			var burnInto = info.burnInto;
			if (burnInto == undefined) {
				burnInto = 'fire';
			}
			else if (burnInto instanceof Array) {
				burnInto = burnInto[Math.floor(Math.random()*burnInto.length)];
			}
			if (burnInto == undefined) {
				burnInto = 'fire';
			}
			changePixel(pixel,burnInto,(burnInto !== "smoke"));
			if (info.fireColor != undefined && burnInto == "fire") {
				pixel.color = pixelColorPick(pixel,info.fireColor);
			}
			else {
				pixel.color = pixelColorPick(pixel)
			}
		};

		function clf3Tick(pixel) {
			for(i = 0; i < adjacentCoords.length; i++) {
				var oX = adjacentCoords[i][0];
				var oY = adjacentCoords[i][1];
				var fX = pixel.x+oX;
				var fY = pixel.y+oY;
				if(!isEmpty(fX,fY,true)) {
					var otherPixel = pixelMap[fX][fY];
					var otherElement = otherPixel.element;
					if(otherElement === "water") {
						explodeAt(otherPixel.x,otherPixel.y,7,"fire,hydrofluoric_acid,oxygen,acid,chlorine")
					};
					if(!elements.chlorine_trifluoride.ignore.includes(otherElement)) {
						if(!otherPixel.burning) { otherPixel.burning = true };
						if(!otherPixel.burnStart) { otherPixel.burnStart = pixelTicks };
						var instaburnChance = 0.05 + (pixelTicks - otherPixel.burnStart) / 1000
						if(Math.random() < instaburnChance) {
							finishBurn(otherPixel);
						};
					};
				};
			};
		};

		var clf3IgnoreList = ["FOOF","solid_FOOF","oxygen","liquid_oxygen","oxygen_ice","chlorine","liquid_chlorine","liquid_hydrogen_fluoride","liquid_fluorine","fluorine","fluorine_ice","hydrogen_fluoride","hydrofluoric_acid","hydrofluoric_acid_gas","fire","acid_gas","neutral_acid","acid","acid_cloud","nitrogen","helium","liquid_helium","tralphium","liquid_tralphium","neon","liquid_neon","solid_neon","neon_ice","neon_snow","argon","liquid_argon","solid_argon","argon_ice","argon_snow", "krypton","liquid_krypton","solid_krypton","krypton_ice","krypton_snow", "xenon","liquid_xenon","solid_xenon","xenon_ice","xenon_snow", "radon","liquid_radon","solid_radon","radon_ice","radon_snow","ionized_helium","ionized_tralphium","wall","chlorine_trifluoride","chlorine_trifluoride_ice","chlorine_trifluoride_gas","quartz"];

		//todo: PTFE, passivation

		elements.chlorine_trifluoride = {
			color: "#8aa65b",
			behavior: behaviors.LIQUID,
			//ignore list copied from chem.js
			ignore: clf3IgnoreList, //the elements that don't exist won't trigger any error here becuase the code's just checking pixels' elements against this list
			tick: function(pixel) {
				clf3Tick(pixel);
			},
			category:"liquids",
			state: "liquid",
			density: 1770,
			tempLow: -76.34,
			tempHigh: 11.75,
			temp: 5,
		};

		elements.chlorine_trifluoride_gas = {
			tick: function(pixel) {
				clf3Tick(pixel);
			},
			density: 3.78, //variously or 3.18,
			//tempHigh: 220, //variously or 180,
			//stateHigh: ["chlorine_fluoride","fluorine"],
		};

		elements.chlorine_trifluoride_ice = {
			tick: function(pixel) {
				clf3Tick(pixel);
			},
		};

	//GLENN'S GASES PARTIAL PORT ##

		//Glenn's Gases is licensed under the GNU LGPL.
		//http://www.gnu.org/licenses/lgpl.html
		//https://www.jamieswhiteshirt.com/minecraft/mods/gases/information/?Licensing

		//Coal exists in NM
		runAfterLoad(function() {
			elements.coal.breakInto = "coal_dust"
		});

		elements.coal_dust = {
			color: "#363023",
			behavior: behaviors.GAS,
			tick: function(pixel) {
				if(pixel.burning) {
					explodeAt(pixel.x,pixel.y,5,"fire,ignited_gas")
				}
			},
			category: "gases",
			density: 561,
			state: "gas",
			burn: 132,
			burnTime: 10,
			burnInto: ["ash", "fire", "carbon_dioxide"],
		};

		//Chlorine exists.

		//Natural gas is mostly ammonia, which exists.

		elements.red_gas = {
			color: "#c74c52",
			behavior: behaviors.GAS,
			tick: function(pixel) {
				if(pixel.burning) {
					explodeAt(pixel.x,pixel.y,8,"fire,ignited_gas")
				}
			},
			category: "gases",
			density: 1.5,
			state: "gas",
			burn: 300,
			burnTime: 10,
			burnInto: ["fire", "explosion", "explosion"],

		};

		elements.nitrous_gas = {
			color: "#854428",
			behavior: behaviors.GAS,
			reactions: {
				"water": {"elem1": "acidic_vapour", "elem2": "acidic_vapour"}
			},
			category: "gases",
			density: 1.5,
			state: "gas",
		};

		elements.acidic_vapour = {
			color: ["#5282d1", "#4e6fad"],
			behavior: [
				"M2|M1 AND DB|M2",
				"M1 AND DB|XX|M1 AND DB",
				"M2%50|M1%50 AND DB|M2%50",
			],
			ignore: elements.acid.ignore,
			category: "gases",
			density: 1.5,
			state: "gas",
		};

		elements.void_gas = {
			color: "#111111",
			behavior: behaviors.GAS,
			reactions: {
				"light": { "elem1": null, "chance": 0.1 },
				"fire": { "elem1": null, "chance": 0.08 }
			},
			category: "gases",
			density: 1.5,
			state: "gas",
		};

		elements.electric_gas = {
			color: ["#3693b3", "#246e64"],
			behavior: [
				"M2%33.3 AND CR:electric%1 AND CR:lightning%0.005|M1%33.3 AND CR:electric%1 AND CR:lightning%0.005|M2%33.3 AND CR:electric%1 AND CR:lightning%0.005",
				"M1%33.3 AND CR:electric%1 AND CR:lightning%0.005|XX%000000000000000000000000000000000000000000000|M1%33.3 AND CR:electric%1 AND CR:lightning%0.005",
				"M2%33.3 AND CR:electric%1 AND CR:lightning%0.005|M1%33.3 AND CR:electric%1 AND CR:lightning%0.005|M2%33.3 AND CR:electric%1 AND CR:lightning%0.005",
			],
			hardness: 0.8,
			reactions: {
				"corrosive_gas": { "elem2": "turquoise_dust", "elem1": "blue_dust", "chance": 0.5 },
				"blue_dust": { "elem1": null, "elem2": "turquoise_dust", "chance": 0.5 }
			},
			category: "gases",
			density: 1.225,
			state: "gas"
		};

		corrosiveGasMaxHardness = 0.6

		elements.corrosive_gas = {
			color: ["#2929e6", "#151cad"],
			behavior: [
				"M2%33.3|M1%33.3|M2%33.3",
				"M1%33.3|XX%0000|M1%33.3",
				"M2%33.3|M1%33.3|M2%33.3",
			],
			hardness: 0.8,
			tick: function(pixel) {
				//delete neighbors
				for(i = 0; i < adjacentCoords.length; i++) {
					nx = pixel.x + adjacentCoords[i][0];
					ny = pixel.y + adjacentCoords[i][1];
					if(!isEmpty(nx,ny,true)) {
						if((elements[pixelMap[nx][ny].element].hardness || 0) <= corrosiveGasMaxHardness) {
							if(Math.random() < 0.2) {
								if(Math.random() < 1 - ((pixel.hardness || 0))) {
									deletePixel(nx,ny);
								};
							};
						};
					};
				};
			},
			reactions: {
				"electric_gas": { "elem2": "blue_dust", "elem1": "turquoise_dust", "chance": 0.5 },
				"turquoise_dust": { "elem1": null, "elem2": "blue_dust", "chance": 0.5 }
			},
			category: "gases",
			density: 1.225,
			state: "gas",
		};

		elements.blue_dust = {
			color: ["#063ca1", "#042d94", "#063ca1", "#042d94", "#1d66ff"],
			behavior: behaviors.POWDER,
			hardness: 0.6,
			category: "powders",
			state: "solid",
			density: 1600,
		}

		elements.turquoise_dust = {
			color: ["#12a6a6","#1aa3a3","#12a6a6","#1aa3a3","#00ffff"],
			behavior: behaviors.POWDER,
			hardness: 0.6,
			category: "powders",
			state: "solid",
			density: 1600,
		}

		if(!settings) {
			settings = {}
		}

		if(!settings.bg) {
			settings.bg = "#000000"
		}

		elements.black_damp = {
			color: settings.bg,
			behavior: behaviors.GAS,
			reactions: {
				"fire": { elem2: null }
			},
			tick: function(pixel) {
				/*var baseColor = settings.bg instanceof Array ? averageRgbPrefixedColorArray(settings.bg.map(x => convertColorFormats(x,"rgb"))) : convertColorFormats(settings.bg,"rgb");
				baseColor = convertColorFormats(baseColor,"json");
				pixel.color = "rgba(" + Object.values(baseColor).join(",") + ",0)"*/
				pixel.color = "rgba(0,0,0,0)"
			},
			hardness: 0.6,
			category: "gases",
			density: 1.225,
			state: "gas",
		};

		if(!elements.torch.reactions) {
			elements.torch.reactions = {}
		}

		elements.torch.reactions.black_damp = { elem1: "wood" }

		elements.rock_dust = {
			color: "#878783",
			behavior: behaviors.GAS,
			reactions: {
				"water": {"elem1": "dirty_water", "elem2": null }
			},
			category: "gases",
			density: 2.45,
			state: "gas",
			tempHigh: 950,
			stateHigh: [null,null,null,null,"magma"],
		}

		elements.rock.breakInto.push("rock_dust")

		lightArray = ["fire", "plasma", "cold_fire", "light", "laser", "electric", "radiation", "mystic_fire", "liquid_fire", "liquid_plasma", "liquid_cold_fire", "le_liquid_light", "liquid_laser", "liquid_electric", "liquid_radiation", "liquid_mystic_fire", "magma", "liquid_light", "solid_light"]

		ledArray = ["led_r", "led_g", "led_b"]

		elements.iocalfaeus_gas = {
			color: ["#562173", "#481b61"],
			behavior: behaviors.GAS,
			tick: function(pixel) {
				if(!pixel.hot) {
					pixel.hot = false
				}
				for (let i = -2; i < 3; i++) {
					for (let j = -2; j < 3; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if ((lightArray.includes(pixelMap[pixel.x+j][pixel.y+i].element)) || (pixelMap[pixel.x+j][pixel.y+i].temp >= 525) || (ledArray.includes(pixelMap[pixel.x+j][pixel.y+i].element) && pixelMap[pixel.x+j][pixel.y+i].charge)) {
								pixel.hot = true
							}
						}
					}
				}
				if(pixel.hot == true) {
					pixel.temp += 16
				}
				if(pixel.hot == true && Math.random() < 0.02) {
					pixel.hot = false
				}
			},
			category: "gases",
			density: 0.97,
			state: "gas",
		}

		//Helium exists.

		finineRange = 6

		elements.finine = {
			color: ["#ffffec", "#fafade", "#ebebd5", "#c9c9b7", "#80806f"],
			behavior: [
				"M2%33.3|M1%33.3|M2%33.3",
				"M1%33.3|XX%0000|M1%33.3",
				"M2%33.3|M1%33.3|M2%33.3",
			],
			tick: function(pixel) {
				for(i = 0; i < adjacentCoords.length; i++) {
					nx = pixel.x + adjacentCoords[i][0];
					ny = pixel.y + adjacentCoords[i][1];
					if(!isEmpty(nx,ny,true)) {
						if (lifeArray.includes(pixelMap[nx][ny].element)) {
							pixel.eeex = pixel.x + Math.floor(Math.random() * ((2 * finineRange) + 1)) - finineRange
							pixel.eeey = pixel.y + Math.floor(Math.random() * ((2 * finineRange) + 1)) - finineRange
							//if human
							//handle heads
							if(pixelMap[nx][ny].element == "head") {
								if(isEmpty(pixel.eeex,pixel.eeey,false) && isEmpty(pixel.eeex,pixel.eeey+1,false)) {
									tryMove(pixelMap[nx][ny],pixel.eeex,pixel.eeey)
									tryMove(pixelMap[nx][ny+1],pixel.eeex,pixel.eeey+1)
								};
							} else if(pixelMap[nx][ny].element == "body") {
								if(isEmpty(pixel.eeex,pixel.eeey,false) && isEmpty(pixel.eeex,pixel.eeey-1,false)) {
									tryMove(pixelMap[nx][ny],pixel.eeex,pixel.eeey)
									tryMove(pixelMap[nx][ny-1],pixel.eeex,pixel.eeey-1)
								};
							} else {
								if(isEmpty(pixel.eeex,pixel.eeey,false)) {
									tryMove(pixelMap[nx][ny],pixel.eeex,pixel.eeey)
								};
							};
						};
					};
				};
			},
			category: "gases",
			density: 1.225,
			state: "gas",
		}

		//Smoke exists.

		elements.ignited_gas = {
			color: ["#fc9a58", "#faae3c", "#ffef3d"],
			behavior: [
				"M2|M1 AND CR:fire%0.5|M2",
				"M1 AND CR:fire%0.5|XX|M1 AND CR:fire%0.5",
				"M2|M1 AND CR:fire%0.5|M2",
			],
			category: "gases",
			state: "gas",
			density: 0.306,
			burning: true,
			burnTime: 30,
			temp: elements.fire.temp,
			burnInto: ["fire","smoke"],
			hidden: true,
		}

		//Diabaline

		elements.diabaline = {
			color: "#7e4e9c",
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(!pixel.glow) {
					pixel.glow = false
				}
				if(!pixel.oldColor) {
					pixel.oldColor = pixel.color
				}
				if(!pixel.rA) {
					pixel.rA = 0
				}
				if(!pixel.gA) {
					pixel.gA = 0
				}
				if(!pixel.bA) {
					pixel.bA = 0
				}
				if(!pixel.rB) {
					pixel.rB = 0
				}
				if(!pixel.gB) {
					pixel.gB = 0
				}
				if(!pixel.bB) {
					pixel.bB = 0
				}
				if(!pixel.finalR) {
					pixel.finalR = 0
				}
				if(!pixel.finalG) {
					pixel.finalG = 0
				}
				if(!pixel.finalB) {
					pixel.finalB = 0
				}
				if(!pixel.finalColor) {
					pixel.finalColor = ""
				}
				for (let i = -1; i < 2; i++) {
					for (let j = -1; j < 2; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (elements[pixelMap[pixel.x+j][pixel.y+i].element].category == "gases") {
								pixel.glow = true
							}
						}
					}
				}
				if(pixel.glow == true) {
					pixel.rA = pixel.oldColor.split(",")[0].slice(4)
					pixel.gA = pixel.oldColor.split(",")[1]
					pixel.bA = pixel.oldColor.split(",")[2].slice(0,-1)
					pixel.finalR = parseInt(pixel.rA) + 86
					pixel.finalG = parseInt(pixel.gA) + 93
					pixel.finalB = parseInt(pixel.bA) + 81
					pixel.finalColor = "rgb(" + pixel.finalR + "," + pixel.finalG + "," + pixel.finalB + ")"
					pixel.color = pixel.finalColor
				}
				if(pixel.glow == true && Math.random() < 0.02) {
					pixel.glow = false
				}
				if(pixel.glow == false) {
					pixel.color = pixel.oldColor
				}
			},
			category: "solids",
			density: 1500,
			state: "solid",
		}


		runAfterLoad(function() {
			lifeArray = Object.keys(elements).filter(function(e) {
				return elements[e].category == "life";
			});
			if(!elements.void_gas.reactions) {
				elements.void_gas.reactions = {}
			};
			for(i = 0; i < lifeArray.length; i++) {
				elements.void_gas.reactions[lifeArray[i]] = { "elem2": null, "chance": 0.7 }
			};
			for(i = 0; i < lifeArray.length; i++) {
				elements.black_damp.reactions[lifeArray[i]] = { "elem2": null, "chance": 0.7 }
			};
			elements.acidic_vapour.ignore = elements.acid.ignore
			/*
				elements.radioactive_rock_dust = {
					color: "#839e78",
					behavior: behaviors.RAD_GAS,
					reactions: {
						"water": {"elem1": "radioactive_water", "elem2": null }
					},
					category: "gases",
					density: 2.45,
					state: "gas",
					tempHigh: 950,
					stateHigh: [null,null,null,null,"radioactive_magma"],
				}

				elements.radioactive_rock.breakInto.push("radioactive_rock_dust")
			*/
			elements.rock_dust.tempHigh = 3000
			elements.rock_dust.stateHigh = "vaporized_rock"
			/*
				elements.radioactive_rock_dust.tempHigh = 3000
				elements.radioactive_rock_dust.stateHigh = "vaporized_rock"
			*/
		});

	//IOCALFAEUS CLONES

		elements.iorefrius_gas = {
			color: ["#217349", "#1b5f3c"],
			behavior: behaviors.GAS,
			tick: function(pixel) {
				if(!pixel.cold) {
					pixel.cold = false
				}
				for (let i = -2; i < 3; i++) {
					for (let j = -2; j < 3; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i,true)) {
							var newPixel = pixelMap[pixel.x+j][pixel.y+i];
							if ((lightArray.includes(newPixel.element)) || (newPixel.temp >= 525) || (ledArray.includes(newPixel.element) && newPixel.charge) || (newPixel.cold && Math.random() < 0.04)) {
								pixel.cold = true;
							};
						};
					};
				};
				if(pixel.cold == true) {
					pixel.temp -= 16;
				};
				if(pixel.cold == true && Math.random() < 0.02) {
					pixel.cold = false;
				};
			},
			category: "gases",
			density: 0.97,
			state: "gas",
		};

		elements.iolucius_gas = {
			color: ["#e9c5ed", "#e2b0e8"],
			behavior: behaviors.GAS,
			tick: function(pixel) {
				if(!pixel.lit) {
					pixel.lit = false
				}
				for (let i = -2; i < 3; i++) {
					for (let j = -2; j < 3; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i,true)) {
							var newPixel = pixelMap[pixel.x+j][pixel.y+i];
							if ((lightArray.includes(newPixel.element)) || (newPixel.temp >= 525) || (ledArray.includes(newPixel.element) && newPixel.charge) || (newPixel.lit && Math.random() < 0.04)) {
								pixel.lit = true;
							};
						};
					};
				};
				if(Math.random() < 0.05) {
					if(pixel.lit == true) {
						var randomLightOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
						var lightX = pixel.x + randomLightOffset[0];
						var lightY = pixel.y + randomLightOffset[1];
						if(isEmpty(lightX,lightY,false)) {
							createPixel("light",lightX,lightY);
						};
					};
				};
				if(pixel.lit == true && Math.random() < 0.02) {
					pixel.lit = false;
				};
			},
			category: "gases",
			density: 0.97,
			state: "gas",
		};

		elements.ioradius_gas = {
			color: ["#a6a258", "#97944e"],
			behavior: behaviors.GAS,
			tick: function(pixel) {
				if(!pixel.rlit) {
					pixel.rlit = false
				}
				for (let i = -2; i < 3; i++) {
					for (let j = -2; j < 3; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i,true)) {
							var newPixel = pixelMap[pixel.x+j][pixel.y+i];
							if ((lightArray.includes(newPixel.element)) || newPixel.element === "radiation" || (newPixel.temp >= 525) || (ledArray.includes(newPixel.element) && newPixel.charge) || (newPixel.rlit && Math.random() < 0.04)) {
								pixel.rlit = true;
							};
						};
					};
				};
				if(Math.random() < 0.05) {
					if(pixel.rlit == true) {
						var randomRadiationOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
						var radiationX = pixel.x + randomRadiationOffset[0];
						var radiationY = pixel.y + randomRadiationOffset[1];
						if(isEmpty(radiationX,radiationY,false)) {
							createPixel("radiation",radiationX,radiationY);
						};
					};
				};
				if(pixel.rlit == true && Math.random() < 0.02) {
					pixel.rlit = false;
				};
			},
			category: "gases",
			density: 0.97,
			state: "gas",
		};

	//SOME METALS AND OTHER ASSORTED SUBSTANCES ##

		elements.iron.hardness = 0.74
		//https://www.engineeringtoolbox.com/bhn-brinell-hardness-number-d_1365.html
		//https://en.wikipedia.org/wiki/Hardnesses_of_the_elements_(data_page)
		//"Annealed chissel steel" hardness and then divided by iron hardness (Brinell)
		//sqrt()ed like IACS-derived conductivities and scaled to the 0.8 hardness of steel
		//and because 1 means infinite hardness, the others are derived using
		//1-(0.26/(otherThingBHN/200))
		//it doesn't matter much anyway but I'd like to have some semblance/veneer of accuracy
		//Then I nerfed and buffed some of them with inconsistent rounding.

		elements.chromium = {
			color: ["#c8cccb", "#dce3e0", "#ebedeb"],
			behavior: behaviors.WALL,
			reactions: {

			},
			tempHigh: 1907,
			category: "solids",
			density: 7190,
			conduct: 0.35,
			hardness: 0.985,
			state: "solid",
		};

		//Makes thinner nichrome wires get hotter
		nichromeDoNeighborCount = true;

		function nichromeNeighborLogic(count) {
			if(count < 3) { return 2.5 };
			return count == 3 ? 1.25 : 0;
		};

		elements.nichrome = {
			color: ["#d1cfcb", "#dbd7ce", "#e8e2d5"],
			behavior: behaviors.WALL,
			tempHigh: 1400,
			category: "solids",
			density: 8300,
			conduct: 0.75,
			hardness: 0.7, //???
			state: "solid",
			tick: function(pixel) {
				if(nichromeDoNeighborCount) {
					var neighbors = 0;
					for(i = 0; i < adjacentCoords.length; i++) {
						if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
							var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]];
							if(elements[newPixel.element].conduct) { neighbors++ };
						};
					};
				};
				if(pixel.charge) {
					pixel.temp += ((1.1 + nichromeNeighborLogic(neighbors)) * pixel.charge);
				};
			},
		};

		elements.molten_chromium = {
			density: 6300,
			temp: 2000,
			reactions: { //(test.hello ??= {}).world
				molten_nichrome: { elem1: "molten_nichrome", elem2: "molten_chromium", chance: 0.4, changeTemp: false, oneway: true }
			},
		};

		elements.molten_nichrome = {
			reactions: { //(test.hello ??= {}).world
				molten_nickel: { elem1: "molten_nickel", elem2: "molten_nichrome", chance: 0.4, changeTemp: false, oneway: true },
				molten_haseulite: { elem2: "molten_hanichrite", elem1: ["molten_nichrome","molten_nichrome","molten_nichrome","molten_nichrome","molten_nichrome","molten_nichrome","molten_nichrome","molten_nichrome","molten_nichrome","molten_hanichrite"], changeTemp: false }
			},
			tick: function(pixel) {
				if(nichromeDoNeighborCount) {
					var neighbors = 0;
					for(i = 0; i < adjacentCoords.length; i++) {
						if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
							var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]];
							if(elements[newPixel.element].conduct) { neighbors++ };
						};
					};
				};
				if(pixel.charge) {
					pixel.temp += ((1.1 + nichromeNeighborLogic(neighbors)) * pixel.charge) * 1.1;
				};
			},
		};

		elements.gold.reactions ??= {};

		elements.molten_gold.reactions.molten_nickel = {
			elem1: "molten_white_gold",
			elem2: new Array(9).fill("molten_nickel").concat("molten_white_gold"),
			changeTemp: false
		};

		elements.molten_copper.reactions.molten_gold = {
			elem1: ["molten_copper","molten_copper","molten_rose_gold"],
			elem2: "molten_rose_gold",
			changeTemp: false
		};

		elements.white_gold = {
			color: ["#c2c2c2","#9e9e9e","#e8e8e8"],
			behavior: behaviors.WALL,
			tempHigh: 937,
			category: "solids",
			density: 15900,
			conduct: 0.83, //Has never been measured x>:(
			hardness: 0.48,
		};
		
		elements.rose_gold.color = ["#f58eb1","#d06c7d","#f58eb1"];
		
		elements.molten_copper.reactions.molten_rose_gold = {
			elem1: ["molten_copper","molten_red_gold"],
			elem2: "molten_red_gold",
			changeTemp: false
		};

		elements.red_gold = {
			color: ["#d97b6a","#c95c49","#d97b6a"],
			behavior: behaviors.WALL,
			tempHigh: 975, //https://www.researchgate.net/figure/Gold-copper-phase-diagram-with-melting-points-of-gold-and-copper-adapted-from-AMS_fig51_233765846
			category: "solids",
			density: 12220, //https://www.handymath.com/cgi-bin/density.cgi?naym1=Gold&weight1=10&den1=19.3&naym2=Copper&weight2=10&den2=8.94&aloynaym=Red+Gold&submit=Calculate&numnum=2&moreless=1&decimal=5
			conduct: 0.85, //Has never been measured x>:(
			hardness: 0.5, //???
		};

		worldgentypes.test = {
			layers: [[0.3, "pointer"], [0, "molten_nickel"]],
			temperature: 2000
		};

		runAfterAutogen(function() {
			if(!elements.molten_nickel.reactions) {
				elements.molten_nickel.reactions = {};
			};

			elements.molten_nickel.reactions.molten_chromium = { elem1: "molten_nichrome", elem2: ["molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_nichrome"], changeTemp: false };
			});

		//Copper exists

		elements.ruthenium = {
			color: ["#e8ebca","#eaebd5"], //color pulled from my ass because I don't want another gray metal
			behavior: behaviors.WALL,
			tempHigh: 2334,
			category: "solids",
			state: "solid",
			density: 12450,
			conduct: 0.45,
			hardness: 0.97,
		},

		elements.molten_ruthenium = {
			density: 10650,
		},

		elements.rhodium = {
			color: ["#f0e4df","#f7eae4"], //it looked slightly reddish on Wikipedia
			behavior: behaviors.WALL,
			tempHigh: 1964,
			category: "solids",
			state: "solid",
			density: 12410,
			conduct: 0.59,
			hardness: 0.95,
		},

		elements.molten_rhodium = {
			density: 10700,
		},

		elements.palladium = {
			color: ["#fff8ed","#f5e6ce","#faeccf"], //Terraria reference
			behavior: behaviors.WALL,
			tempHigh: 1555,
			category: "solids",
			state: "solid",
			density: 12023,
			conduct: 0.38,
			hardness: 0.83,
		},

		elements.molten_palladium = {
			density: 10380,
		},

		//Silver exists

		elements.rhenium = {
			color: ["#e5f0d1","#e6edda"], //it looks like almost every other metal but in some pictures the lighting makes it look ever-so-slightly greenish
			behavior: behaviors.WALL,
			tempHigh: 3186,
			category: "solids",
			state: "solid",
			density: 21020,
			conduct: 0.29,
			hardness: 0.96,
		},

		elements.molten_rhenium = {
			density: 18900,
		},

		elements.osmium = {
			color: ["#d8e1eb","#cee1f0"], //it looks bluish
			behavior: behaviors.WALL,
			tempHigh: 3033,
			category: "solids",
			state: "solid",
			density: 22590,
			conduct: 0.40,
			hardness: 0.98,
		},

		elements.molten_osmium = {
			density: 2e4,
		},

		elements.iridium = {
			color: ["#dfb9f0","#d6a9eb","#dfd1ed","#eeeeee"], //Minecraft and Stardew Valley reference
			behavior: behaviors.WALL,
			tempHigh: 2446,
			category: "solids",
			state: "solid",
			density: 22560,
			conduct: 0.54,
			hardness: 0.97,
		},

		elements.molten_iridium = {
			density: 19000,
		},

		elements.platinum = {
			color: ["#dddddd","#d7d7d7"],
			behavior: behaviors.WALL,
			tempHigh: 1768,
			category: "solids",
			state: "solid",
			density: 21450,
			conduct: 0.38,
			hardness: 0.83226,
		},

		elements.molten_platinum = {
			density: 19770,
		},

		//Gold exists

		elements.mercury = {
			color: ["#d1d1d1", "#bababa"],
			behavior: behaviors.LIQUID,
			tempHigh: 357,
			stateHigh: "mercury_gas",
			tempLow: -39,
			stateLow: "frozen_mercury",
			state: "solid",
			category: "liquids",
			density: 13534,
			conduct: 0.13,
			breakInto: "mercury_gas",
		},

		elements.frozen_mercury = {
			color: ["#d1d1d1", "#bababa"],
			density: 14184,
			behavior: behaviors.WALL,
			conduct: 0.13,
			tempHigh: -39,
			temp: -50,
			stateHigh: "mercury",
			category: "solids",
			state: "solid",
			state: "solid",
			hidden: true,
			hardness: 0.2775, //(desperately scaled Mohs hardness)
		},

		elements.mercury_gas = { //hg d@bp extrapolated from density change with temperature: 12743
			density: 8.477,
			color: ["#d1d1d1", "#bababa"],
			colorOn: ["#96ffbf", "#9cffc2", "#9effe7"],
			conduct: 0.13,
			behavior: behaviors.GAS,
			tempLow: 357,
			temp: 400,
			stateLow: "mercury",
			category: "gases",
			state: "gas",
			hidden: true,
		}

		var mooreNeighborhood = [[-1,-1],[0,-1],[1,1],[-1,0],[1,0],[-1,1],[0,1],[1,1]];

		var bismuthCrystalColorArray = [
			"#f58887",
			"#fcd19a",
			"#fcf588",
			"#aef29d",
			"#9af5e4",
			"#b3bef5",
			"#dbb9f0",
			"#f2acdb"
		]

		var bismuthCrystalElements = ["bismuth","molten_bismuth"];

		quadriCoords = [[-1,1],[0,1],[1,1],[1,0]];

		//i'm not replacing pixelTick for this shit
		/*function mooreDoHeat(pixel) {
			// Check right and bottom adjacent pixels
			for (var i = 0; i < quadriCoords.length; i++) {
				var x = pixel.x+quadriCoords[i][0];
				var y = pixel.y+quadriCoords[i][1];
				if (!isEmpty(x,y,true)) {
					var newPixel = pixelMap[x][y];
					// Skip if both temperatures are the same
					if (pixel.temp == newPixel.temp || elements[newPixel.element].insulate == true) {
						continue;
					}
					// Set both pixel temperatures to their average
					var avg = (pixel.temp + newPixel.temp)/2;
					pixel.temp = avg;
					newPixel.temp = avg;
					pixelTempCheck(pixel);
					pixelTempCheck(newPixel);
				};
			};
		};*/

		function bismuthCrystallization(pixel) {
			if(pixel.temp < elements.bismuth.tempHigh) { //initial crystal on cool
				//pixel.color = "rgb(255,0,0)";
					//initialize CCC
				pixel.crystalColorCounter ??= Math.floor(Math.random() * 8); //initialize CCC
				//pixel.crystalColorCounter ??= 0; 
				if(pixel.element !== "bismuth") {
					pixel.temp -= 0.05; //incentivize cooling
					pixel.element = "bismuth"
					//console.log(`pixel (${pixel.x},${pixel.y}) frozen by bismuthCrystallization`)
					pixel.color = pixelColorPick(pixel,bismuthCrystalColorArray[pixel.crystalColorCounter % 8]);
				}; //solidify
			};

			if(pixel.crystalColorCounter !== undefined) {
				for(i = 0; i < mooreNeighborhood.length; i++) {
					var newX = pixel.x + mooreNeighborhood[i][0];
					var newY = pixel.y + mooreNeighborhood[i][1];

					if(isEmpty(newX,newY,true)) {
						continue;
					} else {
						var newPixel = pixelMap[newX][newY];
						if(bismuthCrystalElements.includes(newPixel.element)) {
							if(newPixel.temp < elements.bismuth.tempHigh) {
								newPixel.temp -= 0.05;
								newPixel.element = "bismuth";
								newPixel.crystalColorCounter = (pixel.crystalColorCounter + 1) % 8;
								newPixel.color = pixelColorPick(pixel,bismuthCrystalColorArray[pixel.crystalColorCounter % 8]);
							};
						};
					};
				};
			};

			//mooreDoHeat(pixel);
		};

		elements.molten_bismuth = {
			color: "#d1c6b0", //not really hot enough to be red
			behavior: behaviors.LIQUID,
			tempLow: -Infinity, //suppress normal freezing mechanism
			stateLow: "molten_bismuth",
			tick: function(pixel) {
				bismuthCrystallization(pixel);
			},
			density: 10050,
			state: "liquid",
			category: "liquids",
			temp: 300,
			tempHigh: 1560,
			fireColor: "#4275db",
		};

		runAfterAutogen(function() {
			delete elements.molten_bismuth.tempLow;
			delete elements.molten_bismuth.stateLow;
		});

		elements.bismuth = {
			color: "#d1c6b0",
			behavior: behaviors.WALL,
			/*reactions: {

			},*/
			tempHigh: 271.5,
			category: "solids",
			density: 9780,
			conduct: 0.12,
			hardness: 0.22,
			state: "solid",
			fireColor: "#4275db",
		};

		elements.bismuth_gas = {
			density: 9, //made-up number
			fireColor: "#4275db",
		};

		zirconoids = ["zirconium","molten_zirconium","zirconium_gas"];

		function zirconiumMoveContainedNeutron(pixelFrom,pixelTo) {
			if(!pixelFrom || !pixelTo) {
				return false
			};
			pixelFrom.neutrons ??= 0;
			if(pixelFrom.neutrons < 1) {
				return false;
			};
			pixelTo.neutrons ??= 0;
			pixelFrom.neutrons--;
			pixelTo.neutrons++;
		};

		function neutronAbsorbency(pixel,otherPixel) {
			if(isNaN(pixel.neutrons)) {
				pixel.neutrons = 0;
			};
			pixel.neutrons ??= 0; //probably redundant with the above
			if(!otherPixel) {
				return null;
			};
			if(otherPixel.element === "neutron") {
				/*var otherIndex = currentPixels.indexOf(otherPixel);
				if(otherIndex !== -1) { currentPixels.splice(otherIndex,1) };
				pixelMap[otherPixel.x][otherPixel.y] = undefined;*/
				deletePixel(otherPixel.x,otherPixel.y);
				pixel.neutrons++;
				return true;
			} else {
				return false;
			};
		};

		function neutronMovement(pixel,whitelist=null) {
			if(!pixel.oldColor) {
				pixel.oldColor = pixel.color;
			};
			if(isNaN(pixel.neutrons)) {
				pixel.neutrons = 0;
			};
			pixel.neutrons ??= 0; //probably redundant with the above

			if(pixel.oldColor === null) { pixel.oldColor = pixel.color };

			var color = convertColorFormats(pixel.oldColor,"json");
			//color.g += (pixel.neutrons * 4);
			//color.b += (pixel.neutrons * 6);
			color.g += (pixel.neutrons * 32);
			color.b += (pixel.neutrons * 48);
			color = convertColorFormats(color,"rgb");
			pixel.color = color;

			for(i = 0; i < pixel.neutrons; i++) {
				if(pixel.neutrons < 1) { break };
				var vx = Math.floor(Math.random() * 3) - 1;
				var vy = Math.floor(Math.random() * 3) - 1;
				if (vx===0 && vy===0) {
					if (Math.random() < 0.5)	{ vx = Math.random() < 0.5 ? 1 : -1; }
					else						{ vy = Math.random() < 0.5 ? 1 : -1; }
				};

				var newPos = {x: pixel.x+vx, y: pixel.y+vy};

				if(outOfBounds(newPos.x,newPos.y)) {
					continue;
				};

				if(isEmpty(newPos.x,newPos.y,false)) {
					createPixelReturn("neutron",newPos.x,newPos.y).temp = pixel.temp;
					pixel.neutrons--;
					if(pixel.neutrons < 1) { break };
				} else if(!isEmpty(newPos.x,newPos.y,true)) {
					var newPixel = pixelMap[newPos.x][newPos.y];
					if(whitelist == null || whitelist.includes(newPixel.element)) {
						zirconiumMoveContainedNeutron(pixel,newPixel);
						if(pixel.neutrons < 1) { break };
					};
				};
			};
		};

		elements.zirconium = {
			color: ["#ccc59b", "#dbd3a4"],
			behavior: behaviors.WALL,
			properties: {
				oldColor: null
			},
			onTryMoveInto: function(pixel,otherPixel) {
				neutronAbsorbency(pixel,otherPixel);
			},
			tick: function(pixel) {
				neutronMovement(pixel,zirconoids);
			},
			tempHigh: 1855,
			category: "solids",
			density: 6520,
			conduct: 0.19,
			hardness: 0.5,
		},

		elements.molten_zirconium = {
			density: 5803,
			tempHigh: 4409,
			behavior: behaviors.MOLTEN,
			onTryMoveInto: function(pixel,otherPixel) {
				neutronAbsorbency(pixel,otherPixel);
			},
			tick: function(pixel) {
				neutronMovement(pixel,zirconoids);
			},
		};


		elements.zirconium_gas = {
			density: 3, //Unknown/Unmeasured value
			behavior: behaviors.GAS,
			onTryMoveInto: function(pixel,otherPixel) {
				neutronAbsorbency(pixel,otherPixel);
			},
			tick: function(pixel) {
				neutronMovement(pixel,zirconoids);
			},
		};

		elements.neutron.state = "gas";
		elements.neutron.ignoreAir = "true";

		neighbors = [[-1,0],[0,-1],[1,0],[0,1]]

		function tryTarnish(pixel,element,chance) {
			if(exposedToAir(pixel)) {
				if(Array.isArray(element)) {
					if(Math.random() < chance) {
						changePixel(pixel,randomChoice(element))
					}
				} else {
					if(Math.random() < chance) {
						changePixel(pixel,element)
					}
				}
			}
		}

		//Non-element: Liquid ammonia
		elements.liquid_ammonia = {
			color: "#bab6a9",
			behavior: behaviors.LIQUID,
			reactions: {
				"methane": { "elem1":null, "elem2":"cyanide", "chance":0.25 },
				"plant": { "elem1":"plant", "chance":0.05 },
				"wheat_seed": { "elem1":"wheat", "chance":0.05 },
				"grass": { "elem1":"grass", "chance":0.05 },
				"grass_seed": { "elem1":"grass", "chance":0.05 },
				"bamboo_plant": { "elem1":"bamboo", "chance":0.05 },
				"flower_seed": { "elem1":"flower_seed", "chance":0.05 },
				"petal": { "elem1":"flower_seed", "chance":0.05 },
				"vine": { "elem1":"vine", "chance":0.05 },
				"sapling": { "elem1":"tree_branch", "chance":0.05 },
				"tree_branch": { "elem1":"tree_branch", "chance":0.05 },
				"corn_seed": { "elem1":"corn", "chance":0.05 },
				"root": { "elem1":"root", "chance":0.05 },
				"dirt": { "elem1":"grass", "chance":0.05 },
				"mud": { "elem1":"grass", "chance":0.05 },
				"potato_seed": { "elem1":"potato", "chance":0.05 },
				"yeast": { "elem1":"yeast", "chance":0.05 },
				"fish": { "elem2":"meat" },
				"frog": { "elem2":"meat" }
			},
			tempHigh: -78,
			stateHigh: "ammonia",
			category: "liquids",
			state: "liquid",
			hidden: true,
			density: 681.9,
		}

		elements.ammonia.tempLow = -78
		elements.ammonia.stateLow = "liquid_ammonia"

		//Hydrogen
		//Hydrogen exists, but its solid form doesn't.
		elements.liquid_hydrogen.tempLow = -259.16
		elements.liquid_hydrogen.stateLow = "hydrogen_ice"

		elements.hydrogen_ice = {
			color: "#E6E6FF",
			behavior: behaviors.WALL,
			density: 76,
			category: "solids",
			state: "solid",
			hidden: true,
			tempHigh: -259,
			stateHigh: "liquid_hydrogen",
		}

		//Lithium (incomplete/hiatus)
		elements.lithium = {
			color: "#b0ab9d",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				tryTarnish(pixel,"lithium_oxide",0.007) 
				if(pixel.temp >= 179) {
					pixel.burning = true; 
					pixel.burnStart = pixelTicks; 
				}
			},
			reactions: {
				"steam": { "elem1": "hydrogen", "elem2": "lithium_hydroxide" }, 
				"water": { "elem1": "hydrogen", "elem2": "lithium_hydroxide" }, 
				"nitrogen": { "elem1": "lithium_nitride", "elem2": "lithium_nitride" }, 
				"liquid_nitrogen": { "elem1": "lithium_nitride", "elem2": "lithium_nitride" }, 
				"liquid_hydrogen": { "elem1": "lithium_hydride", "elem2": "lithium_hydride" }, 
				"ammonia": { "elem1": ["hydrogen",null], "elem2": "lithium_amide" }, 
				"liquid_ammonia": { "elem1": ["hydrogen",null], "elem2": "lithium_amide" }, 
			},
			density: 534,
			category: "solids",
			state: "solid",
			conduct: 0.42697,
			hardness: 0.019,
			tempHigh: 180,
			burn: 20,
			burnTime: 130,
			burnInto: "lithium_oxide",
			fireColor: "#fc0a22",
		}

		elements.molten_lithium = { //too damn reactive
			color: "#b0ab9d",
			behavior: [
			"XX|HT:1%1|XX",
			"M2 AND HT:0.1%1|HT:1%1|M2 AND HT:1%1",
			"M1|M1 AND HT:1%1|M1"
			],
			tick: function(pixel) {
				tryTarnish(pixel,"lithium_oxide",0.014) 
			},
			reactions: {
				"steam": { "elem1": "hydrogen", "elem2": "lithium_hydroxide" }, 
				"water": { "elem1": "hydrogen", "elem2": "lithium_hydroxide" }, 
				"nitrogen": { "elem1": "lithium_nitride", "elem2": "lithium_nitride" }, 
				"liquid_nitrogen": { "elem1": "lithium_nitride", "elem2": "lithium_nitride" }, 
				"hydrogen": { "elem1": "lithium_hydride", "elem2": "lithium_hydride" }, 
				"liquid_hydrogen": { "elem1": "lithium_hydride", "elem2": "lithium_hydride" }, 
				"ammonia": { "elem1": ["hydrogen",null], "elem2": "lithium_amide" }, 
				"liquid_ammonia": { "elem1": ["hydrogen",null], "elem2": "lithium_amide" }, 
			},
			burning: true,
			burnInto: "lithium_oxide",
			fireColor: "#fc0a22",
			density: 512,
		}

		elements.lithium_oxide = {
			color: "#eee9ec", //HRT UV-to-visible strategy again
			behavior: behaviors.POWDER,
			reactions: {
				"steam": { "elem1": "lithium_hydroxide", "elem2": "lithium_hydroxide", chance: 0.03 }, 
				"water": { "elem1": "lithium_hydroxide", "elem2": "lithium_hydroxide", chance: 0.03 }, 
				"carbon_dioxide": { "elem1": null, "elem2": "lithium_carbonate" }
			},
			density: 2013,
			category: "powders",
			state: "solid",
			hidden: true,
			tempHigh: 1438,
		}

		elements.lithium_hydroxide = {
			color: "#eeeeee",
			behavior: behaviors.POWDER,
			reactions: {
				"steam": { "elem1": null, "elem2": "lithium_hydroxide_monohydrate" },
				"water": { "elem1": null, "elem2": "lithium_hydroxide_monohydrate" },
				"carbon_dioxide": { "elem1": "water", "elem2": [null,"lithium_carbonate"], chance: 0.5 }, 
			},
			density: 1460,
			category: "powders",
			state: "solid",
			hidden: true,
			tempHigh: 462,
		}

		elements.lithium_hydroxide_monohydrate = {
			color: "#e0e4e7",
			behavior: behaviors.POWDER,
			reactions: {
				"carbon_dioxide": { "elem1": "water", "elem2": [null,"lithium_carbonate"], chance: 0.5 }, 
			},
			tick: function(pixel) {
				emptyNeighborArray = [] 
				for(i=0;i<4;i++) {
					if(isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],false)) {
						emptyNeighborArray.push(adjacentCoords[i])
					}
				}
				if(pixel.temp >= 100) {
					if(emptyNeighborArray.length > 0) {
						var placement = randomChoice(emptyNeighborArray)
						if(isEmpty(pixel.x+placement[0],pixel.y+placement[1])) {
							createPixel("steam",pixel.x+placement[0],pixel.y+placement[1])
							changePixel(pixel,"lithium_hydroxide")
						}
					}
				}
			},
			density: 1510,
			category: "powders",
			state: "solid",
			hidden: true,
		}

		elements.lithium_carbonate = { //todo
			color: "#eeeeee",
			behavior: behaviors.POWDER,
			density: 2110,
			category: "powders",
			state: "solid",
			hidden: true,
			tempHigh: 723,
		}

		elements.lithium_nitride = {
			color: "#eeeeee",
			behavior: behaviors.POWDER,
			reactions: {
				"steam": { "elem1": "lithium_hydroxide", "elem2": "ammonia" }, 
				"water": { "elem1": "lithium_hydroxide", "elem2": "ammonia" }, 
				"hydrogen": { "elem1": "lithium_hydride", "elem2": "lithium_amide" }, 
				"liquid_hydrogen": { "elem1": "lithium_hydride", "elem2": "lithium_amide" }, 
			},
			density: 1270,
			category: "powders",
			state: "solid",
			hidden: true,
			tempHigh: 813,
		}

		elements.lithium_hydride = {
			color: "#eeeeee",
			behavior: behaviors.POWDER,
			reactions: { //the acid part of lye
				"ammonia": { "elem1": "hydrogen", "elem2": "lithium_amide" },
				"liquid_ammonia": { "elem1": "hydrogen", "elem2": "lithium_amide" }
			},
			density: 780,
			category: "powders",
			state: "solid",
			hidden: true,
			tempHigh: 689,
		}

		elements.lithium_amide = {
			color: "#eeeeee",
			behavior: behaviors.POWDER,
			reactions: {
				"steam": { "elem1": "lithium_hydroxide", "elem2": "ammonia" },
				"water": { "elem1": "lithium_hydroxide", "elem2": "ammonia" }
			},
			density: 1178,
			category: "powders",
			state: "solid",
			hidden: true,
			tempHigh: 375,
		}

		//Sodium exists

		//...

		//at request of Serioustar#1337

		elements.niobium = {
			color: ["#dedede","#edead8","#e8e9ed"],
			behavior: behaviors.WALL,
			tempHigh: 2477,
			category: "solids",
			density: 8570,
			conduct: 0.35,
			hardness: 0.7, //idk lol
		};

		//Random bullshit go

		elements.kurshunjukium = {
			color: ["#435564","#536371"],
			behavior: behaviors.WALL,
			tempHigh: 308,
			category: "solids",
			state: "solid",
			density: 10304,
			conduct: 0.53,
			hardness: 0.12,
			desc: '<em>Fictional</em><br/>Kurshunjukium, whose name comes from the Turkish "kurşuncuk", meaning "little lead", is a blue-gray metal that is soft and dense, similarly to its namesake.'
		};

		elements.molten_kurshunjukium = {
			density: 7989,
			tempHigh: 1411,
			desc: "<em>Fictional</em>"
		};

		elements.kurshunjukium_gas = {
			density: 18
		};

		elements.kurshuth_alloy = {
			color: "#7d806f",
			behavior: behaviors.LIQUID,
			tempHigh: 863,
			stateHigh: "kurshuth_alloy_vapor",
			tempLow: 14,
			stateLow: "solid_kurshuth_alloy",
			category: "liquids",
			state: "liquid",
			density: 5606,
			conduct: 0.13,
			hardness: 0.02,
			desc: '<em>Fictional</em><br/>Kurshuth alloy is a highly eutectic alloy composed of bismuth and kurshunjukium. It has an extremely low melting point, and isn\'t very useful.'
		};

		elements.kurshuth_alloy_vapor = {
			density: 23,
			behavior: behaviors.GAS,
			category: "states",
			state: "gas",
			tempLow: 863,
			stateLow: "kurshuth_alloy",
			hidden: true,
			color: elements.magma.color.map(x => changeSaturation(changeHue(multiplyColors(x,"#DD4444","json"),10,"add","hsl_json"),0.7,"multiply","hex")), //metals.js already uses code_library
			desc: "<em>Fictional</em>"
		};

		elements.solid_kurshuth_alloy = {
			color: "#5d604f",
			behavior: behaviors.WALL,
			tempHigh: 14,
			stateHigh: "kurshuth_alloy",
			category: "solids",
			state: "solid",
			density: 6006,
			desc: "<em>Fictional</em>"
		};

		runAfterAutogen(function() {
			var kesddfroged = ["kurshunjukium","molten_kurshunjukium"];
			var kesddfroged2 = ["bismuth","molten_bismuth"];
			for(var i in kesddfroged) {
				for(var j in kesddfroged2) {
					elements[kesddfroged[i]].reactions ??= {}; elements[kesddfroged[i]].reactions[kesddfroged2[j]] = {"elem1": "kurshuth_alloy", "elem2": "kurshuth_alloy"}
				}
			};
			for(var i in kesddfroged2) {
				for(var j in kesddfroged) {
					elements[kesddfroged2[i]].reactions ??= {}; elements[kesddfroged2[i]].reactions[kesddfroged[j]] = {"elem1": "kurshuth_alloy", "elem2": "kurshuth_alloy"}
				}
			};
		});

		function newMetal(name,color,meltingPoint,boilingPoint,hardness,density,gasDensity,conduct,categoryOverride = null) {
			var temp = {
				"name": name,
				"color": color,
				"meltingPoint": meltingPoint,
				"boilingPoint": boilingPoint,
				"hardness": hardness,
				"density": density,
				"gasDensity": gasDensity,
				"conduct": conduct
			};
			var tempNulls = Object.keys(temp).filter(key => (typeof(temp[key]) == undefined || temp[key] == null));
			if(tempNulls.length > 0) {
				var errorMessage = capitalizeFirstLetter(englishFormatList(tempNulls));
				throw new Error(`newMetal: ${errorMessage} {tempNulls.length == 1 ? "is" : "are"} required (generating "${name}")`);
			};
			
			var scrapColor = gravelizeToHex(color);

			elements[name] = {
				"color": (window["gameLoaded"] ?? false) ? (Array.isArray(color) ? color.map(x => convertColorFormats(x,"rgb")) : convertColorFormats(color,"rgb")) : color,
				"colorObject": (window["gameLoaded"] ?? false) ? (Array.isArray(color) ? color.map(x => convertColorFormats(x,"json")) : convertColorFormats(color,"json")) : undefined,
				"behavior": behaviors.WALL,
				"category": "solids",
				"state": "solid",
				"density": density,
				"conduct": conduct,
				"tempHigh": meltingPoint,
				"breakInto": `${name}_scrap`,
				"hardness": hardness
			};
			
			if(categoryOverride) { elements[name].category = categoryOverride };

			elements[`molten_${name}`] = {
				"tempHigh": boilingPoint
			};

			elements[`${name}_gas`] = {
				"density": gasDensity
			};

			elements[`${name}_scrap`] = {
				"color": (window["gameLoaded"] ?? false) ? (Array.isArray(scrapColor) ? scrapColor.map(x => convertColorFormats(x,"rgb")) : convertColorFormats(scrapColor,"rgb")) : scrapColor,
				"colorObject": (window["gameLoaded"] ?? false) ? (Array.isArray(scrapColor) ? scrapColor.map(x => convertColorFormats(x,"json")) : convertColorFormats(scrapColor,"json")) : undefined,
				"behavior": (window["gameLoaded"] ?? false) ? undefined : behaviors.POWDER,
				"tick": (window["gameLoaded"] ?? false) ? behaviors.POWDER : undefined,
				"tempHigh": meltingPoint,
				"stateHigh": `molten_${name}`,
				"category": "powders",
				"hidden": true,
				"density": density * 0.09,
				"conduct": conduct * 0.4,
				"movable": true,
			};
			
			if(window["gameLoaded"] ?? false) {
				delete elements[`${name}_scrap`].behavior;
				createElementButton(name);
				if(settings.unhide == 1) { createElementButton(`${name}_scrap`) }
				autoGen(`molten_${name}`,name,"molten");
				elements[`molten_${name}`].color = elements[`molten_${name}`].color.map(x => convertColorFormats(x,"rgb"));
				elements[`molten_${name}`].colorObject = elements[`molten_${name}`].color.map(x => convertColorFormats(x,"json"));
			} else {
				delete elements[`${name}_scrap`].tick;
			};

			return [elements[name],elements[`${name}_scrap`]];
		};
		
		//newMetal( "exidmaden", ["#F8EDCF", "#EEAAAE", "#E5678D", "#A6659C", "#6763AD"], 2134, 6769, 0.8, 32333, 49.9, 0.88 );
		//newMetal( "jisooium", "#9d0ac2", 8367, 10003, 0.63, 15024, 12.2, 0.9 );
		//newMetal( "twicium", ["#F9C596", "#FC5D9D"], 10240, 18018, 0.88, 29029, 24.3, 0.91 );

	//ASSORTED LOONA-THEMED MATERIALS ##

		haseuliteSpreadWhitelist = ["haseulite","haseulite_powder","molten_haseulite","haseulite_gas"];
		jinsouliteSpreadWhitelist = ["jinsoulite","jinsoulite_powder","molten_jinsoulite","jinsoulite_gas"];

		function coldExplosionAfterCooling(pixel,x,y,radius,fire,smoke,power,damage) {
			pixel.temp -= 2*damage*radius*power;
		};

		function reactionStealerImmutableElem2(pixel,newPixel,reactionTarget,ignoreSelf=true,_chanceMultMeantForJinsoulites=1) {
			if(!elements[reactionTarget]) {
				throw new Error(`No such element ${reactionTarget}!`);
			};
			if(typeof(newPixel) === "undefined") { //timing issue?
				return false;
			};
			var newElement = newPixel.element;
			if(ignoreSelf && newElement === pixel.element) {
				return false;
			};
			var newInfo = elements[newElement];
			if(typeof(newInfo.reactions) === "undefined") {
				return false;
			};
			if(typeof(newInfo.reactions[reactionTarget]) === "undefined") {
				return false;
			};
			var pixel2 = pixel;
			var pixel1 = newPixel;
			var r = JSON.parse(JSON.stringify(newInfo.reactions[reactionTarget]));

			if (r.setting && settings[r.setting]===0) {
				return false;
			}
			// r has the attribute "y" which is a range between two y values
			// r.y example: [10,30]
			// return false if y is defined and pixel1's y is not in the range
			if (r.tempMin !== undefined && pixel1.temp < r.tempMin) {
				return false;
			}
			if (r.tempMax !== undefined && pixel1.temp > r.tempMax) {
				return false;
			}
			if (r.charged && !pixel.charge) {
				return false;
			}
			if (r.chance !== undefined && Math.random() < (r.chance * _chanceMultMeantForJinsoulites)) {
				return false;
			}
			if (r.y !== undefined && (pixel1.y < r.y[0] || pixel1.y > r.y[1])) {
				return false;
			}
			if(r.elem1 !== undefined && r.elem2 !== undefined) {
				if(r.elem1 !== null && r.elem2 !== null) {
					r.elem1 = [r.elem1,r.elem2].flat();
				};
			};
			if (r.elem1 !== undefined) {
				// if r.elem1 is an array, set elem1 to a random element from the array, otherwise set it to r.elem1
				if (Array.isArray(r.elem1)) {
					var elem1 = r.elem1[Math.floor(Math.random() * r.elem1.length)];
				} else { var elem1 = r.elem1; }

				if (elem1 == null) {
					deletePixel(pixel1.x,pixel1.y);
				}
				else {
					changePixel(pixel1,elem1);
				}
			}
			if (r.charge1) { pixel1.charge = r.charge1; }
			if (r.temp1) { pixel1.temp += r.temp1; pixelTempCheck(pixel1); }
			if (r.color1) { // if it's a list, use a random color from the list, else use the color1 attribute
				pixel1.color = pixelColorPick(pixel1, Array.isArray(r.color1) ? r.color1[Math.floor(Math.random() * r.color1.length)] : r.color1);
			}
			if (r.attr1) { // add each attribute to pixel1
				for (var key in r.attr1) {
					pixel1[key] = r.attr1[key];
				}
			}
			if (r.charge2) { pixel2.charge = r.charge2; }
			if (r.temp2) { pixel2.temp += r.temp2; pixelTempCheck(pixel2); }
			if (r.color2) { // if it's a list, use a random color from the list, else use the color2 attribute
				pixel2.color = pixelColorPick(pixel2, Array.isArray(r.color2) ? r.color2[Math.floor(Math.random() * r.color2.length)] : r.color2);
			}
			if (r.attr2) { // add each attribute to pixel2
				for (var key in r.attr2) {
					pixel2[key] = r.attr2[key];
				}
			}
			if (r.func) { r.func(pixel1,pixel2); }
			return r.elem1!==undefined;
		};

		elements.loona = {
			color: ["#6f7d54","#4f5d34","#7c8a61"],
			behavior: behaviors.POWDER,
			tempHigh: 1031,
			category: "random rocks",
			state: "solid",
			density: 2466.73,
			hardness: 0.56,
			breakInto: ["rock","sulfur","loona_gravel","loona_gravel","loona_gravel","haseulite_powder", "rock","sulfur","loona_gravel","loona_gravel","loona_gravel","jinsoulite_powder", "rock","sulfur","loona_gravel","loona_gravel","loona_gravel","heejinite_powder"],
		};

		var backupCategoryWhitelist = ["land","powders","weapons","food","life","corruption","states","fey","Fantastic Creatures","dyes","energy liquids","random liquids","random gases","random rocks"];
		var backupElementWhitelist = ["mercury", "chalcopyrite_ore", "chalcopyrite_dust", "copper_concentrate", "fluxed_copper_concentrate", "unignited_pyrestone", "ignited_pyrestone", "everfire_dust", "extinguished_everfire_dust", "mistake", "polusium_oxide", "vaporized_polusium_oxide", "glowstone_dust", "redstone_dust", "soul_mud", "wet_soul_sand", "nitrogen_snow", "fusion_catalyst", "coal", "coal_coke", "blast_furnace_fuel", "molten_mythril"];

		function spoutCriteria(name) {
			if(typeof(elements[name]) !== "object") {
				throw new Error(`Nonexistent element ${name}`);
			};
			var info = elements[name];
			//console.log(`${name} (${JSON.stringify(elements[name])})`);
			if(typeof(info.state) === "undefined") {
				var state = null;
			} else {
				var state = info.state;
			};
			if(typeof(info.category) === "undefined") {
				var category = "other";
			} else {
				var category = info.category;
			};
			if(excludedSpoutElements.includes(name)) {
				return false
			};
			var include = false;
			if(["liquid","gas"].includes(state)) {
				include = true;
			};
			if(info.movable) {
				include = true;
			};
			if(backupCategoryWhitelist.includes(category)) {
				include = true;
			};
			if(backupElementWhitelist.includes(name)) {
				include = true;
			};
			if(category.includes("mudstone")) {
				include = true;
			};
			//console.log(include);
			return include;
		};

		function heejiniteHeatCriteria(name) {
			if(typeof(elements[name]) !== "object") {
				throw new Error(`Nonexistent element ${name}`);
			};
			var info = elements[name];
			//console.log(`${name} (${JSON.stringify(elements[name])})`);
			if(typeof(info.tempLow) === "undefined") {
				return false;
			};
			if(typeof(info.tempHigh) !== "undefined" && info.tempHigh < elements.heejinite.tempHigh) {
				return false;
			};
			return (info.tempLow < elements.heejinite.tempHigh) || ((typeof(info.state) !== "undefined") && (info.state === "gas"));
		};

		spoutCriteria = function(name) {
			if(typeof(elements[name]) !== "object") {
				throw new Error(`Nonexistent element ${name}`);
			};
			var info = elements[name];
			//console.log(`${name} (${JSON.stringify(elements[name])})`);
			if(typeof(info.state) === "undefined") {
				var state = null;
			} else {
				var state = info.state;
			};
			if(typeof(info.category) === "undefined") {
				var category = "other";
			} else {
				var category = info.category;
			};
			var include = false;
			if(["liquid","gas"].includes(state)) {
				include = true;
			};
			if(info.movable) {
				include = true;
			};
			if(backupCategoryWhitelist.includes(category)) {
				include = true;
			};
			if(backupElementWhitelist.includes(name)) {
				include = true;
			};
			if(category.includes("mudstone")) {
				include = true;
			};
			//console.log(include);
			return include;
		};

		//it doesn't want to acknowledge spoutCriteria, so...

		runAfterAutogen(function() {
			elements.loona.stateHigh = ["molten_loona","rock","rock","rock","sulfur_gas","sulfur_gas","molten_haseulite","molten_loona","rock","rock","rock","sulfur_gas","sulfur_gas","molten_jinsoulite","molten_loona","rock","rock","rock","sulfur_gas","sulfur_gas","molten_heejinite"];
			hotHeejiniteElements = Object.keys(elements).filter(function(e) {
				return spoutCriteria(e) && heejiniteHeatCriteria(e) && !elements[e].excludeRandom && !e.startsWith("rad");
			});
		});

		elements.loona_gravel = {
			color: ["#b3be98","#919a6f","#68744b","#515931"],
			behavior: behaviors.POWDER,
			tempHigh: 1031,
			stateHigh: ["molten_loona","rock","rock","rock","sulfur_gas","sulfur_gas","molten_haseulite","molten_loona","rock","rock","rock","sulfur_gas","sulfur_gas","molten_jinsoulite","molten_loona","rock","rock","rock","sulfur_gas","sulfur_gas","molten_heejinite"],
			category: "random rocks",
			state: "solid",
			density: 1625.14,
			hardness: 0.97,
			breakInto: ["rock","sulfur","rock","haseulite_powder","rock","sulfur","rock","jinsoulite_powder","rock","sulfur","rock","heejinite_powder"],
		};

		jinsouliteValueObject = {
			cloud: 0.5,
			cloud_cloud: {value: 0.5, remainder: "cloud"},
			snow_cloud: {value: 0.75},
			hail_cloud: {value: 0.75},
			steam: 1,
			steam_cloud: {value: 0.5, remainder: "steam"},
			rain_cloud_cloud: {value: 0.5, remainder: "rain_cloud"},
			snow_cloud_cloud: {value: 0.5, remainder: "snow_cloud"},
			hail_cloud_cloud: {value: 0.5, remainder: "hail_cloud"},
			rain_cloud: {value: 1, remainder: "cloud"},
			water_cloud: {value: 1, remainder: "cloud"},
			snow: {value: 0.125},
			soda: {value: 0.8984375, remainder: "sugar"},
			blood: {value: 0.90625, remainder: "dna"},
			infection: {value: 0.90625, remainder: "dna"},
			packed_snow: {value: 0.90625},
			slime: {value: 0.9609375, remainder: "salt"}, //:eggTF:
			slush: {value: 0.9609375},
			ice: {value: 0.98046875},
			salt_water: {value: 1, remainder: "salt"}, //should be 0.965 but simplified here
			dirty_water: {value: 1, remainder: ["ash","dust","carbon_dioxide","ash","dust","carbon_dioxide","infection"]},
			sugar_water: {value: 1, remainder: "sugar"},
			seltzer: {value: 1, remainder: "carbon_dioxide"},
			pool_water: {value: 1, remainder: "chlorine"},
			water: {value: 1, tempMin: 80},
			water_bomb: 59,
			water_bomb_2: 164.5,
			water_bomb_3: 322.5,
			water_bomb_4: 534,
			water_bomb_5: 798,
			water_bomb_6: 1112.5,
			water_bomb_7: 1480,
			water_bomb_8: 1901.5,
			water_bomb_9: 2373,
			water_bomb_10: 2898, //average rates from in-game simulation since I can't come up with an exponential function
			water_bomb_bomb: 59*59,
			water_bomb_bomb_2: 59*164.5,
			water_bomb_bomb_3: 59*322.5,
			water_bomb_bomb_4: 59*534,
			water_bomb_bomb_5: 59*798,
			water_bomb_bomb_6: 59*1112.5,
			water_bomb_bomb_7: 59*1480,
			water_bomb_bomb_8: 59*1901.5,
			water_bomb_bomb_9: 59*2373,
			water_bomb_bomb_10: 59*2898, //creates up to around 2,898 water bombs, each of which theoretically create up to around 59 water
			water_bomb_bomb: 59*59,
			water_bomb_2_bomb: 164.5*59,
			water_bomb_3_bomb: 322.5*59,
			water_bomb_4_bomb: 534*59,
			water_bomb_5_bomb: 798*59,
			water_bomb_6_bomb: 1112.5*59,
			water_bomb_7_bomb: 1480*59,
			water_bomb_8_bomb: 1901.5*59,
			water_bomb_9_bomb: 2373*59,
			water_bomb_10_bomb: 2898*59,  //creates up to around 59 water bombs, each of which theoretically create up to around 2,898 water
			water_bomb_10_bomb_10: 2898*2898,  //skipping to the funny
			water_bomb_cloud: 30,
		};

		/*function customStaining(pixel,customColorRgb,stainOverride=null) {
			if (settings["stainoff"]) { return }
			var stain = (stainOverride !== null ? stainOverride : elements[pixel.element].stain);
			if (stain > 0) {
				var newColor = customColorRgb.match(/\d+/g);
			}
			else {
				var newColor = null;
			}

			for (var i = 0; i < adjacentCoords.length; i++) {
				var x = pixel.x+adjacentCoords[i][0];
				var y = pixel.y+adjacentCoords[i][1];
				if (!isEmpty(x,y,true)) {
					var newPixel = pixelMap[x][y];
					if (elements[pixel.element].ignore && elements[pixel.element].ignore.indexOf(newPixel.element) !== -1) {
						continue;
					}
					if ((elements[newPixel.element].id !== elements[pixel.element].id || elements[newPixel.element].stainSelf) && (solidStates[elements[newPixel.element].state] || elements[newPixel.element].id === elements[pixel.element].id)) {
						if (Math.random() < Math.abs(stain)) {
							if (stain < 0) {
								if (newPixel.origColor) {
									newColor = newPixel.origColor;
								}
								else { continue; }
							}
							else if (!newPixel.origColor) {
								newPixel.origColor = newPixel.color.match(/\d+/g);
							}
							// if newPixel.color doesn't start with rgb, continue
							if (!newPixel.color.match(/^rgb/)) { continue; }
							// parse rgb color string of newPixel rgb(r,g,b)
							var rgb = newPixel.color.match(/\d+/g);
							if (elements[pixel.element].stainSelf && elements[newPixel.element].id === elements[pixel.element].id) {
								// if rgb and newColor are the same, continue
								if (rgb[0] === newColor[0] && rgb[1] === newColor[1] && rgb[2] === newColor[2]) { continue; }
								var avg = [];
								for (var j = 0; j < rgb.length; j++) {
									avg[j] = Math.round((rgb[j]*(1-Math.abs(stain))) + (newColor[j]*Math.abs(stain)));
								}
							}
							else {
								// get the average of rgb and newColor, more intense as stain reaches 1 
								var avg = [];
								for (var j = 0; j < rgb.length; j++) {
									avg[j] = Math.floor((rgb[j]*(1-Math.abs(stain))) + (newColor[j]*Math.abs(stain)));
								}
							}
							// set newPixel color to avg
							newPixel.color = "rgb("+avg.join(",")+")";
						}
					}
				}
			}
		}*/

		function valueSpreading(pixel,whitelist=null) {
			var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
			var rX = randomNeighborOffset[0];
			var rY = randomNeighborOffset[1];
			var rfX = pixel.x+rX;
			var rfY = pixel.y+rY;
			if(!isEmpty(rfX,rfY,true)) {
				var rOtherPixel = pixelMap[rfX][rfY];
				var rOtherElement = rOtherPixel.element;
				if(whitelist === null || (whitelist !== null && whitelist.includes(rOtherElement))) {
					if(typeof(rOtherPixel.value) !== "number") {
						rOtherPixel.value = 0;
					};
					if(typeof(rOtherPixel) === "undefined" || isEmpty(rfX,rfY,true)) {
						return false;
					};
					var averageValue = (pixel.value + rOtherPixel.value) / 2;
					pixel.value = averageValue;
					rOtherPixel.value = averageValue;
				};
			};
			return true;
		};

		function valueAbsorbency(pixel,valueObject) {
			for(i = 0; i < adjacentCoords.length; i++) {
				var oX = adjacentCoords[i][0];
				var oY = adjacentCoords[i][1];
				var fX = pixel.x+oX;
				var fY = pixel.y+oY;
				if(!isEmpty(fX,fY,true)) {
					var otherPixel = pixelMap[fX][fY];
					var otherElement = otherPixel.element;
					var otherInfo = elements[otherElement];
					if(valueObject[otherElement]) {
						//console.log(`${otherElement} in your area`)
						if(typeof(otherPixel) === "undefined" || isEmpty(fX,fY,true)) {
							console.log(`nope`)
							return false;
						};
						var ValueData = valueObject[otherElement];
						//console.log(ValueData.toString())
						if(typeof(ValueData) == "object") {
							var tempMin = ValueData.tempMin ?? null;
							if(pixel.temp < tempMin) {
								continue;
							};
							var finalElement = ValueData.remainder ?? null;
							if(finalElement instanceof Array) {
								finalElement = finalElement[Math.floor(Math.random() * finalElement.length)];
							};
							if(finalElement !== 0) {
								if(finalElement !== null) {
									changePixel(otherPixel,finalElement);
								} else {
									deletePixel(otherPixel.x,otherPixel.y);
								};
							};
							pixel.value += ValueData.value;
						} else if(typeof(ValueData) === "number") {
							deletePixel(otherPixel.x,otherPixel.y);
							pixel.value += ValueData;
						};
					};
				};
			};
			return true;
		};

		function valueFunction(pixel,valueObject,elementWhitelist=null) {
			if(typeof(pixel.value) === "undefined") {
				pixel.value = 0;
			};

			var oldValue = pixel.value;
			if(!valueAbsorbency(pixel,valueObject) || isNaN(pixel.value)) {
				pixel.value = oldValue;
			};

			var oldValue = pixel.value;
			if(!valueSpreading(pixel,elementWhitelist) || isNaN(pixel.value)) {
				pixel.value = oldValue;
			};
		}

		//this important thing somehow disappeared
		haseuliteValueObject = {
			light: 1,
			radiation: 4,
			fire: {value: 6, remainder: "smoke"},
			rad_fire: {value: 10, remainder: "rad_smoke"},
			liquid_fire: {value: 12, remainder: ["fire","liquid_smoke","smoke"]},
			plasma: {value: 15, remainder: "fire"},
			holy_fire: 25,
			god_slayer_fire: 40,
			liquid_rad_fire: {value: 20, remainder: [null,"rad_fire","rad_fire","rad_smoke","rad_smoke"]},
			liquid_plasma: {value: 30, remainder: ["plasma","liquid_fire","fire"]},
			liquid_holy_fire: {value: 50, remainder: ["holy_fire","bless","light"]},
			liquid_god_slayer_fire: {value: 80, remainder: ["god_slayer_fire",null,null]},
			liquid_irradium: {value: 4, remainder: 0}
		};

		function haseulitoidTick(pixel) {
			if(pixel.value == undefined) { pixel.value = 0 };
			valueFunction(pixel,haseuliteValueObject,haseuliteSpreadWhitelist);
			if(pixel.radioactive && Math.random() < 0.05) { pixel.value += 4 };
			if(pixel.oldColor === undefined) { pixel.oldColor = pixelColorPick(pixel) };
			if(pixel.oldColor === null) { pixel.oldColor = pixel.color };
			if(isNaN(pixel.value)) { pixel.value = 0 };
			pixel.color = lightenColor(pixel.oldColor,pixel.value / 3);

			var mVal = elements[pixel.element].haseulitoidMaxValue ?? 800;
			if(pixel.value >= mVal) {
				var coldBoomChance = Math.max(0.008 * ((pixel.value - mVal) / (mVal * 2/7)), 0.001);
				if(Math.random() < coldBoomChance) {
					var coldBoomRadius = Math.min(40,Math.floor(7 + ((pixel.value - mVal) / (mVal * 2/7))));
					explodeAtPlus(pixel.x,pixel.y,coldBoomRadius,"cold_fire","cold_smoke",null,coldExplosionAfterCooling);
				};
			};
		}

		elements.haseulite = {
			color: ["#3cb00e", "#25d119", "#79f553"],
			fireColor: ["#08a953", "#2ea332", "#d1e0d3"],
			properties: {
				oldColor: null
			},
			behavior: behaviors.WALL,
			tick: function(pixel) { haseulitoidTick(pixel) },
			excludeVelocity: true, //wall shouldn't move
			tempHigh: 1757,
			onExplosionBreakOrSurvive: function(pixel,x,y,radius) {
				/*power is always radius/10
					r 5: value 7
					r 10: value 14
					r 15: value 28 
					r 20: value 56 
					r 25: value 112 
					r 30: value 224 
				*/
				pixel.value += (2**(((radius) / 5) - 1) * 7);
			},
			category: "solids",
			state: "solid",
			density: 7550,
			hardness: 0.93,
			breakInto: "haseulite_powder",
			conduct: 0.84,
		};

		if(!elements.steel.reactions) {
			elements.steel.reactions = {};
		};

		elements.steel.reactions.haseulite_powder = {
			elem1: "haseulite_vent",
			elem2: null,
			chance: 0.01,
			tempMin: 1200,
		};

		adjacentCoordsInverted = [[0,-1],[0,1],[-1,0],[1,0]];

		elements.haseulite_vent = {
			color: "#88b058",
			fireColor: ["#08a953", "#2ea332", "#d1e0d3"],
			behavior: behaviors.WALL,
			rotatable: true,
			desc: "This uses rotation, so just use debug to see the r value. r 0 means it vents haseulite below it upwards, r 1 means it vents haseulite above it downwards, r 2 means it vents left, and r 3 means it vents right.",
			tick: function(pixel) { 
				if(isNaN(pixel.r)) {
					pixel.r = 0;
				};
				pixel.r = pixel.r % 4;
				var coord = adjacentCoords[pixel.r];
				var invertCoord = adjacentCoordsInverted[pixel.r];	

				var fX = pixel.x+coord[0];
				var fY = pixel.y+coord[1];

				if(!isEmpty(fX,fY,true)) {
					var otherPixel = pixelMap[fX][fY];
					var otherElement = otherPixel.element;
					var otherInfo = elements[otherElement];
					if(typeof(otherPixel) === "undefined" || isEmpty(fX,fY,true)) {
						return false;
					};
					if(haseuliteSpreadWhitelist.includes(otherElement)) {
						var ventLimit = Math.min(10,Math.floor(1 + (Math.sqrt(Math.max(otherPixel.value,1)) / 2)));
						for(i = 1; i <= ventLimit; i++) {
							if(otherPixel.value >= 3) {
								var fIX = pixel.x+(invertCoord[0] * i);
								var fIY = pixel.y+(invertCoord[1] * i);
								if(isEmpty(fIX,fIY,false)) {
									var newCF = createPixelReturn("cold_fire",fIX,fIY);
									newCF.temp -= (otherPixel.value / 4);
									otherPixel.value -= 3;
								} else { //if the pixel to place isn't empty
									if(!outOfBounds(fIX,fIY)) { //if it isn't OoB
										if(pixelMap[fIX][fIY].element !== "cold_fire") { //if it isn't cold fire
											break;
										};
									} else { //if it is OoB
										break;
									};
								};
							} else {
								break;
							};
						};
					};
				};
				return true;
			},
			excludeVelocity: true, //wall shouldn't move
			tempHigh: elements.steel.tempHigh,
			stateHigh: ["molten_steel","haseulite_powder"],
			breakInto: ["metal_scrap","haseulite_powder"],
			category: "machines",
			state: "solid",
			density: 7550,
			hardness: 0.93,
			breakInto: "haseulite_powder",
			conduct: 0.84,
		}

		elements.haseulite_powder = {
			color: ["#5fb33e", "#32ba29", "#63d141"],
			properties: {
				oldColor: null
			},
			category: "powders",
			fireColor: ["#08a953", "#2ea332", "#d1e0d3"],
			tempHigh: 1757,
			behavior: behaviors.POWDER,
			tick: function(pixel) { haseulitoidTick(pixel) },
			onExplosionBreakOrSurvive: function(pixel,x,y,radius) {
				/*power is always radius/10
					r 5: value 7
					r 10: value 14
					r 15: value 28 
					r 20: value 56 
					r 25: value 112 
					r 30: value 224 
				*/
				pixel.value += (2**(((radius) / 5) - 1) * 7);
			},
			stateHigh: "molten_haseulite",
			category: "powders",
			state: "solid",
			hidden: true,
			density: 4512,
			hardness: 0.7,
			conduct: 0.43,
		};

		elements.molten_haseulite = {
			color: ["#cbf569","#f1ffd6","#fdffb5", "#fffa99"],
			fireColor: ["#08a953", "#2ea332", "#d1e0d3"],
			properties: {
				oldColor: null
			},
			behavior: behaviors.LIQUID, //fire creation is problematic due to smoke cooling
			tick: function(pixel) { haseulitoidTick(pixel) },
			onExplosionBreakOrSurvive: function(pixel,x,y,radius) {
				/*power is always radius/10
					r 5: value 7
					r 10: value 14
					r 15: value 28 
					r 20: value 56 
					r 25: value 112 
					r 30: value 224 
				*/
				pixel.value += (2**(((radius) / 5) - 1) * 7);
			},
			density: 7214,
			hardness: 0.52,
			breakInto: "haseulite_gas",
			temp: 1957,
			tempHigh: 3100,
			conduct: 0.23,
		};

		elements.haseulite_gas = {
			color: ["#ffff9d", "#ffffff", "#e9ffe6", "#ffffe5"],
			fireColor: ["#08a953", "#2ea332", "#d1e0d3"],
			properties: {
				oldColor: null
			},
			tick: function(pixel) { haseulitoidTick(pixel) },
			onExplosionBreakOrSurvive: function(pixel,x,y,radius) {
				/*power is always radius/10
					r 5: value 7
					r 10: value 14
					r 15: value 28 
					r 20: value 56 
					r 25: value 112 
					r 30: value 224 
				*/
				pixel.value += (2**(((radius) / 5) - 1) * 7);
			},
			density: 0.289,
			temp: 3700,
			hardness: 1,
			conduct: 0.13,
		};

		elements.hanichrite = { //the names nickel, chrome, and haseulite do not mix
			color: ["#dde6bc", "#ebf2ef", "#e8fab1"],
			behavior: behaviors.WALL,
			tempHigh: 1560,
			category: "solids",
			density: 8218,
			conduct: 0.75,
			hardness: 0.78,
			state: "solid",
			tick: function(pixel) {
				if(nichromeDoNeighborCount) {
					var neighbors = 0;
					for(i = 0; i < adjacentCoords.length; i++) {
						if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
							var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]];
							if(elements[newPixel.element].conduct) { neighbors++ };
						};
					};
				};
				if(pixel.charge) {
					pixel.temp -= ((1.13 + nichromeNeighborLogic(neighbors)) * pixel.charge);
				};
			},
		};

		elements.molten_hanichrite = {
			tick: function(pixel) {
				if(nichromeDoNeighborCount) {
					var neighbors = 0;
					for(i = 0; i < adjacentCoords.length; i++) {
						if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
							var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]];
							if(elements[newPixel.element].conduct) { neighbors++ };
						};
					};
				};
				if(pixel.charge) {
					pixel.temp -= ((1.13 + nichromeNeighborLogic(neighbors)) * pixel.charge) * 1.09;
				};
			},
		};

		/*
		var shimmeringColor = convertHslObjects(hslColorStringToObject(`hsl(${(pixelTicks / 2) % 360},100%,50%)`,"rgb"));
		customStaining(pixel,shimmeringColor,0.2);
		*/

		function heejinitoidTick(pixel) {
			pixel.color ??= pixelColorPick(pixel);
			if(pixel.oldColor === null) { pixel.oldColor = pixel.color };
			if(pixel.oldColor === undefined) { pixel.oldColor = pixelColorPick(pixel) };
			var color = rgbStringToHSL((convertColorFormats(pixel.oldColor,"rgb") ?? pixelColorPick(pixel)),"json");
			var heejiniteHueSpread = 30 + (pixel.temp/9.25)
			var hueOffset = (Math.sin(pixelTicks / 11) * heejiniteHueSpread) + 15; color.h += hueOffset;
			var color = convertHslObjects(color,"rgb");
			pixel.color = color;
		};

		function hotHeejinitoidTick(pixel) {
			if(pixel.oldColor === undefined) { pixel.oldColor = pixelColorPick(pixel) };
			if(Math.random() < (pixel.temp >= 1500 ? 0.02 : 0.01)) {
				if(pixel.temp >= 1387.5) {
					var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
					var rX = randomNeighborOffset[0];
					var rY = randomNeighborOffset[1];
					var rfX = pixel.x+rX;
					var rfY = pixel.y+rY;
					if(isEmpty(rfX,rfY,false)) {
						var randomEligibleHotElement = hotHeejiniteElements[Math.floor(Math.random() * hotHeejiniteElements.length)];
						createPixel(randomEligibleHotElement,rfX,rfY);
						pixelMap[rfX][rfY].temp = pixel.temp;
					};
				};
			};
		}

		elements.heejinite = {
			color: ["#cf1172", "#fa1977", "#ff619e"],
			fireColor: ["#a9085e", "#a32e61", "#fca7c6"],
			properties: {
				oldColor: null
			},
			behavior: behaviors.WALL,
			tick: function(pixel) { heejinitoidTick(pixel) },
			excludeVelocity: true, //wall shouldn't move
			tempHigh: 837,
			category: "solids",
			state: "solid",
			density: 3773,
			stain: 0.1,
			hardness: 0.79,
			breakInto: "heejinite_powder",
			conduct: 0.86,
		};

		elements.heejinite_powder = {
			color: ["#d64790", "#e63e84", "#f054ac"],
			fireColor: ["#a9085e", "#a32e61", "#fca7c6"],
			properties: {
				oldColor: null
			},
			behavior: behaviors.POWDER,
			tick: function(pixel) { heejinitoidTick(pixel) },
			excludeVelocity: true, //wall shouldn't move
			tempHigh: 837,
			hidden: true,
			stateHigh: "molten_heejinite",
			category: "powders",
			state: "solid",
			density: 1412,
			stain: 0.1,
			hardness: 0.66,
			breakInto: "heejinite_powder",
			conduct: 0.42,
		};

		elements.molten_heejinite = {
			color: ["#ff0f77","#ff59c2","#ff405c", "#fa5a48"],
			fireColor: ["#a9085e", "#a32e61", "#fca7c6"],
			properties: {
				oldColor: null
			},
			tick: function(pixel) {
				heejinitoidTick(pixel);
				hotHeejinitoidTick(pixel);
			},
			density: 3121,
			hardness: 0.5,
			breakInto: "heejinite_gas",
			temp: 1000,
			tempHigh: 1501,
			conduct: 0.22,
		};

		elements.heejinite_gas = {
			color: ["#fffab8", "#ffdab3", "#ffd1d1", "#ffc4df", "#ffb0eb"],
			fireColor: ["#a9085e", "#a32e61", "#fca7c6"],
			properties: {
				oldColor: null
			},
			tick: function(pixel) {
				heejinitoidTick(pixel);
				hotHeejinitoidTick(pixel);
			},
			density: 0.117,
			temp: 1800,
			hardness: 1,
			conduct: 0.12,
		};

		jinsouliteReducedSwapWhitelist = ["slime","glue","soda","milk","chocolate_milk","fruit_milk","ink","blood","vaccine","antibody","infection","sap","ketchup","spirit_tear","enchanted_ketchup","lean","poisoned_ketchup","dirty_ketchup","zombie_blood"];

		function jinsouliteDissolution(pixel) {
			var did = false;
			for(i = 0; i < 2; i++) {
				var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
				var rfX = pixel.x+randomNeighborOffset[0];
				var rfY = pixel.y+randomNeighborOffset[1];
				if(!isEmpty(rfX,rfY,true)) {
					var rOtherPixel = pixelMap[rfX][rfY];
					if(!rOtherPixel) { return false };
					var rOtherElement = rOtherPixel.element;
					if(rOtherElement.endsWith("water") || (Math.random() < 0.3 && jinsouliteReducedSwapWhitelist.includes(rOtherElement))) {
						swapPixels(pixel,rOtherPixel);
						did = true;
					};
				};
			};
			return did;
		};

		function jinsouliteMovement(pixel,move1Spots,move2Spots) {		
			if(move1Spots.length > 0) {
				var randomMove1 = move1Spots[Math.floor(Math.random() * move1Spots.length)];
				if(!tryMove(pixel, pixel.x+randomMove1[0], pixel.y+randomMove1[1])) {
					//console.log((pixel.x+randomMove1[0]) + " " + (pixel.y+randomMove1[1]))
					var newPixel = null;
					if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
						newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
					};
					if(outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1]) || !reactionStealerImmutableElem2(pixel,newPixel,"water",true,2)) {
						if(move2Spots.length > 0) {
							var randomMove2 = move2Spots[Math.floor(Math.random() * move2Spots.length)];
							if(!tryMove(pixel, pixel.x+randomMove2[0], pixel.y+randomMove2[1])) {
								var newPixel = null;
								if(!outOfBounds(pixel.x+randomMove1[0],pixel.y+randomMove1[1])) {
									newPixel = pixelMap[pixel.x+randomMove1[0]][pixel.y+randomMove1[1]]; //newPixel is AAA
								};
								if(newPixel !== null) { reactionStealerImmutableElem2(pixel,newPixel,"water",true,2) };
							};
						};
					};
				};
			};
			doDefaults(pixel);
		};

		function jinsouliteSolidNonWaterSideReactions(pixel) {
			var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
			var rfX = pixel.x+randomNeighborOffset[0];
			var rfY = pixel.y+randomNeighborOffset[1];
			if(!isEmpty(rfX,rfY,true)) {
				var rOtherPixel = pixelMap[rfX][rfY];
				if(typeof(rOtherPixel) === "undefined" || isEmpty(rfX,rfY,true)) {
					return false;
				};
				reactionStealerImmutableElem2(pixel,rOtherPixel,"water",true,2);
			};
			return true;
		};

		function jinsouliteSolidWaterSideReactions(pixel) {
			var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
			var rfX = pixel.x+randomNeighborOffset[0];
			var rfY = pixel.y+randomNeighborOffset[1];
			if(!isEmpty(rfX,rfY,true)) {
				var pixel2 = pixelMap[rfX][rfY];
				var pixel1 = pixel;
				if(typeof(pixel2) === "undefined" || isEmpty(rfX,rfY,true)) {
					return false;
				};
				if(typeof(pixel1) === "undefined" || isEmpty(pixel.x,pixel.y,true)) {
					return false;
				};
				var rOtherElement = pixel2.element;
				var waterReactions = elements.water.reactions;

				if(rOtherElement === pixel.element) {
					return false;
				};
				if(waterReactions[rOtherElement]) {
					var r = waterReactions[rOtherElement];

					if (r.setting && settings[r.setting]===0) {
						return false;
					}
					// r has the attribute "y" which is a range between two y values
					// r.y example: [10,30]
					// return false if y is defined and pixel1's y is not in the range
					if (r.tempMin !== undefined && pixel1.temp < r.tempMin) {
						return false;
					}
					if (r.tempMax !== undefined && pixel1.temp > r.tempMax) {
						return false;
					}
					if (r.charged && !pixel.charge) {
						return false;
					}
					if (r.chance !== undefined && Math.random() < (r.chance * 2)) {
						return false;
					}
					if (r.y !== undefined && (pixel1.y < r.y[0] || pixel1.y > r.y[1])) {
						return false;
					}
					if (r.charge1) { pixel1.charge = r.charge1; }
					if (r.temp1) { pixel1.temp += r.temp1; pixelTempCheck(pixel1); }
					if (r.color1) { // if it's a list, use a random color from the list, else use the color1 attribute
						pixel1.color = pixelColorPick(pixel1, Array.isArray(r.color1) ? r.color1[Math.floor(Math.random() * r.color1.length)] : r.color1);
					}
					if (r.attr1) { // add each attribute to pixel1
						for (var key in r.attr1) {
							pixel1[key] = r.attr1[key];
						}
					}
					var elem1 = r.elem1
					if (elem1 !== undefined && elem1 instanceof Array) {
						elem1 = elem1[Math.floor(Math.random() * elem1.length)];
					};

					if (r.elem2 !== undefined) {
						// if r.elem2 is an array, set elem2 to a random element from the array, otherwise set it to r.elem2
						if (Array.isArray(r.elem2)) {
							var elem2 = r.elem2[Math.floor(Math.random() * r.elem2.length)];
						} else { var elem2 = r.elem2; }

						if (elem2 == null) {
							if(elem1 !== undefined) { changePixel(pixel2,elem1) };
						}
						else {
							changePixel(pixel2,elem2);
						}
					}
					if (r.charge2) { pixel2.charge = r.charge2; }
					if (r.temp2) { pixel2.temp += r.temp2; pixelTempCheck(pixel2); }
					if (r.color2) { // if it's a list, use a random color from the list, else use the color2 attribute
						pixel2.color = pixelColorPick(pixel2, Array.isArray(r.color2) ? r.color2[Math.floor(Math.random() * r.color2.length)] : r.color2);
					}
					if (r.attr2) { // add each attribute to pixel2
						for (var key in r.attr2) {
							pixel2[key] = r.attr2[key];
						}
					}
					if (r.func) { r.func(pixel1,pixel2); }
					return r.elem1!==undefined || r.elem2!==undefined;
				};
			};
			return true;
		};

		function jinsouliteValue(pixel) {
			valueFunction(pixel,jinsouliteValueObject,jinsouliteSpreadWhitelist);
			if(pixel.oldColor === null) { pixel.oldColor = pixel.color };
			if(isNaN(pixel.value)) { pixel.value = 0 };
			pixel.color = changeSaturation(pixel.oldColor,pixel.value / 3,"subtract","rgb")

			if(pixel.value > 1) {
				if(Math.random() < Math.min((pixel.value / 200),0.5)) {
					var randomNeighborOffset = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)];
					var rX = randomNeighborOffset[0];
					var rY = randomNeighborOffset[1];
					var rfX = pixel.x+rX;
					var rfY = pixel.y+rY;
					if(isEmpty(rfX,rfY,false)) {
						createPixel("water",rfX,rfY);
						pixel.value--;
					};
				};
				/*for(g = 0; g < adjacentCoords.length; g++) {
					var oX = adjacentCoords[g][0];
					var oY = adjacentCoords[g][1];
					var fX = pixel.x+oX;
					var fY = pixel.y+oY;
					if(isEmpty(fX,fY,false)) {
						createPixel("water",fX,fY);
						pixel.value--;
					};
				};*/
			};
		}

		function jinsoulitoidTick(pixel,move1Spots=[],move2Spots=[]) {
			if(pixel.oldColor === undefined) { pixel.oldColor = pixelColorPick(pixel) };
			if(pixel.value == undefined) { pixel.value = 0 };
			if(jinsouliteDissolution(pixel)) {
				return;
			};
			jinsouliteValue(pixel);
			jinsouliteMovement(pixel,move1Spots,move2Spots);
		};

		elements.jinsoulite = {
			color: ["#0e51b0", "#2129ff", "#3b3dbf"],
			fireColor: ["#121978", "#6a9fe6", "#5963d9"],
			behavior: [
				"XX|CR:water%0.05|XX",
				"CR:water%0.05|XX|CR:water%0.05",
				"XX|CR:water%0.05|XX"
			],
			behaviorOn: [
				"XX|CR:water%0.15|XX",
				"CR:water%0.15|XX|CR:water%0.15",
				"XX|CR:water%0.15|XX"
			],
			properties: {
				oldColor: null
			},
			tick: function(pixel) { 
				if(pixel.value == undefined) { pixel.value = 0 };
				if(pixel.oldColor === undefined) { pixel.oldColor = pixelColorPick(pixel) };
				jinsouliteValue(pixel);
				jinsouliteSolidNonWaterSideReactions(pixel);
				jinsouliteSolidWaterSideReactions(pixel);
			},
			tempHigh: 2606,
			category: "solids",
			state: "solid",
			density: 8331,
			hardness: 0.82,
			breakInto: "jinsoulite_powder",
			conduct: 0.93,
		};

		elements.jinsoulite_powder = {
			color: ["#4580ba", "#355eb0", "#2d6fc4"],
			fireColor: ["#121978", "#6a9fe6", "#5963d9"],
			tempHigh: 2606,
			behavior: [
				"XX|CR:water%0.05|XX",
				"CR:water%0.05|XX|CR:water%0.05",
				"XX|CR:water%0.05|XX"
			],
			properties: {
				oldColor: null
			},
			category: "powders",
			behaviorOn: [
				"XX|CR:water%0.15|XX",
				"CR:water%0.15|XX|CR:water%0.15",
				"XX|CR:water%0.15|XX"
			],
			tick: function(pixel) { jinsoulitoidTick(pixel,[[0,1]],[[-1,1],[1,1]]) },
			stateHigh: "molten_jinsoulite",
			category: "powders",
			state: "solid",
			hidden: true,
			density: 5801,
			hardness: 0.7,
			conduct: 0.43,
		};

		elements.molten_jinsoulite = {
			behavior: [
				"XX|CR:fire,fire,steam%0.5|XX",
				"XX|XX|XX",
				"XX|XX|XX"
			],
			behaviorOn: [
				"XX|CR:fire,steam,steam%0.7|XX",
				"CR:steam%0.1|XX|CR:steam%0.1",
				"XX|CR:steam%0.1|XX"
			],
			properties: {
				oldColor: null
			},
			color: ["#4e35db","#7767eb","#a876f5", "#78acff"],
			fireColor: ["#121978", "#6a9fe6", "#5963d9"],
			fireElement: ["fire","fire","steam"],
			tick: function(pixel) { jinsoulitoidTick(pixel,[[-1,1],[0,1],[1,1]],[[-1,0],[1,0]]); },
			density: 6448,
			hardness: 0.61,
			breakInto: "jinsoulite_gas",
			temp: 3000,
			tempHigh: 5532.8509,
			conduct: 0.34,
		};

		elements.jinsoulite_gas = {
			color: ["#c0f0ef", "#c2c1db", "#c0bff5", "#cdcce6"],
			behavior: [
				"XX|CR:steam%0.5|XX",
				"CR:steam%0.5|XX|CR:steam%0.5",
				"XX|CR:steam%0.5|XX",
			],
			behaviorOn: [
				"XX|CR:steam%1|XX",
				"CR:steam%1|XX|CR:steam%1",
				"XX|CR:steam%1|XX",
			],
			fireColor: ["#08a953", "#2ea332", "#d1e0d3"],
			properties: {
				oldColor: null
			},
			tick: function(pixel) { jinsoulitoidTick(pixel,adjacentCoords,[[-1,-1],[1,-1],[1,1],[-1,1]]) },
			density: 0.5833,
			temp: 6000,
			hardness: 1,
			conduct: 0.19,
		};

		//apples (used for yvesite)

		appleAttachWhitelist = ["wood","tree_branch"];

		elements.apple = {
			color: ["#ad2333", "#b51616", "#d6782f", "#e3c634", "#99de31"],
			isFood: true,
			tick: function(pixel) {
				if(pixel.attached) { //only attaches upwards
					if(isEmpty(pixel.x,pixel.y-1,true)) {
						pixel.attached = false;
					};
				} else { //Move if not attached
					if (!tryMove(pixel, pixel.x, pixel.y+1)) {
						if(Math.random() < 0.3) {
							if (Math.random() < 0.5) {
								if (!tryMove(pixel, pixel.x+1, pixel.y+1)) {
									tryMove(pixel, pixel.x-1, pixel.y+1);
								};
							} else {
								if (!tryMove(pixel, pixel.x-1, pixel.y+1)) {
									tryMove(pixel, pixel.x+1, pixel.y+1);
								};
							};
						};
					};
				};
				doDefaults(pixel);
				var shouldSpoil = true; //spoil by default
				if(pixel.attached) { //if it's attached
					if(!isEmpty(pixel.x,pixel.y-1,true)) { //if the attachment coords are a pixel and not OOB
						var attachPixel = pixelMap[pixel.x][pixel.y-1];
						var attachElement = attachPixel.element;
						if(appleAttachWhitelist.includes(attachElement)) {//if the element is a whitelisted "don't spoil" element
							shouldSpoil = false; //then don't spoil
						};
					};
				};
				if(shouldSpoil) { //spoil if not attached
					if(pixel.temp > -18 && pixel.temp <= 4) { //(no spoiling below -18C)
						pixel.spoilage += Math.max(Math.min(scale(pixel.temp,-18,4,0,9),9),0)
					} else if(pixel.temp > 4) {
						pixel.spoilage += Math.max(Math.min(scale(pixel.temp,4,20,9,30),40),0)
					};
				};
				if(pixel.spoilage > 14400) { //3600 = 120 ticks at 20C
					if(Math.random() < 0.05) {
						changePixel(pixel,"rotten_apple");
					};
				};
			},
			properties: {
				"spoilage": 0,
				"attached": false
			},
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			tempHigh: 200,
			stateHigh: ["steam", "ash"],
			onTryMoveInto: function(pixel,otherPixel) {
				var otherInfo = elements[otherPixel.element]
				if(typeof(otherInfo.state) === "string" && otherInfo.state !== "gas") {
					pixel.attached = false;
				};
			},
		};

		elements.rotten_apple = {
			hidden: true,
			color: ["#802e24", "#9c4227", "#a34b26"],
			behavior: [
				"XX|CR:stench,fly%0.1|XX",
				"M2%0.5|CH:dirty_water,fly,fly%0.007|M2%0.5",
				"M2|M1|M2"
			],
			stain: 0.01,
			burn: 5,
			burnInto: ["steam", "ash"],
			burnTime: 600,
			tempHigh: 200,
			stateHigh: ["steam", "ash"],
		};

		//Yvesite

		var yvesiteAppleSpots = [[-1, 1], [0, 1], [1, 1]];
		var yvesitePowderAppleSpots = [[-1, 0], [0, -1], [1, 0]];

		function yvesiteApples(pixel,offsets) {
			if(pixel.charge) {
				var probAdd = Math.min(0,Math.max(pixel.temp,100)) / 2500;
				if(Math.random() < (0.02 + probAdd)) {
					var appleOffset = randomChoice(offsets);
					if(tryCreatePixel("apple",pixel.x+appleOffset[0],pixel.y+appleOffset[1])) {
						var apple = pixelMap[pixel.x+appleOffset[0]][pixel.y+appleOffset[1]];
						apple.color = pixelColorPick(apple,"#c40e2c");
						apple.spoilage = -Infinity;
						return true;
					} else {
						return null;
					};
				};
			};
			return false;
		};

		elements.yvesite = {
			color: ["#850f2c", "#9c0e3d", "#911f3b", "#701625"],
			fireColor: ["#b5103f", "#ab3254", "#cc2157", "#ba0936"],
			behavior: behaviors.WALL,
			tick: function(pixel) {
				yvesiteApples(pixel,yvesiteAppleSpots);
			},
			reactions: {
				heejinite: {temp1: 1, temp2: 1},
				molten_heejinite: {temp1: 2, temp2: 2},
				heejinite_powder: {temp1: 2, temp2: 2},
				heejinite_gas: {temp1: 3, temp2: 3}
			},
			tempHigh: 1545,
			category: "solids",
			state: "solid",
			density: 3601,
			hardness: 0.88,
			breakInto: "yvesite_powder",
			conduct: 0.94,
		};

		elements.yvesite_powder = {
			color: ["#8f1734", "#962638", "#b32749", "#911a3e"],
			fireColor: ["#b5103f", "#ab3254", "#cc2157", "#ba0936"],
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				yvesiteApples(pixel,yvesitePowderAppleSpots);
			},
			reactions: {
				heejinite: {temp1: 2, temp2: 2},
				molten_heejinite: {temp1: 3, temp2: 3},
				heejinite_powder: {temp1: 3, temp2: 3},
				heejinite_gas: {temp1: 4, temp2: 4}
			},
			tempHigh: 1545,
			stateHigh: "molten_yvesite",
			category: "solids",
			state: "solid",
			density: 1500,
			hardness: 0.43,
			breakInto: "yvesite_powder",
			conduct: 0.84,
		};

		elements.molten_yvesite = {
			color: ["#e81554", "#c90842", "#db4d70", "#cf2b54"],
			fireColor: ["#b5103f", "#ab3254", "#cc2157", "#ba0936"],
			behavior: behaviors.MOLTEN,
			density: 3608,
			state: "liquid",
			hardness: 0.57,
			breakInto: "yvesite_gas",
			temp: 1545,
			tempHigh: 3145,
			stateHigh: "yvesite_gas",
			tempLow: 1545,
			stateLow: "yvesite",
			conduct: 0.22,
		};

		elements.yvesite_gas = {
			color: ["#e34070", "#d13060", "#c2234a", "#db4866"],
			fireColor: ["#b5103f", "#ab3254", "#cc2157", "#ba0936"],
			behavior: behaviors.GAS,
			state: "gas",
			tempLow: 3145,
			stateLow: "molten_yvesite",
			density: 8.16,
			temp: 3300,
			hardness: 1,
			conduct: 0.11,
		};

		elements.fly.reactions.rotten_apple = { "elem2":null, chance:0.15, func:behaviors.FEEDPIXEL };

		//Vivite

		elements.vivite = {
			color: ["#ffb5ee", "#fc9fe0", "#fcd9fb"],
			colorOn: ["#ff8ca9", "#f76583", "#fcb8d5"],
			fireColor: ["#ff66ba", "#ff85ef", "#ff99f7"],
			behavior: behaviors.WALL,
			onCharge: function(pixel) {
				if(isNaN(pixel.charge)) { pixel.charge = 0 };
				if(isNaN(pixel.temp)) { pixel.temp = 20 };
				if(!pixel) { return false };
				doElectricity(pixel);
				for(i = 0; i < adjacentCoords.length; i++) {
					var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]]?.[pixel.y+adjacentCoords[i][1]];
					if(newPixel) {
						newPixel.charge ??= 0;
						if(isNaN(newPixel.charge)) { newPixel.charge = 0 };
						try {
							newPixel.charge ??= 0;
							if(isNaN(newPixel.charge)) { newPixel.charge = 0 };
							doElectricity(newPixel)
							if(isNaN(newPixel.charge)) { newPixel.charge = 0 };
						} catch (error) {
							if(error.toString().includes("Maximum call stack size exceeded")) {
								return;
							};
							throw error;
						};
					} else {
						continue;
					};
				};
			},
			tick: function(pixel) {
				if(isNaN(pixel.charge)) { pixel.charge = 0 };
				if(isNaN(pixel.temp)) { pixel.temp = 20 };
				if(Math.random() < 0.013 && exposedToAir(pixel)) {
					changePixel(pixel,"vivite_oxide",false);
					pixel.temp += 4;
				};			
			},
			burnTime: 160,
			burnTempChange: 10.65,
			burnInto: "vivite_oxide_powder",
			noResistance: true,
			reactions: {
				"ice": { elem1: "vivite_oxide", elem2: null, temp1: 0.2 },
				"water": { elem1: "vivite_oxide", elem2: null, temp1: 0.2 },
				"steam": { elem1: "vivite_oxide", elem2: null, temp1: 0.2 },
				"seltzer": { elem1: "vivite_oxide", elem2: "carbon_dioxide" },
				"seltzer_ice": { elem1: "vivite_oxide", elem2: "carbon_dioxide" },
				"sugar_water": { elem1: "vivite_oxide", elem2: "sugar" },
				"sugar_ice": { elem1: "vivite_oxide", elem2: "sugar" },
				"pool_water": { elem1: "vivite_oxide", elem2: "chlorine" },
				"pool_ice": { elem1: "vivite_oxide", elem2: "chlorine" },
				"salt_water": { elem1: "vivite_oxide", elem2: "salt" },
				"salt_ice": { elem1: "vivite_oxide", elem2: "salt" }
			},
			tempHigh: 938,
			category: "solids",
			state: "solid",
			density: 11013,
			hardness: 0.98,
			breakInto: "vivite_powder",
			conduct: 1,
		};

		elements.vivite_powder = {
			color: ["#f7a6e5", "#fa70d1", "#f0bbf2"],
			fireColor: ["#ff66ba", "#ff85ef", "#ff99f7"],
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(Math.random() < 0.027 && exposedToAir(pixel)) {
					if(getEmptyMooreNeighbors(pixel).length > 4) {
						pixel.burning = true;
						pixel.burnStart = pixelTicks;
					} else {
						pixel.temp += 18;
						changePixel(pixel,getStateAtTemp("vivite_oxide_powder",pixel.temp),false);
					};
					
					if(pixel.burning && ((pixel.temp + (2 * elements[pixel.element].burnTempChange)) > elements[pixel.element].tempHigh)) {
						changePixel(pixel,elements[pixel.element].burnInto,false);
						pixel.temp += 213;
					};
				};
			},
			burnTime: 8,
			burnTempChange: 213,
			burnInto: "vivite_oxide_powder",
			reactions: {
				"ice": { elem1: "vivite_oxide_powder", elem2: null, temp1: 0.2 },
				"water": { elem1: "vivite_oxide_powder", elem2: null, temp1: 0.2 },
				"steam": { elem1: "vivite_oxide_powder", elem2: null, temp1: 0.2 },
				"seltzer": { elem1: "vivite_oxide_powder", elem2: "carbon_dioxide" },
				"seltzer_ice": { elem1: "vivite_oxide_powder", elem2: "carbon_dioxide" },
				"sugar_water": { elem1: "vivite_oxide_powder", elem2: "sugar" },
				"sugar_ice": { elem1: "vivite_oxide_powder", elem2: "sugar" },
				"pool_water": { elem1: "vivite_oxide_powder", elem2: "chlorine" },
				"pool_ice": { elem1: "vivite_oxide_powder", elem2: "chlorine" },
				"salt_water": { elem1: "vivite_oxide_powder", elem2: "salt" },
				"salt_ice": { elem1: "vivite_oxide_powder", elem2: "salt" }
			},
			noResistance: true,
			tempHigh: 1725,
			stateHigh: "molten_vivite",
			category: "solids",
			state: "solid",
			density: 8471,
			hardness: 0.99613,
			breakInto: "vivite_powder",
			conduct: 0.89,
		};

		elements.smog.stateLow = ["water","dirty_water"];

		/*Object.keys(elements).filter(
			function(elem) {
				return (
					!wateroids.includes(elem) && (
						wateroids.includes(elements[elem].stateHigh) ||
						wateroids.includes(elements[elem].stateLow)
					)
				)
			}
		);*/

		function doViviteSlag(pixel,target) {
			if(!target) { //No pixel
				return false;
			};

			var newElement = target.element;
			if(newElement.includes("vivite")) { //Is vivite
				return false;
			};

			if(elements[pixel.element].reactions?.[newElement]) { //Pre-existing reaction
				return false;
			};

			if(elements[newElement].noViviteSlag || elements[pixel.element].ignore?.includes(newPixel.element)) { //Excluded
				return false;
			};

			if(elements[newElement].state == "gas" && !elements[newElement].viviteSlag) {
				return false;
			};

			changePixel(newPixel,"vivite_slag",false);
			if(Math.random() < 1/3) { changePixel(pixel,"molten_vivite_slag",false) };
			return true;
		};

		elements.molten_vivite = {
			color: ["#f7a6e5", "#fa70d1", "#f0bbf2"],
			colorOn: ["#ff63ac", "#ff21bd", "#e81af0"],
			fireColor: ["#ff66ba", "#ff85ef", "#ff99f7"],
			ignore: ["wall","heejinite","jinsoulite","haseulite","molten_heejinite","molten_jinsoulite","molten_haseulite","yvesite","molten_yvesite"],
			tick: function(pixel) {
				var info = elements[pixel.element];

				if(Math.random() < 0.022 && exposedToAir(pixel)) {
					changePixel(pixel,pixel.temp > 7315.27 ? "molten_vivite_oxide" : "vivite_oxide_powder",false)
					pixel.temp += 18;
				};			

				if(Math.random() < 0.025) {
					var fireColor = info.fireColor;
					while(fireColor instanceof Array) { fireColor = fireColor[Math.floor(Math.random() * fireColor.length)] };
					var newFire = tryCreatePixelReturn("fire",pixel.x,pixel.y-1);
					if(newFire) {
						newFire.color = pixelColorPick(newFire,fireColor);
						newFire.temp = pixel.temp;
					};
				};

				var viscosityMoveChance = ((Math.random()*100) < 100 / ((info.viscosity ?? 1) ** 0.25)); //returns true if doing m2

				var move1Spots = viscosityMoveChance ? [[-1,1],[0,1],[1,1]] : [[0,1]];

				var move1Offset = move1Spots[Math.floor(Math.random() * move1Spots.length)];

				if(!tryMove(pixel, pixel.x + move1Offset[0], pixel.y + move1Offset[1])) {
					var newPixel = pixelMap[pixel.x + move1Offset[0]]?.[pixel.y + move1Offset[1]];
					if(newPixel) {
						doViviteSlag(pixel,newPixel);
					};
					var move2Spots = [[-1,0],[1,0]];

					var move2Offset = move2Spots[Math.floor(Math.random() * move2Spots.length)];

					//console.log(info.viscosity);

					var viscosityMoveChance = ((Math.random()*100) < 100 / ((info.viscosity ?? 1) ** 0.25)); //returns true if doing m2

					if(viscosityMoveChance) {
						if(!tryMove(pixel, pixel.x + move2Offset[0], pixel.y + move2Offset[1])) {
							var newPixel = pixelMap[pixel.x + move2Offset[0]]?.[pixel.y + move2Offset[1]];
							if(newPixel) {
								doViviteSlag(pixel,newPixel);
							};
						};
					};
				};
			},
			density: 8212,
			state: "liquid",
			burnTime: 160,
			burnTempChange: 10.65,
			burnInto: "vivite_oxide_powder",
			hardness: 0.88,
			viscosity: 10000,
			breakInto: "vivite_gas",
			temp: 2000,
			tempHigh: 2256,
			stateHigh: "vivite_gas",
			tempLow: 938,
			stateLow: "vivite",
			conduct: 1,
			stain: 0.03,
			behavior: behaviors.WALL, //placeholder to prevent an auto-behaviors.MOLTEN process
		};

		elements.vivite_slag = {
			color: ["#d962b1", "#bf67b6", "#b57b9e"],
			fireColor: ["#fa5584", "#e85a70", "#e05c69", "#ed8672"],
			behavior: [
				"XX|XX|XX",
				"XX|XX|XX",
				"M2%25|M1|M2%25"
			],
			density: 9777,
			category: "solids",
			state: "solid",
			hardness: 0.71,
			breakInto: ["vivite_powder","slag"],
			tempHigh: 953,
			conduct: 0.27,
		};

		elements.vivite_oxide = {
			color: ["#f5b3c5", "#ffb0c6"],
			fireColor: ["#ff1f69", "#ff0004", "#ff006a"],
			reactions: {
				"carbon_monoxide": { elem1: "vivite", elem2: "carbon_dioxide", tempMin: 543, chance: 0.03 }
			},
			behavior: behaviors.WALL,
			reactions: {
				"ice": { elem1: "vivite_hydroxide", elem2: null, temp1: 234, tempMax: 733},
				"water": { elem1: "vivite_hydroxide", elem2: null, temp1: 234, tempMax: 733},
				"steam": { elem1: "vivite_hydroxide", elem2: null, temp1: 234, tempMax: 733},
				"seltzer": { elem1: "vivite_hydroxide", elem2: "carbon_dioxide", temp1: 234, tempMax: 733},
				"seltzer_ice": { elem1: "vivite_hydroxide", elem2: "carbon_dioxide", temp1: 234, tempMax: 733},
				"sugar_water": { elem1: "vivite_hydroxide", elem2: "sugar", temp1: 234, tempMax: 733},
				"sugar_ice": { elem1: "vivite_hydroxide", elem2: "sugar", temp1: 234, tempMax: 733},
				"pool_water": { elem1: "vivite_hydroxide", elem2: "chlorine", temp1: 234, tempMax: 733},
				"pool_ice": { elem1: "vivite_hydroxide", elem2: "chlorine", temp1: 234, tempMax: 733},
				"salt_water": { elem1: "vivite_hydroxide", elem2: "salt", temp1: 234, tempMax: 733},
				"salt_ice": { elem1: "vivite_hydroxide", elem2: "salt", temp1: 234, tempMax: 733}
			},
			density: 5135,
			category: "solids",
			state: "solid",
			hardness: 0.9983,
			breakInto: "vivite_oxide_powder",
			tempHigh: 7315.27,
		};

		elements.vivite_oxide_powder = {
			color: ["#f5b3c5", "#ffb0c6"],
			fireColor: ["#ff1f69", "#ff0004", "#ff006a"],
			behavior: behaviors.POWDER,
			reactions: {
				"carbon_monoxide": { elem1: "vivite_powder", elem2: "carbon_dioxide", tempMin: 543, chance: 0.03 }
			},
			reactions: {
				"ice": { elem1: "vivite_hydroxide", elem2: null, temp1: 234, tempMax: 733},
				"water": { elem1: "vivite_hydroxide", elem2: null, temp1: 234, tempMax: 733},
				"steam": { elem1: "vivite_hydroxide", elem2: null, temp1: 234, tempMax: 733},
				"seltzer": { elem1: "vivite_hydroxide", elem2: "carbon_dioxide", temp1: 234, tempMax: 733},
				"seltzer_ice": { elem1: "vivite_hydroxide", elem2: "carbon_dioxide", temp1: 234, tempMax: 733},
				"sugar_water": { elem1: "vivite_hydroxide", elem2: "sugar", temp1: 234, tempMax: 733},
				"sugar_ice": { elem1: "vivite_hydroxide", elem2: "sugar", temp1: 234, tempMax: 733},
				"pool_water": { elem1: "vivite_hydroxide", elem2: "chlorine", temp1: 234, tempMax: 733},
				"pool_ice": { elem1: "vivite_hydroxide", elem2: "chlorine", temp1: 234, tempMax: 733},
				"salt_water": { elem1: "vivite_hydroxide", elem2: "salt", temp1: 234, tempMax: 733},
				"salt_ice": { elem1: "vivite_hydroxide", elem2: "salt", temp1: 234, tempMax: 733}
			},
			density: 4813,
			category: "solids",
			state: "solid",
			hardness: 0.964,
			breakInto: "vivite_oxide_powder",
			tempHigh: 7315.27,
			stateHigh: "molten_vivite_oxide",
		};

		elements.vivite_hydroxide = {
			color: ["#f19cf7", "#d88aff"],
			behavior: behaviors.POWDER,
			density: 6112,
			category: "solids",
			state: "solid",
			hardness: 0.9983,
			breakInto: "vivite_hydroxide",
			tempHigh: 733,
			stateHigh: ["vivite_oxide_powder","water"],
		};

		elements.molten_vivite_oxide = {
			fireColor: ["#ff1f69", "#ff0004", "#ff006a"],
			reactions: {
				"carbon_monoxide": { elem1: "vivite_gas", elem2: "carbon_dioxide", tempMin: 543, chance: 0.03 }
			},
			density: 6113,
			hardness: 0.9993,
			breakInto: "molten_vivite_oxide",
			temp: 8000,
			tempHigh: 15500,
		};

		elements.vivite_oxide_gas = {
			reactions: {
				"carbon_monoxide": { elem1: "vivite", elem2: "carbon_dioxide", tempMin: 543, chance: 0.03 }
			},
		};

		elements.molten_vivite_slag = {
			color: ["#d44e88", "#db7258", "#eda455"],
			fireColor: ["#fa5584", "#e85a70", "#e05c69", "#ed8672"],
			density: 9213,
			hardness: 0.71,
			breakInto: ["molten_vivite","slag"],
			temp: 1100,
			conduct: 0.18,
			stain: 0.04
		};

		elements.vivite_gas = {
			color: ["#ffedfe", "#ffe0fd", "#ffd9f9", "#ffd1f0", "#ffccdf"],
			colorOn: ["#eec7fc", "#f5b1fc", "#faa2f1", "#fa93c3", "#ff99b1"],
			fireColor: ["#ff66ba", "#ff85ef", "#ff99f7"],
			tick: function(pixel) {
				if(Math.random() < 0.032 && exposedToAir(pixel)) {
					if(getEmptyMooreNeighbors(pixel).length > 4) {
						pixel.burning = true;
						pixel.burnStart = pixelTicks;
					} else {
						pixel.temp += 18;
						changePixel(pixel,pixel.temp > 15500 ? "vivite_oxide_gas" : pixel.temp > 7315.27 ? "molten_vivite_oxide" : "vivite_oxide_powder",false);
					};
				};
			},
			behavior: behaviors.GAS,
			state: "gas",
			tempLow: 2256,
			stateLow: "molten_vivite",
			burnTime: 8,
			burnTempChange: 213,
			burnInto: "vivite_oxide_powder",
			density: 18.02,
			temp: 3300,
			hardness: 1,
			conduct: 1,
		};

		runAfterLoad(function() {
			if(elements.carbon_monoxide) {
				elements.carbon_monoxide.reactions ??= {};
				elements.carbon_monoxide.reactions.vivite_oxide = { elem1: "carbon_dioxide", elem2: "vivite", tempMin: 543, chance: 0.03 };
				elements.carbon_monoxide.reactions.vivite_oxide_powder = { elem1: "carbon_dioxide", elem2: "vivite_powder", tempMin: 543, chance: 0.03 };
				elements.carbon_monoxide.reactions.molten_vivite_oxide = { elem1: "carbon_dioxide", elem2: "vivite_gas", tempMin: 543, chance: 0.03 };
				elements.carbon_monoxide.reactions.vivite_oxide_gas = { elem1: "carbon_dioxide", elem2: "vivite_gas", tempMin: 543, chance: 0.03 };
			}
		});

		runAfterLoad(function() {
			for(key in elements.water.reactions) {
				var value = JSON.parse(JSON.stringify(elements.water.reactions[key]));
				if(typeof(value.chance) === "number") {
					value.chance = Math.min(1,value.chance * 2);
				};
				if(value.elem2 === null && value.elem1 !== null) { 
					value.elem2 = value.elem1;
				};
				delete value.elem1;

				var movableJinsoulitoids = ["jinsoulite_powder","molten_jinsoulite","jinsoulite_gas"];
				for(j = 0; j < movableJinsoulitoids.length; j++) {
					var jinsoulitoid = movableJinsoulitoids[j];
					if(typeof(elements[jinsoulitoid].reactions) === "undefined") {
						elements[jinsoulitoid].reactions = {};
					};
					if(typeof(elements[jinsoulitoid].reactions[key]) === "undefined") {
						elements[jinsoulitoid].reactions[key] = value;
					};
				};
			};
		});

	//IDOL BIRTHDAY STUFF

		fakeDate = urlParams.get('fakeDate');
		shortenedTest = (urlParams.get('shortenedTest') !== null);

		idolData = {
			"08-01": [
				{member: "Seo Dahyun", color: "rgb(251,160,227)", group: "tripleS"},
				{member: "Kwak Yeonji", color: "rgb(89,116,255)", group: "tripleS"},
			],
			"23-01": {member: "Isa", color: "rgb(0,0,0)", group: "STAYC"},
			"01-02": {member: "Jihyo", color: "rgb(250,200,87)", group: "Twice"},
			"03-02": [
				{member: "Rei", color: "rgba(105,195,45)", group: "IVE"},
				{member: "Gong Yubin", color: "rgb(255,227,226)", group: "tripleS"},
			],
			"09-02": {member: "Kim Yooyeon", color: "rgb(205,102,171)", group: "tripleS"},
			"21-02": {member: "Leeseo", color: "rgb(255,240,1)", group: "IVE"},
			"10-02": {member: "Kim Lip", color: "rgb(234,2,1)", group: "Loona"},
			"02-03": {member: "Dayeon", color: "rgb", group: "Kep1er"},
			"10-03": {member: "Kotone", color: "rgb(255,246,84)", group: "tripleS"},
			"12-03": {member: "Hikaru", color:"rgb", group: "Kep1er"},
			"13-03": {member: "Sumin", color: "rgb(255,192,203)", group: "STAYC"},
			"24-03": {member: "Mina", color: "rgb(111,197,194)", group: "Twice"},
			"12-04": {member: "Jeong Hyerin", color: "rgb(142,108,255)", group: "tripleS"},
			"14-04": {member: "Yoon", color: "rgb(50,205,50)", group: "STAYC"},
			"23-04": {member: "Chaeyoung", color: "rgb(255,23,68)", group: "Twice"},
			"26-04": {member: "Chaehyun", color:"rgb", group: "Kep1er"},
			"24-05": {member: "Yves", color: "rgb(125,0,30)", group: "Loona"},
			"28-05": {member: "Dahyun", color: "rgb(255,255,255)", group: "Twice"},
			"02-06": {member: "Nien", color: "rgb(255,149,64)", group: "tripleS"},
			"04-06": {member: "Choerry", color: "rgb(92,44,146)", group: "Loona"},
			"13-06": {member: "JinSoul", color: "rgb(20,36,176)", group: "Loona"},
			"14-06": [
				{member: "Seeun", color: "rgb(135,206,235)", group: "STAYC"},
				{member: "Tzuyu", color: "rgb(2,119,189)", group: "Twice"}
			],
			"27-07": {member: "Huening Bahiyyih", color:"rgb", group: "Kep1er"},
			"01-08": {member: "Sieun", color: "rgb(255,255,255)", group: "STAYC"},
			"06-08": {member: "Yoon Seoyeon", color: "rgb(34,174,255)", group: "tripleS"},
			"12-08": {member: "Choi Yujin", color:"rgb", group: "Kep1er"},
			"18-08": {member: "HaSeul", color: "rgb(0,166,81)", group: "Loona"},
			"22-08": {member: "Yeseo", color:"rgb", group: "Kep1er"},
			"31-08": {member: "Wonyoung", color: "rgb(255,0,30)", group: "IVE"},//stay mad
			"01-09": {member: "Yujin", color: "rgb(255,57,154)", group: "IVE"},
			"22-09": {member: "Nayeon", color: "rgb(129,212,250)", group: "Twice"},
			"24-09": {member: "Gaeul", color: "rgb(0,85,168)", group: "IVE"},
			"03-10": {member: "Kim Soomin", color: "rgb(236,138,165)", group: "tripleS"},
			"13-10": [
				{member: "Kim Nakyoung", color: "rgb(101,153,164)", group: "tripleS"},
				{member: "Park Sohyun", color: "rgb(19,34,182)", group: "tripleS"},
			],
			"19-10": {member: "HeeJin", color: "rgb(255,0,146)", group: "Loona"},
			"20-10": {member: "Chuu", color: "rgb(246,144,126)", group: "Loona"},
			"24-10": {member: "Lee Jiwoo", color: "rgb(255,249,36)", group: "tripleS"},
			"01-11": {member: "Jeongyeon", color: "rgb(188,215,118)", group: "Twice"},
			"09-11": {member: "Momo", color: "rgb(248,207,215)", group: "Twice"},
			"11-11": {member: "YeoJin", color: "rgb(244,111,31)", group: "Loona"},
			"12-11": {member: "Xiaoting", color:"rgb", group: "Kep1er"},
			"13-11": {member: "Hyeju", color: "rgb(143,143,143)", group: "Loona"},
			"15-11": {member: "HyunJin", color: "rgb(255,204,0)", group: "Loona"},
			"19-11": {member: "Go Won", color: "rgb(48,195,156)", group: "Loona"},
			"21-11": {member: "Liz", color: "rgb(0,195,245)", group: "IVE"},
			"04-12": {member: "Kim Chaeyeon", color: "rgb(141,191,65)", group: "tripleS"},
			"09-12": [
				{member: "ViVi", color: "rgb(255,152,180)", group: "Loona"},
				{member: "J", color: "rgb(255,0,0)", group: "STAYC"}
			],
			"16-12": {member: "Mashiro", color:"rgb", group: "Kep1er"},
			"20-12": {member: "Kaede", color: "rgb(255,201,53)", group: "tripleS"},
			"27-12": {member: "Youngeun", color:"rgb", group: "Kep1er"},
			"29-12": {member: "Sana", color: "rgb(159,168,218)", group: "Twice"}
		};

		var chaos = [];
		for(date in idolData) {
			if(date == "chaos") { continue };
			if(!(idolData[date] instanceof Array)) { idolData[date] = [idolData[date]] }; //array wrap

			chaos = chaos.concat(idolData[date]);
		};
		idolData.chaos = chaos;

		var february10Override = false;

		function getDayMonth() {
			var d = february10Override ? new Date(1549800000000) : new Date();
			var month = (d.getMonth()+1).toString();
			if(month.length == 1) { month = "0" + month };
			var day = d.getDate().toString();
			if(day.length == 1) { day = "0" + day };
			var dayMonth = day + "-" + month;
			return (fakeDate === null ? dayMonth : fakeDate);
		}

		function registerElemClick(elementName,memberDataIndex) {
			var dateData = idolData[getDayMonth()];
			if(!dateData) {
				alert("No birthday data here");
				return false;
			};

			var memberData = dateData[memberDataIndex];

			var fakeDateMessage = "";
			if(fakeDate !== null) {
				fakeDateMessage += "(Fake date) ";
			};

			var shortenedTestMessage = "";
			if(shortenedTest) {
				shortenedTestMessage += "(Shortened) ";
			};

			memberName = memberData.member;

			if(clickedElements[memberName][elementName] === false) {
				clickedElements[memberName][elementName] = true;
			};

			if(evaluateTheClickedElements(memberName)) {
				alert(
					`You have clicked on all ${Object.keys(clickedElements[memberName]).length} birthday messages spread throughout some of the elements.`
					+ "\n" +
					`Member: ${fakeDateMessage}${shortenedTestMessage}${memberName}. Stan ${memberData.group}!`
				);
			};

			return typeof(clickedElements[memberName][elementName]) === "boolean";
		};

		function evaluateTheClickedElements(memberName) {
			var done = true;
			for(element in clickedElements[memberName]) {
				done = done && clickedElements[memberName][element];
			};
			return done;
		};

		function highlightButton(element,color,blurRadius="15px",spreadRadius="5px") {
			var button = document.getElementById(`elementButton-${element}`);
			if(button == null) {
				throw new Error(`Nonexistent button for ${element}`)
			};
			if(typeof(blurRadius) == "number") { blurRadius = blurRadius + "px" };
			if(typeof(spreadRadius) == "number") { spreadRadius = spreadRadius + "px" };
			document.getElementById(`elementButton-${element}`).style["-webkit-box-shadow"] = `0px 0px ${blurRadius} ${spreadRadius} ${color}`;
			document.getElementById(`elementButton-${element}`).style["box-shadow"] = `0px 0px ${blurRadius} ${spreadRadius} ${color}`;
		};

		clickedElements = {};

		runAfterAutogen(function() {

			var alreadyHighlightedElements = [];

			var changingDescElements = ["distance_display","find_toggle","prop","number_adjuster","replace","alt_replace","alt_alt_replace","change","alt_change","alt_alt_change"];

			var blacklist = ["toxin","poison","blood","cancer","rotten_meat","frozen_rotten_meat","zombie_blood","plague","stench","infection","acid","acid_gas","rot","shit","shit_gravel","poo","dioxin","lean","cyanide"];

			var dayMonth = getDayMonth();

			var baseArray = ["heejinite","heejinite_powder","molten_heejinite","heejinite_gas","haseulite","haseulite_powder","molten_haseulite","haseulite_gas","jinsoulite","jinsoulite_powder","molten_jinsoulite","jinsoulite_gas","haseulite_vent","loona","loona_gravel","molten_loona"];

			var loonaTheHTML = "";

			var randomElementSets = {};

			if(idolData[dayMonth]) {
				var data = idolData[dayMonth];

				for(var memberIndex = 0; memberIndex < data.length; memberIndex++) {
					var member = data[memberIndex].member;
					randomElementSets[member] = Object.keys(elements).filter(function(e) {
						var cat = elements[e].category;
						if(cat == undefined) { cat = "other" };
						cat = cat.toLowerCase();
						return (
							cat !== "clouds" &&
							cat !== "auto creepers" &&
							cat !== "auto_bombs" &&
							cat !== "auto_fey" &&
							cat !== "spouts" &&
							cat !== "singularities" &&
							cat !== "random" &&
							cat !== "weapons" &&
							cat !== "idk" &&
							cat !== "corruption" &&
							cat !== "radioactive" &&
							cat !== "piss" &&
							cat !== "shit" &&
							cat !== "vomit" &&
							cat !== "cum" &&
							!e.includes("head") &&
							(!e.includes("body") || e.includes("antibody")) &&
							!cat.includes("random") &&
							!cat.includes("udstone") &&
							!elements[e].nocheer &&
							!changingDescElements.includes(e) &&
							!blacklist.includes(e) &&
							!alreadyHighlightedElements.includes(e) &&
							!elements[e].hidden && 
							!baseArray.includes(e)
						);
					}); shuffleArray(randomElementSets[member]); randomElementSets[member] = randomElementSets[member].slice(0,shortenedTest ? 2 : 12);

					clickedElements[member] = {};

					alreadyHighlightedElements = alreadyHighlightedElements.concat(randomElementSets[member]);

					for(i = 0; i < randomElementSets[member].length; i++) {
						var elemName = randomElementSets[member][i];
						clickedElements[member][elemName] = false;
					};

					runAfterButtons(function() {
						var data = idolData[getDayMonth()];
						//console.log(data);
						for(var memberIndex = 0; memberIndex < data.length; memberIndex++) {
							var member = data[memberIndex].member;
							//console.log(member, data[memberIndex]);
							var elems = Object.keys(clickedElements[member]);
							//console.log(elems);
							for(j = 0; j < elems.length; j++) {
								var name = elems[j];
								var color = data[memberIndex].color;
								if(data.gradient) {
									color = "rgb(255,255,255)";
								};
								//console.log(name);
								//console.log(color);
								color == "rgb(0,0,0)" ? highlightButton(name,color,15,12) : highlightButton(name,color,7,2);
							};
						};
					});

					var funnyElements = ["heejinite","heejinite_powder","molten_heejinite","heejinite_gas","haseulite","haseulite_powder","molten_haseulite","haseulite_gas","jinsoulite","jinsoulite_powder","molten_jinsoulite","jinsoulite_gas","haseulite_vent","loona","loona_gravel","molten_loona"].concat(randomElementSets[member]);

					//console.log(member, funnyElements);

					for(element in funnyElements) {
						var elemName = funnyElements[element];
						var info = elements[elemName];
						if(!info) { console.log(element) };
						var memberData = data[memberIndex];
						if(typeof(info.desc) === "undefined") {
							info.desc = ""
						} else {
							info.desc += "<br/>"
						};

						var normalDesc = baseArray.includes(elemName);

						loonaTheHTML = normalDesc ? `<span style="${memberData.gradient ? ('background: ' + memberData.color + '; background-clip: text; -webkit-background-clip: text; text-fill-color: transparent; -webkit-text-fill-color: transparent;') : ('color:' + memberData.color)}">Happy birthday, ${memberData.member}!</span>` : `<em style="${memberData.gradient ? ('background: ' + memberData.color + '; background-clip: text; -webkit-background-clip: text; text-fill-color: transparent; -webkit-text-fill-color: transparent;') : ('color:' + memberData.color)}" onclick=registerElemClick("${elemName}",${memberIndex})>Happy birthday, ${memberData.member}!</em>`;

						info.desc += loonaTheHTML;
					};
				};
			};

		});

	//OTHER FICTIONAL MATERIALS ##

		function splitRgbColor(color) {
			var colorTempArray = color.split(",")
			var r = colorTempArray[0].split(",")[0].substring(4)
			var g = colorTempArray[1]
			var b = colorTempArray[2].slice(0,-1)
			return [r,g,b]
		}

		elements.laetium = {
			color: "#f57f87",
			tempHigh: 2950,
			hardness: 0.87252,
			density: 6719,
			conduct: 4.7E210,
			behavior: behaviors.WALL,
			state: "solid",
			category: "solids",
			reactions: {
				sand: {temp1: 5}
			}
		}

		elements.molten_laetium = {
			color: ['#ff9f44', '#ff7f44', '#ff5f00'],
			behavior: behaviors.MOLTEN,
			reactions: {
				"ash": { "elem1": null, "elem2": "laetium_slag"},
				"dust": { "elem1": null, "elem2": "laetium_slag"},
				"magma": { "elem1": null, "elem2": "laetium_slag"},
				"sand": { temp1: 5 }
			},
			density: 6100,
			temp: 3000,
			tempLow: 2944,
			stateLow: "laetium",
			tempHigh: 5837,
			stateHigh: "vaporized_laetium",
			viscosity: 1.517,
			hidden: true,
			state: "liquid",
			category: "molten",
		}

		elements.vaporized_laetium = {
			color: ['#efdf54', '#efbf54', '#efaf10'],
			behavior: behaviors.GAS,
			reactions: {
				"sand": { temp1: 5 }
			},
			density: 49,
			temp: 6000,
			tempLow: 5837,
			stateLow: "molten_laetium",
			viscosity: 0.1,
			hidden: true,
			state: "gas",
			category: "gases",
		}

		elements.atisanium = {
			color: "#8dadb8",
			conduct: 0.87,
			colorOn: ["#ff00ff", "#e600e6", "#a300cc", "#ce07e8"],
			tempLow: -44,
			stateLow: "liquid_atisanium",
			density: 1.225,
			behavior: [
				"M1 AND M1|M1 AND M1|M1 AND M1",
				"M1 AND M1|XX|M1 AND M1",
				"M1 AND M1|M1 AND M1|M1 AND M1"
			],
			state: "gas",
			category: "gases"
		}

		elements.liquid_atisanium = {
			color: "#3f878a",
			conduct: 0.96,
			colorOn: ["#8307eb", "#8c00ff", "#9617ff", "#a02eff"],
			tempHigh: -45,
			stateHigh: "atisanium",
			tempLow: -214,
			stateLow: "alpha_atisanium",
			temp: -100,
			density: 1594.1,
			behavior: behaviors.LIQUID,
			state: "liquid",
			category: "liquids",
			tick: function(pixel) {
				var moveSpotsA = [[-1,1],[0,1],[1,1]]
				var moveSpotsB = [[-1,0],[1,0]]
				var msaChoice = randomChoice(moveSpotsA)
				var msbChoice = randomChoice(moveSpotsB)
				if(isEmpty(msaChoice[0],pixel.y+msaChoice[1],true)) {
					if(!tryMove(pixel,pixel.x+msaChoice[0],pixel.y+msaChoice[1])) {
						tryMove(pixel,pixel.x+msbChoice[0],pixel.y+msbChoice[1])
					}
				}
				if(pixel.chargeCD) {
					if(pixel.chargeCD > 2) {
						pixel.chargeCD = 2
					}
				}
				if(pixel.chargeCD) {
					if(pixel.chargeCD > 1) {
						if(Math.random() < 0.2) {
							pixel.chargeCD = 1
						}
					}
				}
			},
		}

		elements.alpha_atisanium = {
			color: "#00382a",
			conduct: 0.987,
			colorOn: ["#3700ff", "#6820f7", "#4b15bf"],
			tempHigh: -213,
			stateHigh: "liquid_atisanium",
			tempLow: -261,
			stateLow: "beta_atisanium",
			temp: -240,
			density: 5129.5,
			behavior: behaviors.WALL,
			state: "solid",
			category: "solids",
			tick: function(pixel) {
				if(pixel.chargeCD) {
					if(pixel.chargeCD > 2) {
						pixel.chargeCD = 2
					}
				}
				if(pixel.chargeCD) {
					if(pixel.chargeCD > 1) {
						if(Math.random() < 0.4) {
							pixel.chargeCD = 1
						}
					}
				}
			},
		}

		elements.beta_atisanium = {
			color: "#750e35",
			conduct: Infinity, //This is where I would make it a superconductor.
			colorOn: ["#0f0021", "#120324", "#4b106e", "#a6058e", "#42043a"], //pretend this is UV becoming more pronounced
			tempHigh: -260,
			stateHigh: "alpha_atisanium",
			temp: -270,
			density: 11129.5,
			behavior: behaviors.WALL,
			state: "solid",
			category: "solids",
			tick: function(pixel) {
				if(pixel.chargeCD) {
					if(pixel.chargeCD > 3) {
						pixel.chargeCD = 3
					}
				}
			},
		}

		elements.polusium = {
			color: "#dedc9e",
			tempHigh: 1213,
			hardness: 0.921952,
			density: 4113,
			conduct: 0.98,
			behavior: behaviors.WALL,
			state: "solid",
			category: "solids",
			tick: function(pixel) {
				var emptyNeighbors = [];
				for(i = 0; i < adjacentCoords.length; i++) {
					if(isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
						emptyNeighbors.push(adjacentCoords[i]);
					};
				};
				if(Math.random() < 0.002) {
					if(emptyNeighbors.length > 0) {
						var randomEmptyNeighbor = emptyNeighbors[Math.floor(Math.random() * emptyNeighbors.length)];
						changePixel(pixel,"polusium_oxide")
						createPixel("nitrogen",pixel.x+randomEmptyNeighbor[0],pixel.y+randomEmptyNeighbor[1])
					};
				};
			},
			reactions: {
				water: { elem1: "polusium_oxide", elem2: ["water","water","water","water","hydrogen"], chance: 0.006 },
				salt_water: { elem1: "polusium_oxide", elem2: ["salt_water","salt_water","salt_water","salt","hydrogen"], chance: 0.012 },
				bleach: { elem1: "polusium_oxide", chance: 0.02 }
			},
		}

		elements.molten_polusium = {
			tick: function(pixel) {
				neighbors = [[-1,0],[0,-1],[1,0],[0,1]]
				for(i = 0; i < neighbors.length; i++) {
					if(isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],false)) {
						if(Math.random() < 0.004) {
							changePixel(pixel,"molten_polusium_oxide")
						}
					}
					if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
						if(pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]].element == "salt_water") {
							if(Math.random() < 0.024) {
								changePixel(pixel,"molten_polusium_oxide")
							}
						}
					}
				}
			},
			density: 3410,
			temp: 1300,
			tempLow: 1212,
			stateLow: "polusium",
			tempHigh: 3110,
			stateHigh: "vaporized_polusium",
			viscosity: 13,
		}

		elements.vaporized_polusium = {
			color: ["#fdffd1", "#edf2cb", "#fcfac7"],
			behavior: behaviors.GAS,
			tick: function(pixel) {
				neighbors = [[-1,0],[0,-1],[1,0],[0,1]]
				for(i = 0; i < neighbors.length; i++) {
					if(isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],false)) {
						if(Math.random() < 0.015) {
							changePixel(pixel,"vaporized_polusium_oxide")
						}
					}
					if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
						if(pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]].element == "salt_water") {
							if(Math.random() < 0.06) {
								changePixel(pixel,"vaporized_polusium_oxide")
							}
						}
					}
				}
			},
			density: 21,
			temp: 3200,
			tempLow: 3109,
			stateLow: "molten_polusium",
			viscosity: 0.2,
			hidden: true,
			state: "gas",
			category: "gases",
		}

		elements.polusium_oxide = {
			color: "#a9b594",
			tempHigh: 1300,
			hardness: 0.511952,
			density: 3717,
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			viscosity: 13,
		}

		elements.molten_polusium_oxide = {
			temp: 1350,
			tempHigh: 1400,
			stateHigh: "vaporized_polusium_oxide",
			density: 2917,
		}

		elements.vaporized_polusium_oxide = {
			color: "#faffc7",
			temp: 1500,
			tempLow: 1399,
			stateLow: "molten_polusium_oxide",
			density: 10,
			behavior: behaviors.GAS,
		}

		runAfterLoad(function() {
			elements.laetium_slag = JSON.parse(JSON.stringify(elements.slag))
			elements.laetium_slag.color = ['#a05c5a', '#af6967', '#b06d6d', '#ae6b6c', '#b67a7a']
			elements.laetium_slag.tempHigh = 2950
			elements.laetium_slag.stateHigh = ["molten_slag","molten_laetium"]
		});

	//MORE BROKEN FORMS ##

		elements.loose_straw = {
			color: ["#F9E3A1","#93734E","#C7AA83"],
			behavior: behaviors.POWDER,
			tempHigh: 380,
			stateHigh: "fire",
			burn: 80,
			burnTime: 30,
			burnInto: "ash",
			category: "powders",
			state: "solid",
			density: 47.5,
			hidden: true,
		},

		elements.straw.breakInto = "loose_straw"

		elements.plastic_scrap = {
			color: "#c3cccc",
			behavior: behaviors.POWDER,
			category: "powders",
			tempHigh: 200,
			stateHigh: "molten_plastic",
			burn: 15,
			burnTime: 350,
			burnInto: "dioxin",
			state: "solid",
			density: 952,
			hidden: true,
		},

		elements.plastic.breakInto = ["plastic_scrap","dioxin"]

		elements.insulation.breakInto = ["plastic_scrap","dioxin","glass_shard"]

		elements.copper_scrap = {
			color: ["#B96242","#CE5332","#D77045","#994222","#AE3312","#B75025","#A95232","#BE4322","#C76035"],
			behavior: [
				"XX|XX|XX",
				"XX|CH:oxidized_copper%0.005|XX",
				"M2|M1|M2",
			],
			reactions: {
				"water": { "elem1":"oxidized_copper", chance:0.0035 },
				"salt_water": { "elem1":"oxidized_copper", chance:0.006 },
				"dirty_water": { "elem1":"oxidized_copper", chance:0.045 },
				"sugar_water": { "elem1":"oxidized_copper", chance:0.0045 }
			},
			category: "powders",
			tempHigh: 1085,
			stateHigh: "molten_copper",
			density: 5960,
			conduct: 0.90,
			hidden: true,
		},

		elements.oxidized_copper_scrap = {
			color: ["#507565","#52665A","#618374","#305545","#32463A","#416354","#406555","#42564A","#517364"],
			behavior: behaviors.POWDER,
			category: "powders",
			hidden: true,
			tempHigh: 1085,
			stateHigh: "molten_copper",
			density: 5960,
			conduct: 0.80,
			hidden: true,
		}

		elements.copper.breakInto = ["copper_scrap","copper_scrap","copper_scrap","copper_scrap","copper_scrap","oxidized_copper_scrap"]

		elements.dry_ice.breakInto = "carbon_dioxide"

		elements.frozen_ketchup.breakInto = "ketchup_snow"
		elements.frozen_poisoned_ketchup.breakInto = "poisoned_ketchup_snow"

		regularShinyThingArray = ["iron", "zinc", "tin", "nickel", "silver", "aluminum", "lead", "tungsten", "brass", "bronze", "sterling", "steel", "white_gold", "blue_gold", "rose_gold", "red_gold", "solder", "gold", "pyrite", "mythril", "mithril_mythril_alloy", "titanium", "ilitium", "mithril", "beryllium", "boron", "ruthenium", "rhodium", "palladium", "rhenium", "osmium", "iridium", "platinum", "frozen_mercury", "lithium", "niobium", "ketchup_metal", "ketchup_gold", "tungstensteel", "densinium", "mithril", "signalum", "laetium", "kurshunjukium", "zirconium", "jinsoulite"];

		elements.nitrogen_snow = {
			color: "#efefef",
			behavior: behaviors.POWDER,
			category: "solids",
			temp: -259.86,
			tempHigh: -209.86,
			stateHigh: "liquid_nitrogen",
			state: "solid",
			density: 850,
			hidden: true,
		}

		elements.nitrogen_ice.breakInto = "nitrogen_snow"

		runAfterLoad(function() {
			for(i = 0; i < regularShinyThingArray.length; i++) {
				var thing = regularShinyThingArray[i];
				if(typeof(elements[thing]) == "object") {
					if(typeof(elements[thing]?.breakInto) == "undefined") {
						elements[`${thing}_scrap`] = {
							color: gravelizeToHex(elements[thing].color),
							behavior: behaviors.POWDER,
							tempHigh: elements[thing].tempHigh,
							stateHigh: thing,
							category: "powders",
							hidden: true,
							density: elements[thing].density * 0.09,
							conduct: elements[thing].conduct * 0.4,
							movable: true,
						};
						if(elements[thing].reactions) {
							elements[`${thing}_scrap`].reactions = elements[thing].reactions;
						};
						elements[thing].breakInto = `${thing}_scrap`;
					};
					elements[thing].cutInto = elements[thing].breakInto
				}
			};

			elements.acid.ignore.push("densinium_scrap")
			elements.lithium_scrap.tick = function(pixel) {
				tryTarnish(pixel,"lithium_oxide",0.021) 
				if(pixel.temp >= 178) {
					pixel.burning = true; 
					pixel.burnStart = pixelTicks; 
				};
			};
			elements.laetium_scrap.reactions.sand = { temp1: 7 }
		});

	//SOIL AND ROCKS, WORLDGEN PRESETS, AND MINERALS (the_ground.js and friends) ##

		/*
			TODO:
			Soils
			More sedimentary rocks
			Metamorphic rocks
				Ersatz pressure
			Merge crimson?
			Proper classification of limestone within these code comments
		*/

		//Variables

			eLists.WHL = "water,salt_water,sugar_water,dirty_water,swamp_water,heavy_water,radioactive_water,crimwater,pure_water,chilly_water,honey,magma"

			var vitreousFelsicName = "obsidian";
			var vitreousInterfelsicName = "dacidian";
			var vitreousIntermediateName = "diodian";
			var vitreousMaficName = "basidian";
			var vitreousUltramaficName = "komatidian";

			var sandSimplification = ["gravel","granite_gravel","granodiorite_gravel","diorite_gravel","basalt_gravel","peridotite_gravel","rhyolite_gravel","dacite_gravel","andesite_gravel","komatiite_gravel","pumice_gravel","intermediate_pumice_gravel","scoria_gravel","mafic_scoria_gravel","ultramafic_scoria_gravel", "dacidian_shard", "andesidian_shard", "basalidian_shard", "komatidian_shard"];

			var rocks = [ "granite",  "granodiorite",  "diorite",  "rock",  "peridotite",   "rhyolite",  "dacite",  "andesite",  "basalt",  "komatiite",   "pumice",  "intermediate_pumice",  "scoria",  "mafic_scoria",  "ultramafic_scoria",   "obsidian",  vitreousInterfelsicName,  vitreousIntermediateName,  vitreousMaficName,  vitreousUltramaficName];

			var gravels = [ "granite_gravel",  "granodiorite_gravel",  "diorite_gravel",  "gravel",  "peridotite_gravel",   "rhyolite_gravel",  "dacite_gravel",  "andesite_gravel",  "basalt_gravel",  "komatiite_gravel",   "pumice_gravel",  "intermediate_pumice_gravel",  "scoria_gravel",  "mafic_scoria_gravel",  "ultramafic_scoria_gravel",   "obsidian_shard",  "dacidian_shard",  "andesidian_shard",  "basalidian_shard",  "komatidian_shard" ];

		//Functions

			//Stepped rainbow colors (used for rainbow earth)

				function makeRegularRainbow(steps,s,l,outputFormat="rgb") {
					var hslArray = [];
					var divisionSize = 360 / steps;
					for(i = 0; i < 360; i += divisionSize) {
						hslArray.push({h: i, s: s, l: l});
					};
					return hslArray.map(x => convertHslObjects(x,outputFormat));
				};

				crimsonObject = {
					grass: "crimson_grass",
					ice: "red_ice",
					water: "crimwater",
					snow: "crimsnow",
					packed_snow: "crimsnow",
					vine: "crimson_vine",
					fish: "vicious_goldfish",
					sapling: "shadewood_sapling",
					sandy_water: "crimsandy_water",
					muddy_water: "crimmuddy_water"
				};

				crimRate = 0.001

			//Crimson spreaders

				function grassSpread(pixel,dirt,grass,chance) {
					pixel.dirtArray = [] //initialize dirt neighbor list
					for (i = -2; i < 3; i++) { //iterate around
						for (j = -2; j < 3; j++) {
							if (!isEmpty(pixel.x+i,pixel.y+j,true)) { //check for a pixel to see if it's dirt
								if(Array.isArray(dirt)) {
									if(dirt.includes(pixelMap[pixel.x+i][pixel.y+j].element)) { //see if it's dirt
										if(!includesArray(pixel.dirtArray,[i,j])) { //avoid duplicate dirt entry
											pixel.dirtArray.push([i,j]) //store dirt
										}
									}
								} else {
									if(pixelMap[pixel.x+i][pixel.y+j].element == dirt) { //see if it's dirt
										if(!includesArray(pixel.dirtArray,[i,j])) { //avoid duplicate dirt entry
											pixel.dirtArray.push([i,j]) //store dirt
										}
									}
								}
							}
						}
					}
					for (k = 0; k < pixel.dirtArray.length; k++) { //iterate through dirt list
						if(Math.random() < chance) { //random chance
							if(isEmpty(pixel.x+pixel.dirtArray[k][0],pixel.y+pixel.dirtArray[k][1]-1)) { //check for empty space to grow grass
								createPixel(grass,pixel.x+pixel.dirtArray[k][0],pixel.y+pixel.dirtArray[k][1]-1) //place grass above dirt
							}
						}
					}
				}

				function crimSpread(pixel) {
					for (let i = -2; i < 3; i++) {
						for (let j = -2; j < 3; j++) {
							if (!isEmpty(pixel.x+j,pixel.y+i,true)) {
								var destPixel = pixelMap[pixel.x+j][pixel.y+i];
								var elementToCheck = destPixel.element;
								if(Math.random() < crimRate) {
									if(crimsonObject[elementToCheck]) {
										changePixel(destPixel,crimsonObject[elementToCheck]);
									};
									grassSpread(pixel,["dirt","crimsoil","rainbow_dirt"],"crimson_grass",0.5);
								};
							};
						};
					};
				};

			//Nellfire code (used for the obvious)

				function doNellfire(pixel) {
					var info = elements[pixel.element];
					if((info.nellfireImmune && info.nellfireImmune !== "torch") && pixel.nellburn) {
						delete pixel.nellburn;
						delete pixel.nellburnStart;
						return;
					};
					if (pixel.nellburn) { // Burning
						pixel.nellburnStart ??= pixelTicks;
						var nellburnTempChange = info.nellburnTempChange ?? 1;
						var fire = info.nellFireElement === undefined ? "nellfire" : info.nellFireElement; //allow null but disallow undefined
						//console.log(info.nellFireElement,fire);
						while(fire instanceof Array) {
							fire = fire[Math.floor(Math.random()*fire.length)];
						};
						var nellFireTemp = info.nellFireSpawnTemp ?? pixel.temp;
						var nellFireChance = info.nellFireSpawnChance ?? 20;

						pixel.temp += nellburnTempChange ?? 4;
						pixelTempCheck(pixel);

						for (var i = 0; i < adjacentCoords.length; i++) { // Burn adjacent pixels
							var x = pixel.x+adjacentCoords[i][0];
							var y = pixel.y+adjacentCoords[i][1];
							if (!isEmpty(x,y,true)) {
								var newPixel = pixelMap[x][y];
								var newInfo = elements[newPixel.element];
								var spreadChance = newInfo.nellburn ?? 15
								if (spreadChance && !newPixel.nellburn) {
									if (Math.floor(Math.random()*100) < spreadChance) {
										newPixel.nellburn = true;
										newPixel.nellburnStart = pixelTicks;
									}
								}
							}
						}

						if (info.nellfireImmune !== "torch" && (pixelTicks - pixel.nellburnStart > (info.nellburnTime || 150)) && Math.floor(Math.random()*100)<(info.nellburn || 25)) {
							var burnInto = info.nellburnInto;
							//console.log(burnInto);
							if(burnInto === undefined) { burnInto = nellburnObject[pixel.element] };
							//console.log(burnInto);
							if(burnInto === undefined) { burnInto = [null,"nell_ash"] };
							//console.log(burnInto);
							while(burnInto instanceof Array) {
								burnInto = burnInto[Math.floor(Math.random()*burnInto.length)];
							};
							//console.log(burnInto);
							if(burnInto == null) { deletePixel(pixel.x,pixel.y); return } else { changePixel(pixel,burnInto,burnInto !== "smoke") };
							if(!elements[burnInto].nellBurningWhenConverted) {
								delete pixel.nellburn;
								delete pixel.nellburnStart;
							};
							//console.log("ass");
							pixel.temp = nellFireTemp;
							if (info.nellFireColor != undefined && burnInto == "nellfire") {
								pixel.color = pixelColorPick(pixel,info.nellFireColor);
							}
							else {
								pixel.color = pixelColorPick(pixel)
							}
						}
						else if (Math.floor(Math.random()*100)<nellFireChance && !nellfireSpawnBlacklist.includes(pixel.element)) { // Spawn fire
							//console.log(fire);
							if (isEmpty(pixel.x,pixel.y-1)) {
								if(fire !== null) {
									createPixel(fire,pixel.x,pixel.y-1);
									pixelMap[pixel.x][pixel.y-1].temp = nellFireTemp;
									if (info.nellFireColor != undefined) {
										pixelMap[pixel.x][pixel.y-1].color = pixelColorPick(pixelMap[pixel.x][pixel.y-1],info.nellFireColor);
									};
								};
							}
							// same for below if top is blocked
							else if (isEmpty(pixel.x,pixel.y+1)) {
								if(fire !== null) {
									createPixel(fire,pixel.x,pixel.y+1);
									pixelMap[pixel.x][pixel.y+1].temp = nellFireTemp;
									if (info.nellFireColor != undefined) {
										pixelMap[pixel.x][pixel.y+1].color = pixelColorPick(pixelMap[pixel.x][pixel.y+1],info.nellFireColor);
									};
								};
							}
						}
					}
				}

				//Makes it run

					everyTick(function() {
						if(paused) { return };
						var nellfirePixels = currentPixels.filter(function(pixel) { return pixel.nellburn });
						for(var pixel in nellfirePixels) {
							doNellfire(nellfirePixels[pixel]);
						};
					});

			//Basically the entire hot_rocks.js code

				var rocksSandsAndSoilsToGiveHotForms = [];

				function hotRockFunction() {

					function hotData2Switch(data2) {
						switch(data2) {
							case "igneous_rock":
								return "hot_igneous_rock";
							case "solid_igneous_rock":
								return "hot_solid_igneous_rock";
							case "particulate":
								return "hot_particulate";
							case "sedimentary_rock":
								return "hot_sedimentary_rock";
							case "igneous_gravel":
								return "hot_igneous_gravel";
							default:
								return "hot_" + data2;
						};
					};

					behaviors.HOT_POWDER = [
						"XX|CR:fire%0.5|XX",
						"XX|XX|XX",
						"M2|M1|M2"
					];

					behaviors.HOT_STURDYPOWDER = [
						"XX|CR:fire%0.5|XX",
						"XX|XX|XX",
						"XX|M1|XX"
					];

					behaviors.HOT_WALL = [
						"XX|CR:fire%0.1|XX",
						"CR:fire%0.1|XX|CR:fire%0.1",
						"XX|CR:fire%0.1|XX"
					];

					behaviors.HOT_SUPPORT = [
						"XX|CR:fire%0.1|XX",
						"SP AND CR:fire%0.1|XX|SP AND CR:fire%0.1",
						"XX|M1|XX"
					];

					//console.log(rocksSandsAndSoilsToGiveHotForms)
					for(j = 0; j < rocksSandsAndSoilsToGiveHotForms.length; j++) {
						var rockName = rocksSandsAndSoilsToGiveHotForms[j];
						var rockInfo = elements[rockName];
						if(!rockInfo) {
							console.error(`${rockName}`);
							continue;
						};
						var rockData = rockInfo._data ?? ["error","error","hot_unknown"];
						var newName = rockName.startsWith("dry_") ? rockName.replace("dry_","hot_") : "hot_" + rockName;
						//console.log(rockInfo.stateHigh);
						elements[newName] = {
							color: redHotColorgen(rockInfo.color,"hex"),
							behavior: function() {
								switch((rockInfo.behavior ?? "undefined").toString()) {
									case (behaviors.WALL.toString()):
										return behaviors.HOT_WALL;
									case (behaviors.STURDYPOWDER.toString()):
										return behaviors.HOT_STURDYPOWDER;
									case (behaviors.POWDER.toString()):
										return behaviors.HOT_POWDER;
									case (behaviors.SUPPORT.toString()):
										return behaviors.HOT_SUPPORT;
									default:
										console.log(`Hot rock generation: Unknown base behavior for ${rockName}, defaulting to hot powder`);
										return behaviors.HOT_POWDER
								}
							}(),
							category: "hot rock",
							state: "solid",
							stateHigh: rockName == "soilstone" ? "molten_dirt" : rockInfo.stateHigh,
							temp: Math.min(rockInfo.tempHigh - 50,850),
							tempHigh: rockInfo.tempHigh,
							tempLow: Math.min(rockInfo.tempHigh - 100,800),
							stateLow: rockName,
							density: rockInfo.density * 0.9,
							hardness: rockInfo.density * 0.85,
							//breakInto: newName + "_gravel",
							_data: [rockData[0], rockData[1], hotData2Switch(rockData[2])],
						};

						//console.log([elements[rockName].tempHigh,elements[rockName].stateHigh]);
						//console.log([elements[newName].tempLow,elements[newName].stateLow])

						if(rockName == "basalt") {
							elements[newName].behavior = [
								"XX|CR:fire%0.5|XX",
								"XX|XX|XX",
								"XX|M1|XX"
							]
						};

						//console.log(j);

						if(rockInfo.nellfireImmune) {
							elements[newName].nellfireImmune = true;
						};

						elements[rockName].tempHigh = Math.min(rockInfo.tempHigh - 100,800);
						elements[rockName].stateHigh = newName;
						if(rockInfo._data[2] == "igneous_rock" && elements[newName + "_gravel"]) {
							elements[newName].stateHigh = newName + "_gravel";
						};
					};

					elements.dirt.tempHigh = 100;
					elements.dirt.stateHigh = "dry_dirt";
				};

			//Star world matter function

				var stellarPlasmaSpreadWhitelist = ["stellar_plasma","liquid_stellar_plasma","liquid_degenerate_neutronium","gaseous_degenerate_neutronium","neutron_star"];

				function starColor(pixel) {
					if (pixel.temp < 0) { pixel.color = pixelColorPick(pixel,"#615e5e"); var c=0 }
					else if (pixel.temp < 200) { pixel.color = pixelColorPick(pixel,"#6e4c4b"); var c=0 }
					else if (pixel.temp < 400) { pixel.color = pixelColorPick(pixel,"#944340"); var c=0.00003 }
					else if (pixel.temp < 650) { pixel.color = pixelColorPick(pixel,"#d14c47"); var c=0.0001 }
					else if (pixel.temp < 900) { pixel.color = pixelColorPick(pixel,"#e35b56"); var c=0.0004 }
					else if (pixel.temp < 1300) { pixel.color = pixelColorPick(pixel,"#eb6a6a"); var c=0.001 }
					else if (pixel.temp < 1500) { pixel.color = pixelColorPick(pixel,"#f27e7e"); var c=0.0025 }
					else if (pixel.temp < 1700) { pixel.color = pixelColorPick(pixel,"#f58e8e"); var c=0.004 }
					else if (pixel.temp < 2400) { pixel.color = pixelColorPick(pixel,"#f59a9a"); var c=0.007 }
					else if (pixel.temp < 3000) { pixel.color = pixelColorPick(pixel,"#faaaaa"); var c=0.01 }
					else if (pixel.temp < 3600) { pixel.color = pixelColorPick(pixel,"#ffbdbd"); var c=0.015 }
					else if (pixel.temp < 5000) { pixel.color = pixelColorPick(pixel,"#ffd5bd"); var c=0.025 }
					else if (pixel.temp < 6000) { pixel.color = pixelColorPick(pixel,"#ffe7bd"); var c=0.035 } //new in-between state because the transition is too jarring
					else if (pixel.temp < 7000) { pixel.color = pixelColorPick(pixel,"#ffffbd"); var c=0.05 } //most of these are not real because of the kid named Planckian locus, but it's cooler
					else if (pixel.temp < 9000) { pixel.color = pixelColorPick(pixel,"#feffd6"); var c=0.07 }
					else if (pixel.temp < 11000) { pixel.color = pixelColorPick(pixel,"#f7fff5"); var c=0.1 }
					else if (pixel.temp < 14000) { pixel.color = pixelColorPick(pixel,"#e3fcfc"); var c=0.125 }
					else if (pixel.temp < 17000) { pixel.color = pixelColorPick(pixel,"#d1f6ff"); var c=0.15 }
					else if (pixel.temp < 20000) { pixel.color = pixelColorPick(pixel,"#d1f0ff"); var c=0.175 }
					else if (pixel.temp < 27000) { pixel.color = pixelColorPick(pixel,"#bde0ff"); var c=0.2 }
					else if (pixel.temp < 34000) { pixel.color = pixelColorPick(pixel,"#bdd3ff"); var c=0.25 }
					else if (pixel.temp < 43500) { pixel.color = pixelColorPick(pixel,"#bdc7ff"); var c=0.3 }
					else if (pixel.temp < 50000) { pixel.color = pixelColorPick(pixel,"#c3bdff"); var c=0.4 }
					else if (pixel.temp < 53000) { pixel.color = pixelColorPick(pixel,"#bfb3fe"); var c=0.45 }
					else if (pixel.temp < 56000) { pixel.color = pixelColorPick(pixel,"#bba9fc"); var c=0.5 }
					else if (pixel.temp < 61000) { pixel.color = pixelColorPick(pixel,"#b09df9"); var c=0.6 }
					else if (pixel.temp < 66000) { pixel.color = pixelColorPick(pixel,"#a590f5"); var c=0.7 }
					else if (pixel.temp < 71000) { pixel.color = pixelColorPick(pixel,"#a68af2"); var c=0.8 }
					else { pixel.color = pixelColorPick(pixel,"#a26ffc"); var c=0.9 }
					return c;
				};

				function rainbowSunColor(pixel) {
					var age = pixelTicks - pixel.start;
					var ss, ll;
					if (pixel.temp < 0) { ss = 20; ll = 10; var c=0 }
					else if (pixel.temp < 300) { ss = 20; ll = 15; var c=0 }
					else if (pixel.temp < 500) { ss = 30; ll = 20; var c=0.00003 }
					else if (pixel.temp < 850) { ss = 30; ll = 25; var c=0.0001 }
					else if (pixel.temp < 1300) { ss = 35; ll = 30; var c=0.0004 }
					else if (pixel.temp < 1800) { ss = 40; ll = 35; var c=0.001 }
					else if (pixel.temp < 2100) { ss = 45; ll = 40; var c=0.0025 }
					else if (pixel.temp < 2400) { ss = 50; ll = 45; var c=0.004 }
					else if (pixel.temp < 3200) { ss = 55; ll = 50; var c=0.007 }
					else if (pixel.temp < 3900) { ss = 65; ll = 55; var c=0.01 }
					else if (pixel.temp < 4600) { ss = 70; ll = 60; var c=0.015 }
					else if (pixel.temp < 6100) { ss = 75; ll = 65; var c=0.025 }
					else if (pixel.temp < 7200) { ss = 85; ll = 65; var c=0.035 }
					else if (pixel.temp < 8300) { ss = 95; ll = 70; var c=0.05 }
					else if (pixel.temp < 10400) { ss = 100; ll = 72.5; var c=0.07 }
					else if (pixel.temp < 12500) { ss = 100; ll = 75; var c=0.1 }
					else if (pixel.temp < 15600) { ss = 100; ll = 77.5; var c=0.125 }
					else if (pixel.temp < 18700) { ss = 100; ll = 80; var c=0.15 }
					else if (pixel.temp < 21800) { ss = 100; ll = 85; var c=0.175 }
					else if (pixel.temp < 28900) { ss = 100; ll = 90; var c=0.2 }
					else if (pixel.temp < 36000) { ss = 100; ll = 92.5; var c=0.25 }
					else if (pixel.temp < 45600) { ss = 100; ll = 94; var c=0.3 }
					else if (pixel.temp < 52200) { ss = 100; ll = 96.5; var c=0.4 }
					else if (pixel.temp < 58300) { ss = 100; ll = 98; var c=0.5 }
					else if (pixel.temp < 63400) { ss = 100; ll = 98.5; var c=0.6 }
					else if (pixel.temp < 68500) { ss = 100; ll = 98.5; var c=0.7 }
					else if (pixel.temp < 73600) { ss = 100; ll = 98.5; var c=0.8 }
					else { ss = 100; ll = 98.5; var c=0.9 }

					var hslJson = {h: (age) % 360, s: ss, l: ll};
					pixel.color = pixelColorPick(pixel,convertHslObjects(hslJson,"hex"));

					return c;
				};

				function starLightAndConduction(pixel,c,whitelist=["sun"]) {
					for (var i = 0; i < adjacentCoords.length; i++) {
						var x = pixel.x+adjacentCoords[i][0];
						var y = pixel.y+adjacentCoords[i][1];
						if (isEmpty(x,y)) {
							if (Math.random() <= c) {
								createPixel("light", x, y);
								pixelMap[x][y].color = pixel.color;
							};
						} else if (!outOfBounds(x,y)) {
							var newPixel = pixelMap[x][y];
							//console.log(whitelist,newPixel.element,whitelist.includes(newPixel.element));
							if (pixel.temp!==newPixel.temp && whitelist.includes(newPixel.element)) {
								var avg = (pixel.temp + newPixel.temp)/2;
								pixel.temp = avg;
								newPixel.temp = avg;
								pixelTempCheck(pixel);
								pixelTempCheck(newPixel);
							}
						}
					}
				};

				ferromagneticMaterials = ["iron", "cobalt", "nickel", "steel", "hematite"];

				//x = real number
				//L = maximum value
				//x_0 = "the x value of the sigmoid midpoint" i.e. the x center of the bendy part
				//k = steepness
				function logisticCurve(x,L,k,x0) {
					return L/(   1 + (  Math.E ** ( -k * (x - x0) )  )   );
				};

				// https://stackoverflow.com/questions/10756313/javascript-jquery-map-a-range-of-numbers-to-another-range-of-numbers
				// Function from August Miller
				function scale (number, inMin, inMax, outMin, outMax) {
					return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
				};

				function neutronStarLightAndConduction(pixel,c,whitelist=["neutron_star"]) {
					var pixelAge = pixelTicks - pixel.start;
					var coolingFactor;
					var logistic = logisticCurve(pixelAge/1000, 1, 0.6, -7.7);
					if(pixel.temp > 1000000) {
						//console.log('case 1');
						coolingFactor = logistic
					};
					if(pixel.temp <= 1000000 && pixel.temp > 100000) {
						//console.log('case 2');
						//console.log("l",logistic);
						coolingFactor = scale(pixel.temp,1000000,100000,logistic,0.99999);
						//if(pixelAge % 10 == 0 || pixel.temp < 100500) { console.log(coolingFactor) };
					};
					if(pixel.temp < 100000) {
						//console.log('case 3');
						coolingFactor = 0.99999
					};
					//console.log(coolingFactor);
					pixel.temp = ((pixel.temp - (settings.abszero ?? -273.15)) * coolingFactor) + (settings.abszero ?? -273.15);

					for (var i = 0; i < adjacentCoords.length; i++) {
						var x = pixel.x+adjacentCoords[i][0];
						var y = pixel.y+adjacentCoords[i][1];
						if (isEmpty(x,y)) {
							if (Math.random() <= c) {
								createPixel(Math.random() < 0.995 ? "light" : "neutron", x, y);
								pixelMap[x][y].color = pixel.color;
							};
						} else if (!outOfBounds(x,y)) {
							var newPixel = pixelMap[x][y];
							//console.log(elements[newPixel.element].conduct);
							if(ferromagneticMaterials.includes(newPixel.element) && (Math.random() < 0.1)) { newPixel.charge = 20 }; //no magnetism in sb
							//console.log(whitelist,newPixel.element,whitelist.includes(newPixel.element));
							if (pixel.temp!==newPixel.temp && whitelist.includes(newPixel.element)) {
								var avg = (pixel.temp + newPixel.temp)/2;
								pixel.temp = avg;
								newPixel.temp = avg;
								pixelTempCheck(pixel);
								pixelTempCheck(newPixel);
							}
						}
					}
				};

				function almostSun(pixel,lightScale=1,whitelist=["sun"]) {
					starLightAndConduction(pixel,starColor(pixel) * lightScale,whitelist);
				};

				function nsTick(pixel,lightScale=1,whitelist=["sun"]) {
					neutronStarLightAndConduction(pixel,starColor(pixel) * lightScale,whitelist);
				};

				elements.sun.tick = function(pixel) {
					almostSun(pixel);
				};

			//"Generalized" sedimentation function

				function sedimentation(pixel,finalRock,chance=0.0003) {
					if(finalRock == undefined) { return false };
					if(Math.random() < chance) {
						var validNeighborArray = Array.apply(null, Array(adjacentCoords.length)).map(function() {return false});
						//sedimentSandstoneTries++;
						for(i = 0; i < adjacentCoords.length; i++) {
							//sedimentSandstoneTryIterations++;
							if(isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],false)) {
								validNeighborArray[i] = false;
								//sedimentSandstoneNoDetects++;
							} else if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) {
								/*if(sedimentNeighborTable.includes(pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]].element)) {
									validNeighborArray[i] = true;
									//sedimentSandstoneDetects++;
								} else {
									validNeighborArray[i] = false;
									//sedimentSandstoneNoDetects++;
								};*/
								//validNeighborArray[i] = sedimentNeighborTable.includes(pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]].element);
								validNeighborArray[i] = (pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]].state ?? "solid") == "solid";
							};
						};
						if(validNeighborArray.includes(true)) {
							//sandstoneFormations++;
							//console.log(finalRock);
							changePixel(pixel,finalRock);
						}/* else {
							sandstoneFailures++;
						}*/;
					};
				};

			//Function for mass replacement according to an object

				function transformAround(pixel,range,substitutionObject,reverse=false) {
					var radius1 = (-1 * range);
					var radius2 = (range + 1);
					for (let i = radius1; i < radius2; i++) {
						for (let j = radius1; j < radius2; j++) {
							if(reverse) {
								if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
									var destPixel = pixelMap[pixel.x+j][pixel.y+i];
									var elementToCheck = destPixel.element;
									if(getKeyByValue(substitutionObject,elementToCheck)) {
										changePixel(destPixel,getKeyByValue(substitutionObject,elementToCheck));
									};
								};
							} else {
								if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
									var destPixel = pixelMap[pixel.x+j][pixel.y+i];
									var elementToCheck = destPixel.element;
									if(substitutionObject[elementToCheck]) {
										changePixel(destPixel,substitutionObject[elementToCheck]);
									};
								};
							};
						};
					};
				};

			//Previous function with adjacentCoords

				function transformAdjacent(pixel,substitutionObject,reverse=false) {
					for(k = 0; k < adjacentCoords.length; k++) {
						var i = adjacentCoords[k][0]
						var j = adjacentCoords[k][1]
						if(reverse) {
							if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
								var destPixel = pixelMap[pixel.x+j][pixel.y+i];
								var elementToCheck = destPixel.element;
								if(getKeyByValue(substitutionObject,elementToCheck)) {
									changePixel(destPixel,getKeyByValue(substitutionObject,elementToCheck));
								};
							};
						} else {
							if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
								var destPixel = pixelMap[pixel.x+j][pixel.y+i];
								var elementToCheck = destPixel.element;
								if(substitutionObject[elementToCheck]) {
									changePixel(destPixel,substitutionObject[elementToCheck]);
								};
							};
						};
					};
				};

			//Cooling rate-varied magma solidification

			function magmaRateBasedCooling(pixel,freezingPoint,vitriteName,vitriteThreshold,aphaniteName,aphaniteThreshold,phaneriteName) {
				pixel.lastTemperatures ??= [];
				pixel.lastTemperatures.push(pixel.temp); //due to how it's structured, last temp will always equal pixel.temp;

				while(pixel.lastTemperatures.length > 2) {
					pixel.lastTemperatures.shift();
				};

				if(pixel.lastTemperatures.length > 1) {
					var overallTemperatureChangeRate = (pixel.temp - pixel.lastTemperatures[0]) / (pixel.lastTemperatures.length - 1);
					//console.log(overallTemperatureChangeRate);
					if(overallTemperatureChangeRate >= 0) {
						return;
					};
					if(pixel.temp > freezingPoint) {
						return;
					};
					//console.log(pixel.x,pixel.y,overallTemperatureChangeRate)
					var stateLow;
					if(overallTemperatureChangeRate < vitriteThreshold) { //numbers made up
						//console.log("f99fd90");
						stateLow = vitriteName;
					} else if(overallTemperatureChangeRate < aphaniteThreshold) {
						//console.log("aaaaaaaaaa");
						stateLow = aphaniteName;
					} else {
						//console.log("03");
						stateLow = phaneriteName;
					};
					var stateLowInfo = elements[stateLow];
					var slHasHotRock = (stateLowInfo.stateLow == "hot_" + stateLow);
					var changeToRockIsHot = false;
					if(slHasHotRock) {
						var hotRockPoint = stateLowInfo.tempHigh;
						if(pixel.temp >= hotRockPoint) {
							changeToRockIsHot = true;
						};
					};
					changePixel(pixel,changeToRockIsHot ? "hot_" + stateLow : stateLow,false);
				};
			};

			//Gravel finder
			function getGravelElementName(rockName) {
				if(rockName == "rock") {
					return "gravel";
				};
				var gravelBasedName = rockName + "_gravel";
				if(elements[gravelBasedName]) {
					return gravelBasedName;
				};
				var shardBasedName = rockName + "_shard";
				if(elements[shardBasedName]) {
					return shardBasedName;
				};
				return false;		
			};

			//Sand finder
			function getSandElementName(sandName) {
				var theName = sandName;
				if(getGravelElementName(theName)) { //will fire if it was a rock with a valid gravel
					theName = getGravelElementName(theName)
				};
				if(["komatiite","peridotite","komatiite_gravel","peridotite_gravel"].includes(theName)) {
					return "olivine_sand";
				};
				if(theName == "gravel" || sandSimplification.includes(theName)) {
					return "sand";
				};
				theName = theName.replace(/(gravel|shard)/,"sand");
				if(elements[theName]) {
					return theName;
				};
				return false;
			};

			/*Metamorphism test
			function metamorphosisPressureHandler(rockBeingSquished,rockDoingSquishing) {
				pixel.lastPressures ??= [];

				while(pixel.lastPressures.length > 2) {
					pixel.lastPressures.shift();
				};

				var squisherInfo = elements[rockDoingSquishing.element];
				var squisheeInfo = elements[rockBeingSquished.element];

				rockBeingSquished._squishers ??= {};
				rockBeingSquished._squishers[pixelTicks] ??= {};
				rockBeingSquished._squishers[pixelTicks][`x${rockDoingSquishing.x}y${rockDoingSquishing.y}`] = (squisherInfo.density ?? 2500) + (rockDoingSquishing._receivedPressure ?? 0);

				rockBeingSquished._receivedPressure = sumNumericArray(Object.values(rockBeingSquished._squishers[pixelTicks]));

				if(squisheeInfo.metamorphismFunction) {
					squisheeInfo.metamorphismFunction(rockBeingSquished)
				};
			};	

			function removeLastSquishers(pixel) {
				if(!pixel._squishers) {
					return false;
				};
				if(pixel._squishers[pixelTicks - 1]) {
					delete pixel._squishers[pixelTicks - 1];
				};
			};

			elements.metal_scrap.onTryMoveInto = function(pixel,otherPixel) {
				metamorphosisPressureHandler(pixel,otherPixel);
			};

			elements.metal_scrap.tick = function(pixel) {
				removeLastSquishers(pixel);
			};

			elements.metal_scrap.metamorphismFunction = function(pixel) {
				pixel.temp = pixel._receivedPressure;
			};

			elements.metal_scrap.insulate = true;
			delete elements.metal_scrap.tempHigh;
			delete elements.metal_scrap.stateHigh;
			*/

			/*Erosion
			function toGravelErodeOtmi(pixel,otherPixel,erosionChanceDivisor=5500) {
				var gravelName = getGravelElementName(pixel.element);
				//console.log(gravelName);
				if(!gravelName) { return false };
				var otherState = elements[otherPixel.element].state ?? "solid";
				if(otherState == "solid") {
					return false;
				};
				//console.log(otherState);
				var otherDensity = elements[otherPixel.element].density ?? otherState == "gas" ? 1.3 : 1000;
				var erosionChance = ((otherState == "gas" ? otherDensity * 5 : otherDensity) ** 1/1.7) / erosionChanceDivisor;
				if(Math.random() < erosionChance) {
					changePixel(pixel,gravelName,false);
					//changePixelReturn(pixel,gravelName,false).color = "rgb(255,0,0)";
				};
			};

			function toSandErodeOtmi(pixel,otherPixel,erosionChanceDivisor=5500) {
				var sandName = getSandElementName(pixel.element);
				//console.log(sandName);
				if(!sandName) { return false };
				var otherState = elements[otherPixel.element].state ?? "solid";
				if(otherState == "solid") {
					return false;
				};
				var otherDensity = elements[otherPixel.element].density ?? otherState == "gas" ? 1.3 : 1000;
				var erosionChance = ((otherState == "gas" ? otherDensity * 5 : otherDensity) ** 1/1.7) / erosionChanceDivisor;
				if(Math.random() < erosionChance) {
					changePixel(pixel,sandName,false);
					//changePixelReturn(pixel,sandName,false).color = "rgb(255,255,0)";
				};
			};*/

			//I really hate boilerplate

				//Array maker

					function twoPartRepeatedArray(value1,amount1,value2,amount2) {
						var array1 = Array(amount1).fill(value1);
						var array2 = Array(amount2).fill(value2);
						return array1.concat(array2)
					};
				//Powder maker
				function newPowder(name,color,density=null,tempHigh=null,stateHigh=null,breakInto=null) { //boilerplate my dick
					if(tempHigh == null) {
						stateHigh = null;
					};

					elements[name] = {
						color: color,
						behavior: behaviors.POWDER,
						category: "solids",
						state: "solid",
						density: density ?? 1000,
					};

					if(tempHigh !== null) {
						elements[name].tempHigh = tempHigh;
					};

					if(tempHigh !== null && stateHigh !== null) {
						elements[name].stateHigh = stateHigh;
					};

					if(breakInto !== null) {
						elements[name].breakInto = breakInto;
					};

					return elements[name];
				};

				//Color gen

					//Gravels

						function gravelizeToHex(colorIn) {
							var colorInput = colorIn; //side effects?
							//console.log(`gravelizeToHex: ${colorInput}`)

							//make sure in is array
							if(!(colorInput instanceof Array)) {
								colorInput = [colorInput];
							};
							//console.log(`gravelizeToHex: ${colorInput}`)

							//console.log(colorInput);

							//prepare final color
							var finalColor = [];

							//console.log(colorInput);
							for(var i = 0; i < colorInput.length; i++) {
								finalColor.push(colorInput[i]);
								finalColor.push(colorInput[i]);
								finalColor.push(colorInput[i]);
							};


							//vary luminance
							for(i = 0; i < finalColor.length; i+=3) {
								finalColor[i] = changeLuminance(finalColor[i],1.25,"multiply","hsljson");
							};

							//leave offset-1 colors as-is

							for(i = 2; i < finalColor.length; i+=3) {
								finalColor[i] = changeLuminance(finalColor[i],0.85,"multiply","hsljson");
							};


							//desaturate
							for(i = 0; i < finalColor.length; i++) {
								finalColor[i] = changeSaturation(finalColor[i],0.9,"multiply","hex");
							};

							//finish
							//console.log(finalColor);
							return finalColor;
						};

					//Sands

						function sandizeToHex(rockColor,type="normal",sBringTo=31,sBringFactor=0.4,lBringTo=70,lBringFactor=0.6) {
							if(elements[rockColor]) {
								//Assuming an element was given, for compatibility
								rockColor = elements[rockColor].color
							};
							if(!["normal","n","wet","w","packed","p"].includes(type.toLowerCase())) {
								throw new Error("Type must be 'normal', 'wet', or 'packed'");
							};
							var sandColor = [];
							//var sandColorObject = [];
							if(!(rockColor instanceof Array)) {
								rockColor = [rockColor];
							};
							for(i = 0; i < rockColor.length; i++) {
								var colorAsHsl = normalizeColorToHslObject(rockColor[i]);
								if(colorAsHsl.s > 0) {	colorAsHsl.s = sBringTo + (-sBringFactor * (sBringTo - colorAsHsl.s)) }; //bring towards 31;
														colorAsHsl.l = lBringTo + (-lBringFactor * (lBringTo - colorAsHsl.l)); //bring towards 70
								switch(type.toLowerCase()) {
									case "normal":
									case "n":
										break;
									case "wet":
									case "w":
										if(colorAsHsl.s > 0) { colorAsHsl.s += 3 };
										colorAsHsl.l -= 15;
										break;
									case "packed":
									case "p":
										colorAsHsl.s = Math.max(colorAsHsl.s - 11, 0);
										colorAsHsl.l += 6;
										break;
									default:
										break;
								};								
								sandColor.push(convertHslObjects(colorAsHsl,"hex"));
								//sandColorObject.push(convertHslObjects(colorAsHsl,"rgbjson"));
							};

							return sandColor;
						};

						function dustizeToHex(rockColor,sBringTo=25,sBringFactor=0.4,lBringTo=55,lBringFactor=0.6) {
							if(elements[rockColor]) {
								//Assuming an element was given, for compatibility
								rockColor = elements[rockColor].color
							};
							//console.log(rockName);
							var dustColor = [];
							//var dustColorObject = [];
							if(!(rockColor instanceof Array)) {
								rockColor = [rockColor];
							};
							for(i = 0; i < rockColor.length; i++) {
								var colorAsHsl = normalizeColorToHslObject(rockColor[i]);
								if(colorAsHsl.s > 0) {	colorAsHsl.s = sBringTo + (-sBringFactor * (sBringTo - colorAsHsl.s)) }; //bring towards 31;
														colorAsHsl.l = lBringTo + (-lBringFactor * (lBringTo - colorAsHsl.l)); //bring towards 70
								dustColor.push(convertHslObjects(colorAsHsl,"hex"));
								//dustColorObject.push(convertHslObjects(colorAsHsl,"rgbjson"));
							};

							return dustColor;
						};

					//Sandstones

						function sandstonizeToHex(sandName,type="normal") {
							//console.log(sandName);
							var sandInfo = elements[sandName];
							if(!sandInfo) { throw new Error("No such element '" + sandName + "'") };
							var finalColor = [];
							//var sandColorObject = [];
							var sandColor = sandInfo.color;
							if(!(sandColor instanceof Array)) {
								sandColor = [sandColor];
							};
							//console.log(sandColor);
							for(var i = 0; i < sandColor.length; i++) {
								//console.log(i,sandColor[i]);
								var colorAsHsl = normalizeColorToHslObject(sandColor[i]);
								//console.log(colorAsHsl);
								if(colorAsHsl.s > 5 && colorAsHsl.h !== 0) { colorAsHsl.h -= 10 };
								if(colorAsHsl.s > 5 && colorAsHsl.h !== 0) { colorAsHsl.s = 21 + (-0.8 * (21 - colorAsHsl.s)) }; //bring towards 21;
								colorAsHsl.l = 58 + (-0.8 * (58 - colorAsHsl.l)); //bring towards 58
								if(colorAsHsl.s > 5 && colorAsHsl.h !== 0) { colorAsHsl.s -= 4 };
								colorAsHsl.l += 2;
								//console.log(colorAsHsl);
								finalColor.push(convertHslObjects(colorAsHsl,"hex"));
								//sandColorObject.push(convertHslObjects(colorAsHsl,"rgbjson"));
							};

							return finalColor;
						};

						function sedimentHslOffset(hslJsonColor) {
							return {h: hslJsonColor.h - 4, s: hslJsonColor.s - 20, l: hslJsonColor.l - 25};
						};

					//Magmas

						function makeMoltenColor(colorIn) { //Edited vanilla code
							//console.log(colorIn);

							var newcolor = colorIn;
							var moltenColorFactors = [ [2,1.25,0.5], [2,1,0.5], [2,0.75,0] ];
							var colorList = [];
							var colorObjectList = [];
							// if newcolor is not an array, put it in an array
							if (!(newcolor instanceof Array)) { newcolor = [newcolor]; }
							newcolor = newcolor.map(x => convertColorFormats(x,"json"));
							// for every color in the newcolor array, add a new color with the same value, but with the r and g values increased
							for (var i = 0; i < newcolor.length; i++) {
								var c = newcolor[i];
								for (var j = 0; j < moltenColorFactors.length; j++) {
									var newc = moltenColorFactors[j];
									//console.log(c,newc);
									r = Math.floor(c.r * newc[0]);
									g = Math.floor(c.g * newc[1]);
									b = Math.floor(c.b * newc[2]);
									if (r > 255) {r = 255}; if (g > 255) {g = 255};
									//edit: to hex
									var rHex = r.toString(16); if(rHex.length == 1) { rHex = "0" + rHex };
									var gHex = g.toString(16); if(gHex.length == 1) { gHex = "0" + gHex };
									var bHex = b.toString(16); if(bHex.length == 1) { bHex = "0" + bHex };
									colorList.push("#"+rHex+gHex+bHex);
								}
							}
							return colorList;
						}

					//Magma vapors
						function magmavaporizeToHex(colorIn) {
							var color = colorIn;

							if(!(color instanceof Array)) {
								color = [color];
							};

							color = color.map(x => normalizeColorToHslObject(x));

							for(i = 0; i < color.length; i++) {
								color[i].h += 5;
								color[i].s -= 5;
								color[i].l += 20;
							};

							color = color.map(x => convertHslObjects(x,"hex"));

							return color;
						};

						function magmacloudizeToHex(colorIn) {
							var color = colorIn;

							if(!(color instanceof Array)) {
								color = [color];
							};

							color = color.map(x => normalizeColorToHslObject(x));

							for(i = 0; i < color.length; i++) {
								color[i].h += 5;
								color[i].s -= 8;
								color[i].l += 5;
							};

							color = color.map(x => convertHslObjects(x,"hex"));

							return color;
						};

						function rockcloudizeToHex(colorIn) {
							var color = colorIn;

							if(!(color instanceof Array)) {
								color = [color];
							};

							color = color.map(x => normalizeColorToHslObject(x));

							for(i = 0; i < color.length; i++) {
								color[i].h -= 12;
								color[i].s *= 0.12;
								color[i].l -= 6;
							};

							color = color.map(x => convertHslObjects(x,"hex"));

							return color;
						};

				//Generate an entire composition family at once

				function redHotColorgen(colorIn,outputFormat="rgb") {
					var color = colorIn;
					//console.log(color);
					if(!Array.isArray(color)) {
						color = [color];
					};
					//console.log(color);
					color = color.map(x => convertColorFormats(x,"json"));
					//console.log(color);
					for(i = 0; i < color.length; i++) {
						var subcolor = color[i];
						//console.log(i);
						subcolor.r += 48;
						subcolor.r *= 1.7;
						subcolor.g += 24;
						subcolor.g *= 1.2;
						subcolor.g -= 16;
						subcolor.b -= 10;
						subcolor.b *= 0.75;
						for(colorlet in subcolor) {
							subcolor[colorlet] = Math.round(rgbColorBound(subcolor[colorlet]));
						};
						//console.log(color);
					};
					//console.log(color);
					color = color.map(x => convertColorFormats(x,outputFormat));
					if(color.length == 1) { color = color[0] };
					return color;
				};

				var sands = ["sand", "dirt", "crimsoil", "rainbow_dirt"]; //Some sources suggest the existence of topsoil sediment, so for the purposes of sedimentary rock generation, dirt is now a sand /hj
				var wetSands = ["wet_sand", "mud"];
				var sandSuspensions = [];
				var sandSediments = ["radioactive_sand_sediment","clay_sediment"];
				var sandstones = ["radioactive_sandstone","shale"];
				var vaporizedMagmas = [];
				var magmaClouds = [];
				var rockClouds = [];

				function nicffunc_getReactions(elemName) {
					if(!(elements[elemName])) {
						return null;
					};
					if(!(elements[elemName].reactions)) {
						return null;
					};
					var reactions = elements[elemName].reactions;
					if(structuredClone) {
						return !!reactions ? structuredClone(reactions) : null;
					} else {
						return !!reactions ? JSON.parse(JSON.stringify(reactions)) : null;
					};
				};

				function simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) {
					if(pixel.exposedToAir) { return };
					if(pixel.temp > 200 && Math.random () < 0.0001) {
						changePixel(pixel,elements[pixel.element].metamorphite)
					};
					return
				};

				function newIgneousCompositionFamily(
					compositionFamilyName,

					magmaViscosity, magmaDensity, vitriteCoolingRateThreshold, aphaniteCoolingRateThreshold, magmaBoilingPoint,

					phaneriteName, phaneriteColor, phaneriteMeltingPoint, phaneriteDensity,
					metaphaneriteName, metaphaneriteColor, metaphaneriteMeltingPoint, metaphaneriteDensity,

					aphaniteName, aphaniteColor, aphaniteMeltingPoint, aphaniteDensity,
					metaaphaniteName, metaaphaniteColor, metaaphaniteMeltingPoint, metaaphaniteDensity,

					vesiculiteName, vesiculiteColor, vesiculiteMeltingPoint, vesiculiteDensity,
					metavesiculiteName, metavesiculiteColor, metavesiculiteMeltingPoint, metavesiculiteDensity,

					vitriteName, vitriteColor, vitriteMeltingPoint, vitriteDensity,
					metavitriteName, metavitriteColor, metavitriteMeltingPoint, metavitriteDensity,

					sandFormationReactionRegularSandCount, sandFormationReactionSpecificSandCount,
				) {
					//console.log(compositionFamilyName,vesiculiteMeltingPoint,vitriteMeltingPoint);

					//Auto names
						//Sand
							//gabbro_sand instead of rock_sand for rock's unique sand
							var phaneriteSandName = compositionFamilyName == "mafic" ? "gabbro_sand" : phaneriteName + "_sand"; 
							var aphaniteSandName = aphaniteName + "_sand";
							var vesiculiteSandName = vesiculiteName + "_sand";
							var vitriteSandName = vitriteName + "_sand";
							var metaphaneriteSandName = metaphaneriteName + "_sand"; 
							var metaaphaniteSandName = metaaphaniteName + "_sand";
							var metavesiculiteSandName = metavesiculiteName + "_sand";
							var metavitriteSandName = metavitriteName + "_sand";

						//Solid rocks (rock walls)
							//keep rock_wall to replace vanilla rock wall
							var phaneriteWallName = compositionFamilyName == "mafic" ? "rock_wall" : phaneriteName + "_wall";
							var aphaniteWallName = aphaniteName + "_wall";
							var vesiculiteWallName = vesiculiteName + "_wall";
							var vitriteWallName = vitriteName + "_wall";
							var metaphaneriteWallName = metaphaneriteName + "_wall";
							var metaaphaniteWallName = metaaphaniteName + "_wall";
							var metavesiculiteWallName = metavesiculiteName + "_wall";
							var metavitriteWallName = metavitriteName + "_wall";

						//Gravel
							//gravel instead of rock_gravel for normal gravel (as rock's unique gravel)
							var phaneriteGravelName = compositionFamilyName == "mafic" ? "gravel" : phaneriteName + "_gravel";
							var aphaniteGravelName = aphaniteName + "_gravel";
							var vesiculiteGravelName = vesiculiteName + "_gravel";
							var vitriteGravelName = vitriteName + "_shard";
							var metaphaneriteGravelName = metaphaneriteName + "_gravel";
							var metaaphaniteGravelName = metaaphaniteName + "_gravel";
							var metavesiculiteGravelName = metavesiculiteName + "_gravel";
							var metavitriteGravelName = metavitriteName + "_shard";

						//Dust
							//gabbro_dust instead of rock_dust for rock's unique dust
							var phaneriteDustName = compositionFamilyName == "mafic" ? "gabbro_dust" : phaneriteName + "_dust";		
							var aphaniteDustName = aphaniteName + "_dust";
							var vesiculiteDustName = vesiculiteName + "_dust";
							var vitriteDustName = vitriteName + "_dust";
							var metaphaneriteDustName = metaphaneriteName + "_dust";		
							var metaaphaniteDustName = metaaphaniteName + "_dust";
							var metavesiculiteDustName = metavesiculiteName + "_dust";
							var metavitriteDustName = metavitriteName + "_dust";

						//Push future sand names and wet sand names to sand list for sandstone system generation
							sands.push(phaneriteSandName);
							sands.push(aphaniteSandName);
							sands.push(vesiculiteSandName);
							sands.push(vitriteSandName);
							sands.push(metaphaneriteSandName);
							sands.push(metaaphaniteSandName);
							sands.push(metavesiculiteSandName);
							sands.push(metavitriteSandName);
							wetSands.push("wet_" + phaneriteSandName);
							wetSands.push("wet_" + aphaniteSandName);
							wetSands.push("wet_" + vesiculiteSandName);
							wetSands.push("wet_" + vitriteSandName);
							wetSands.push("wet_" + metaphaneriteSandName);
							wetSands.push("wet_" + metaaphaniteSandName);
							wetSands.push("wet_" + metavesiculiteSandName);
							wetSands.push("wet_" + metavitriteSandName);

						//Magma and magma derivative names
							var magmaName = compositionFamilyName == "mafic" ? "magma" : compositionFamilyName + "_magma";
							var magmaCloudName = magmaName + "_cloud"
							var rockCloudName = compositionFamilyName + "_rock_cloud"

						//Create rocks, transplant existing reactions if they exist, add/change erosion reactions to match, and create corresponding physical variants
							//Phanerite
								var phaneriteOldReactions = nicffunc_getReactions(phaneriteName);

								elements[phaneriteName] = {
									color: phaneriteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: phaneriteMeltingPoint,
									stateHigh: magmaName,
									density: phaneriteDensity,
									hardness: 0.75,
									breakInto: phaneriteGravelName,
									_data: [compositionFamilyName,"phanerite","igneous_rock"],
									metamorphite: metaphaneriteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};
								if(phaneriteOldReactions) {
									elements[phaneriteName].reactions = phaneriteOldReactions;
								};

								//replace water rock-erosion reaction
								elements.water.reactions[phaneriteName] = { "elem2": phaneriteGravelName, "chance": 0.00035 }

								//create unique gravel
								elements[phaneriteGravelName] = {
									color: gravelizeToHex(phaneriteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: phaneriteMeltingPoint,
									stateHigh: magmaName,
									breakInto: phaneriteDustName,
									density: phaneriteDensity * 0.55,
									_data: [compositionFamilyName,"phanerite","igneous_gravel"],
									metamorphite: metaphaneriteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements[phaneriteDustName] = {
									color: dustizeToHex(phaneriteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: phaneriteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[phaneriteDustName]: {elem1: phaneriteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (phaneriteDensity / 1000), //unmeasured value
									_data: [compositionFamilyName,"phanerite","dust"],
								};

								//generate water gravel-erosion reaction using rock family's sand ratio
								elements.water.reactions[phaneriteGravelName] = { "elem2": twoPartRepeatedArray(phaneriteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								//generate unique solid version
								elements[phaneriteWallName] = {
									color: phaneriteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: phaneriteMeltingPoint,
									stateHigh: magmaName,
									density: phaneriteDensity,
									hardness: 0.8,
									breakInto: phaneriteName,
									_data: [compositionFamilyName,"phanerite","solid_igneous_rock"],
									metamorphite: metaphaneriteWallName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements.water.reactions[phaneriteWallName] = { "elem2": phaneriteName, "chance": 0.00035 }

								//Sand and sand variants
									elements[phaneriteSandName] = {
										color: sandizeToHex(phaneriteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: phaneriteMeltingPoint,
										stateHigh: vitriteName,
										density: phaneriteDensity * 0.595,
										_data: [compositionFamilyName,"phanerite","particulate"],
										metamorphite: metaphaneriteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									//console.log(phaneriteSandName, elements[phaneriteSandName].color);

									elements["wet_" + phaneriteSandName] = {
										color: sandizeToHex(phaneriteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + phaneriteSandName,
										tempLow: -50,
										stateLow:"packed_" + phaneriteSandName,
										density: phaneriteDensity * 0.595 + 150,
										_data: [compositionFamilyName,"phanerite","wet_particulate"],
										metamorphite: metaphaneriteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["packed_" + phaneriteSandName] = {
										color: sandizeToHex(phaneriteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: phaneriteMeltingPoint,
										stateHigh: vitriteName,
										density: phaneriteDensity * 0.59,
										breakInto: phaneriteSandName,
										_data: [compositionFamilyName,"phanerite","packed_particulate"],
										metamorphite: metaphaneriteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements.water.reactions[phaneriteSandName] = {
										"elem1": null, "elem2": "wet_" + phaneriteSandName,
									};

							//Metaphanerite
								var metaphaneriteOldReactions = nicffunc_getReactions(phaneriteName);

								elements[metaphaneriteName] = {
									color: metaphaneriteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: metaphaneriteMeltingPoint,
									stateHigh: magmaName,
									density: metaphaneriteDensity,
									hardness: 0.75,
									breakInto: metaphaneriteGravelName,
									_data: [compositionFamilyName,"metaphanerite","metamorphic_rock"],
								};
								if(metaphaneriteOldReactions) {
									elements[metaphaneriteName].reactions = metaphaneriteOldReactions;
								};

								//replace water rock-erosion reaction
								elements.water.reactions[metaphaneriteName] = { "elem2": metaphaneriteGravelName, "chance": 0.00035 }

								//create unique gravel
								elements[metaphaneriteGravelName] = {
									color: gravelizeToHex(metaphaneriteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: metaphaneriteMeltingPoint,
									stateHigh: magmaName,
									breakInto: metaphaneriteDustName,
									density: metaphaneriteDensity * 0.55,
									_data: [compositionFamilyName,"metaphanerite","metamorphic_gravel"],
								};

								elements[metaphaneriteDustName] = {
									color: dustizeToHex(metaphaneriteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: metaphaneriteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[metaphaneriteDustName]: {elem1: metaphaneriteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (metaphaneriteDensity / 1000), //unmeasured value
									_data: [compositionFamilyName,"metaphanerite","dust"],
								};

								//generate water gravel-erosion reaction using rock family's sand ratio
								elements.water.reactions[metaphaneriteGravelName] = { "elem2": twoPartRepeatedArray(metaphaneriteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								//generate unique solid version
								elements[metaphaneriteWallName] = {
									color: metaphaneriteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: metaphaneriteMeltingPoint,
									stateHigh: magmaName,
									density: metaphaneriteDensity,
									hardness: 0.8,
									breakInto: metaphaneriteName,
									_data: [compositionFamilyName,"metaphanerite","solid_metamorphic_rock"],
								};

								elements.water.reactions[metaphaneriteWallName] = { "elem2": metaphaneriteName, "chance": 0.00035 }

								//Sand and sand variants
									elements[metaphaneriteSandName] = {
										color: sandizeToHex(metaphaneriteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: metaphaneriteMeltingPoint,
										stateHigh: vitriteName,
										density: metaphaneriteDensity * 0.595,
										_data: [compositionFamilyName,"metaphanerite","particulate"],
									};

									//console.log(metaphaneriteSandName, elements[metaphaneriteSandName].color);

									elements["wet_" + metaphaneriteSandName] = {
										color: sandizeToHex(metaphaneriteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + metaphaneriteSandName,
										tempLow: -50,
										stateLow:"packed_" + metaphaneriteSandName,
										density: metaphaneriteDensity * 0.595 + 150,
										_data: [compositionFamilyName,"metaphanerite","wet_particulate"],
									};

									elements["packed_" + metaphaneriteSandName] = {
										color: sandizeToHex(metaphaneriteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: metaphaneriteMeltingPoint,
										stateHigh: vitriteName,
										density: metaphaneriteDensity * 0.59,
										breakInto: metaphaneriteSandName,
										_data: [compositionFamilyName,"metaphanerite","packed_particulate"],
									};

									elements.water.reactions[metaphaneriteSandName] = {
										"elem1": null, "elem2": "wet_" + metaphaneriteSandName,
									};

							//Aphanite
								var aphaniteOldReactions = nicffunc_getReactions(aphaniteName);

								elements[aphaniteName] = {
									color: aphaniteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: aphaniteMeltingPoint,
									stateHigh: magmaName,
									density: aphaniteDensity,
									hardness: 0.75,
									breakInto: aphaniteGravelName,
									_data: [compositionFamilyName,"aphanite","igneous_rock"],
									metamorphite: metaaphaniteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};
								if(aphaniteOldReactions) {
									elements[aphaniteName].reactions = aphaniteOldReactions;
								};

								elements.water.reactions[aphaniteName] = { "elem2": aphaniteGravelName, "chance": 0.00035 }

								elements[aphaniteGravelName] = {
									color: gravelizeToHex(aphaniteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: aphaniteMeltingPoint,
									stateHigh: magmaName,
									breakInto: aphaniteDustName,
									density: aphaniteDensity * 0.55,
									_data: [compositionFamilyName,"aphanite","igneous_gravel"],
									metamorphite: metaaphaniteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements[aphaniteDustName] = {
									color: dustizeToHex(aphaniteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: aphaniteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[aphaniteDustName]: {elem1: aphaniteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (aphaniteDensity / 1000), //unmeasured value
									_data: [compositionFamilyName,"aphanite","dust"],
								};

								elements.water.reactions[aphaniteGravelName] = { "elem2": twoPartRepeatedArray(aphaniteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								elements[aphaniteWallName] = {
									color: aphaniteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: aphaniteMeltingPoint,
									stateHigh: magmaName,
									density: aphaniteDensity,
									hardness: 0.8,
									breakInto: aphaniteName,
									_data: [compositionFamilyName,"aphanite","solid_igneous_rock"],
									metamorphite: metaaphaniteWallName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements.water.reactions[aphaniteWallName] = { "elem2": aphaniteName, "chance": 0.00035 }

								//Sand and sand variants
									elements[aphaniteSandName] = {
										color: sandizeToHex(aphaniteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: aphaniteMeltingPoint,
										stateHigh: vitriteName,
										density: aphaniteDensity * 0.595,
										_data: [compositionFamilyName,"aphanite","particulate"],
										metamorphite: metaaphaniteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["wet_" + aphaniteSandName] = {
										color: sandizeToHex(aphaniteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + aphaniteSandName,
										tempLow: -50,
										stateLow:"packed_" + aphaniteSandName,
										density: aphaniteDensity * 0.595 + 150,
										_data: [compositionFamilyName,"aphanite","wet_particulate"],
										metamorphite: metaaphaniteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["packed_" + aphaniteSandName] = {
										color: sandizeToHex(aphaniteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: aphaniteMeltingPoint,
										stateHigh: vitriteName,
										density: aphaniteDensity * 0.59,
										breakInto: aphaniteSandName,
										_data: [compositionFamilyName,"aphanite","packed_particulate"],
										metamorphite: metaaphaniteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements.water.reactions[aphaniteSandName] = {
										"elem1": null, "elem2": "wet_" + aphaniteSandName,
									};

							//Metaaphanite
								var metaaphaniteOldReactions = nicffunc_getReactions(phaneriteName);

								elements[metaaphaniteName] = {
									color: metaaphaniteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: metaaphaniteMeltingPoint,
									stateHigh: magmaName,
									density: metaaphaniteDensity,
									hardness: 0.75,
									breakInto: metaaphaniteGravelName,
									_data: [compositionFamilyName,"metaaphanite","metamorphic_rock"],
								};
								if(metaaphaniteOldReactions) {
									elements[metaaphaniteName].reactions = metaaphaniteOldReactions;
								};

								//replace water rock-erosion reaction
								elements.water.reactions[metaaphaniteName] = { "elem2": metaaphaniteGravelName, "chance": 0.00035 }

								//create unique gravel
								elements[metaaphaniteGravelName] = {
									color: gravelizeToHex(metaaphaniteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: metaaphaniteMeltingPoint,
									stateHigh: magmaName,
									breakInto: metaaphaniteDustName,
									density: metaaphaniteDensity * 0.55,
									_data: [compositionFamilyName,"metaaphanite","metamorphic_gravel"],
								};

								elements[metaaphaniteDustName] = {
									color: dustizeToHex(metaaphaniteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: metaaphaniteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[metaaphaniteDustName]: {elem1: metaaphaniteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (metaaphaniteDensity / 1000), //unmeasured value
									_data: [compositionFamilyName,"metaaphanite","dust"],
								};

								//generate water gravel-erosion reaction using rock family's sand ratio
								elements.water.reactions[metaaphaniteGravelName] = { "elem2": twoPartRepeatedArray(metaaphaniteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								//generate unique solid version
								elements[metaaphaniteWallName] = {
									color: metaaphaniteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: metaaphaniteMeltingPoint,
									stateHigh: magmaName,
									density: metaaphaniteDensity,
									hardness: 0.8,
									breakInto: metaaphaniteName,
									_data: [compositionFamilyName,"metaaphanite","solid_metamorphic_rock"],
								};

								elements.water.reactions[metaaphaniteWallName] = { "elem2": metaaphaniteName, "chance": 0.00035 }

								//Sand and sand variants
									elements[metaaphaniteSandName] = {
										color: sandizeToHex(metaaphaniteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: metaaphaniteMeltingPoint,
										stateHigh: vitriteName,
										density: metaaphaniteDensity * 0.595,
										_data: [compositionFamilyName,"metaaphanite","particulate"],
									};

									//console.log(metaaphaniteSandName, elements[metaaphaniteSandName].color);

									elements["wet_" + metaaphaniteSandName] = {
										color: sandizeToHex(metaaphaniteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + metaaphaniteSandName,
										tempLow: -50,
										stateLow:"packed_" + metaaphaniteSandName,
										density: metaaphaniteDensity * 0.595 + 150,
										_data: [compositionFamilyName,"metaaphanite","wet_particulate"],
									};

									elements["packed_" + metaaphaniteSandName] = {
										color: sandizeToHex(metaaphaniteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: metaaphaniteMeltingPoint,
										stateHigh: vitriteName,
										density: metaaphaniteDensity * 0.59,
										breakInto: metaaphaniteSandName,
										_data: [compositionFamilyName,"metaaphanite","packed_particulate"],
									};

									elements.water.reactions[metaaphaniteSandName] = {
										"elem1": null, "elem2": "wet_" + metaaphaniteSandName,
									};

							//Vesiculite
								elements[vesiculiteName] = {
									color: vesiculiteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: vesiculiteMeltingPoint,
									stateHigh: magmaName,
									density: vesiculiteDensity,
									hardness: 0.75,
									breakInto: vesiculiteGravelName,
									_data: [compositionFamilyName,"vesiculite","igneous_rock"],
									maxColorOffset: 40,
									metamorphite: metavesiculiteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements.water.reactions[vesiculiteName] = { "elem2": vesiculiteGravelName, "chance": 0.00035 }

								elements[vesiculiteGravelName] = {
									color: gravelizeToHex(vesiculiteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: vesiculiteMeltingPoint,
									stateHigh: magmaName,
									breakInto: vesiculiteDustName,
									density: vesiculiteDensity * 3.2,
									_data: [compositionFamilyName,"vesiculite","igneous_gravel"],
									maxColorOffset: 40,
									metamorphite: metavesiculiteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements[vesiculiteDustName] = {
									color: dustizeToHex(vesiculiteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: vesiculiteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[vesiculiteDustName]: {elem1: vesiculiteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (vesiculiteDensity / 800), //unmeasured value
									_data: [compositionFamilyName,"vesiculite","dust"],
								};

								elements.water.reactions[vesiculiteGravelName] = { "elem2": twoPartRepeatedArray(vesiculiteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								elements[vesiculiteWallName] = {
									color: vesiculiteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: vesiculiteMeltingPoint,
									stateHigh: magmaName,
									density: vesiculiteDensity,
									hardness: 0.8,
									breakInto: vesiculiteName,
									_data: [compositionFamilyName,"vesiculite","solid_igneous_rock"],
									maxColorOffset: 40,
									metamorphite: metavesiculiteWallName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }

								};

								elements.water.reactions[vesiculiteWallName] = { "elem2": vesiculiteName, "chance": 0.00035 }

								//Sand and sand variants
									elements[vesiculiteSandName] = {
										color: sandizeToHex(vesiculiteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: vesiculiteMeltingPoint,
										stateHigh: vitriteName,
										density: vesiculiteDensity * 1.9,
										_data: [compositionFamilyName,"vesiculite","particulate"],
										metamorphite: metavesiculiteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["wet_" + vesiculiteSandName] = {
										color: sandizeToHex(vesiculiteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + vesiculiteSandName,
										tempLow: -50,
										stateLow:"packed_" + vesiculiteSandName,
										density: vesiculiteDensity * 1.9 + 150,
										_data: [compositionFamilyName,"vesiculite","wet_particulate"],
										metamorphite: metavesiculiteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["packed_" + vesiculiteSandName] = {
										color: sandizeToHex(vesiculiteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: vesiculiteMeltingPoint,
										stateHigh: vitriteName,
										density: vesiculiteDensity * 1.888,
										breakInto: vesiculiteSandName,
										_data: [compositionFamilyName,"vesiculite","packed_particulate"],
										metamorphite: metavesiculiteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements.water.reactions[vesiculiteSandName] = {
										"elem1": null, "elem2": "wet_" + vesiculiteSandName,
									};

							//Metavesiculite
								elements[metavesiculiteName] = {
									color: metavesiculiteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: metavesiculiteMeltingPoint,
									stateHigh: magmaName,
									density: metavesiculiteDensity,
									hardness: 0.75,
									breakInto: metavesiculiteGravelName,
									_data: [compositionFamilyName,"metavesiculite","igneous_rock"],
									maxColorOffset: 35
								};

								elements.water.reactions[metavesiculiteName] = { "elem2": metavesiculiteGravelName, "chance": 0.00035 }

								elements[metavesiculiteGravelName] = {
									color: gravelizeToHex(metavesiculiteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: metavesiculiteMeltingPoint,
									stateHigh: magmaName,
									breakInto: metavesiculiteDustName,
									density: metavesiculiteDensity * 3.2,
									_data: [compositionFamilyName,"metavesiculite","metamorphic_gravel"],
									maxColorOffset: 35
								};

								elements[metavesiculiteDustName] = {
									color: dustizeToHex(metavesiculiteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: metavesiculiteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[metavesiculiteDustName]: {elem1: metavesiculiteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (metavesiculiteDensity / 800), //unmeasured value
									_data: [compositionFamilyName,"metavesiculite","dust"],
								};

								elements.water.reactions[metavesiculiteGravelName] = { "elem2": twoPartRepeatedArray(metavesiculiteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								elements[metavesiculiteWallName] = {
									color: metavesiculiteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: metavesiculiteMeltingPoint,
									stateHigh: magmaName,
									density: metavesiculiteDensity,
									hardness: 0.8,
									breakInto: metavesiculiteName,
									_data: [compositionFamilyName,"metavesiculite","solid_metamorphic_rock"],
									maxColorOffset: 35
								};

								elements.water.reactions[metavesiculiteWallName] = { "elem2": metavesiculiteName, "chance": 0.00035 }

								//Sand and sand variants
									elements[metavesiculiteSandName] = {
										color: sandizeToHex(metavesiculiteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: metavesiculiteMeltingPoint,
										stateHigh: vitriteName,
										density: metavesiculiteDensity * 1.9,
										_data: [compositionFamilyName,"metavesiculite","particulate"],
									};

									elements["wet_" + metavesiculiteSandName] = {
										color: sandizeToHex(metavesiculiteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + metavesiculiteSandName,
										tempLow: -50,
										stateLow:"packed_" + metavesiculiteSandName,
										density: metavesiculiteDensity * 1.9 + 150,
										_data: [compositionFamilyName,"metavesiculite","wet_particulate"],
									};

									elements["packed_" + metavesiculiteSandName] = {
										color: sandizeToHex(metavesiculiteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: metavesiculiteMeltingPoint,
										stateHigh: vitriteName,
										density: metavesiculiteDensity * 1.888,
										breakInto: metavesiculiteSandName,
										_data: [compositionFamilyName,"metavesiculite","packed_particulate"],
									};

									elements.water.reactions[metavesiculiteSandName] = {
										"elem1": null, "elem2": "wet_" + metavesiculiteSandName,
									};

							//Vitrite
								elements[vitriteName] = {
									color: vitriteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: vitriteMeltingPoint,
									stateHigh: magmaName,
									density: vitriteDensity,
									hardness: 0.75,
									breakInto: vitriteGravelName,
									_data: [compositionFamilyName,"vitrite","igneous_rock"],
									metamorphite: metavitriteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements.water.reactions[vitriteName] = { "elem2": vitriteGravelName, "chance": 0.00035 }

								elements[vitriteGravelName] = {
									color: gravelizeToHex(vitriteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: vitriteMeltingPoint,
									stateHigh: magmaName,
									breakInto: vitriteDustName,
									density: vitriteDensity * 0.55,
									_data: [compositionFamilyName,"vitrite","glass_shard"],
									metamorphite: metavitriteName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements[vitriteDustName] = {
									color: dustizeToHex(vitriteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: vitriteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[vitriteDustName]: {elem1: vitriteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (vitriteDensity / 1000), //unmeasured value
									_data: [compositionFamilyName,"vitrite","dust"],
								};

								elements.water.reactions[vitriteGravelName] = { "elem2": twoPartRepeatedArray(vitriteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								elements[vitriteWallName] = {
									color: vitriteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: vitriteMeltingPoint,
									stateHigh: magmaName,
									density: vitriteDensity,
									hardness: 0.8,
									breakInto: vitriteName,
									_data: [compositionFamilyName,"vitrite","solid_igneous_rock"],
									metamorphite: metavitriteWallName,
									onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
								};

								elements.water.reactions[vitriteWallName] = { "elem2": vitriteName, "chance": 0.00035 }
								
								//Sand and sand variants
									elements[vitriteSandName] = {
										color: sandizeToHex(vitriteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: vitriteMeltingPoint,
										stateHigh: vitriteName,
										density: vitriteDensity * 0.595,
										_data: [compositionFamilyName,"vitrite","particulate"],
										metamorphite: metavitriteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["wet_" + vitriteSandName] = {
										color: sandizeToHex(vitriteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + vitriteSandName,
										tempLow: -50,
										stateLow:"packed_" + vitriteSandName,
										density: vitriteDensity * 0.595 + 150,
										_data: [compositionFamilyName,"vitrite","wet_particulate"],
										metamorphite: metavitriteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements["packed_" + vitriteSandName] = {
										color: sandizeToHex(vitriteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: vitriteMeltingPoint,
										stateHigh: vitriteName,
										density: vitriteDensity * 0.59,
										breakInto: vitriteSandName,
										_data: [compositionFamilyName,"vitrite","packed_particulate"],
										metamorphite: metavitriteName,
										onTryMoveInto: function(pixel,otherPixel) { simplifiedSingleMetamorphiteMetamorphismOTMI(pixel,otherPixel) }
									};

									elements.water.reactions[vitriteSandName] = {
										"elem1": null, "elem2": "wet_" + vitriteSandName,
									};
							
							//Metavitrite
								elements[metavitriteName] = {
									color: metavitriteColor,
									behavior: behaviors.POWDER,
									category: "rock",
									state: "solid",
									tempHigh: metavitriteMeltingPoint,
									stateHigh: magmaName,
									density: metavitriteDensity,
									hardness: 0.75,
									breakInto: metavitriteGravelName,
									_data: [compositionFamilyName,"metavitrite","metamorphic_rock"],
								};

								elements.water.reactions[metavitriteName] = { "elem2": metavitriteGravelName, "chance": 0.00035 }

								elements[metavitriteGravelName] = {
									color: gravelizeToHex(metavitriteColor),
									behavior: behaviors.POWDER,
									category: "gravel",
									state: "solid",
									tempHigh: metavitriteMeltingPoint,
									stateHigh: magmaName,
									breakInto: metavitriteDustName,
									density: metavitriteDensity * 0.55,
									_data: [compositionFamilyName,"metavitrite","glass_shard"],
								};

								elements[metavitriteDustName] = {
									color: dustizeToHex(metavitriteName),
									behavior: behaviors.GAS,
									category: "rock dust",
									state: "gas",
									tempHigh: metavitriteMeltingPoint,
									stateHigh: ["fire",magmaName],
									reactions: {
										[metavitriteDustName]: {elem1: metavitriteSandName, elem2: null, chance: 0.003},
									},
									density: airDensity + (metavitriteDensity / 1000), //unmeasured value
									_data: [compositionFamilyName,"metavitrite","dust"],
								};

								elements.water.reactions[metavitriteGravelName] = { "elem2": twoPartRepeatedArray(metavitriteSandName,sandFormationReactionSpecificSandCount,"sand",sandFormationReactionRegularSandCount), "chance": 0.0005 };

								elements[metavitriteWallName] = {
									color: metavitriteColor,
									behavior: behaviors.WALL,
									category: "solid rock",
									state: "solid",
									tempHigh: metavitriteMeltingPoint,
									stateHigh: magmaName,
									density: metavitriteDensity,
									hardness: 0.8,
									breakInto: metavitriteName,
									_data: [compositionFamilyName,"metavitrite","solid_metamorphic_rock"],
								};

								elements.water.reactions[metavitriteWallName] = { "elem2": metavitriteName, "chance": 0.00035 }
								
								//Sand and sand variants
									elements[metavitriteSandName] = {
										color: sandizeToHex(metavitriteName,"normal"),
										behavior: behaviors.POWDER,
										category: "sand",
										state: "solid",
										tempHigh: metavitriteMeltingPoint,
										stateHigh: metavitriteName,
										density: metavitriteDensity * 0.595,
										_data: [compositionFamilyName,"metavitrite","particulate"],
									};

									elements["wet_" + metavitriteSandName] = {
										color: sandizeToHex(metavitriteName,"wet"),
										behavior: behaviors.STURDYPOWDER,
										category: "wet sand",
										reactions: {
											"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
										},
										state: "solid",
										tempHigh: 100,
										stateHigh: "packed_" + metavitriteSandName,
										tempLow: -50,
										stateLow:"packed_" + metavitriteSandName,
										density: metavitriteDensity * 0.595 + 150,
										_data: [compositionFamilyName,"metavitrite","wet_particulate"],
									};

									elements["packed_" + metavitriteSandName] = {
										color: sandizeToHex(metavitriteName,"packed"),
										behavior: behaviors.SUPPORT,
										category: "packed sand",
										state: "solid",
										tempHigh: metavitriteMeltingPoint,
										stateHigh: metavitriteName,
										density: metavitriteDensity * 0.59,
										breakInto: metavitriteSandName,
										_data: [compositionFamilyName,"metavitrite","packed_particulate"],
									};

									elements.water.reactions[metavitriteSandName] = {
										"elem1": null, "elem2": "wet_" + metavitriteSandName,
									};
							
							//Magma
						var magmaOldReactions = nicffunc_getReactions(magmaName);

						var magmaOldColor = elements.magma.color;

						elements[magmaName] = {
							reactions: {
								"ash": { "elem1": "molten_slag", "elem2": null },
								"dust": { "elem1": "molten_slag", "elem2": null },
							},
							_magmaCoolingPassToElement: {
								vitreous: [vitriteCoolingRateThreshold,vitriteName],
								aphanitic: [aphaniteCoolingRateThreshold,aphaniteName],
								phaneritic: [Infinity,phaneriteName],
								meltingPoints: {
									vitreous: vitriteMeltingPoint,
									vesicular: vesiculiteMeltingPoint,
									aphanitic: aphaniteMeltingPoint,
									phaneritic: phaneriteMeltingPoint,
								},
							},
							tick: function(pixel) {
								var coolingInfo = elements[pixel.element]._magmaCoolingPassToElement;
								magmaRateBasedCooling(
									pixel,
									Math.min(
										coolingInfo.meltingPoints.vitreous,
										coolingInfo.meltingPoints.vesicular,
										coolingInfo.meltingPoints.aphanitic,
										coolingInfo.meltingPoints.phaneritic
									) - 20,
									coolingInfo.vitreous[1],
									coolingInfo.vitreous[0],
									coolingInfo.aphanitic[1],
									coolingInfo.aphanitic[0],
									coolingInfo.phaneritic[1]
								);
							},
							"color": makeMoltenColor(phaneriteColor),
							"behavior": behaviors.MOLTEN,
							"temp": Math.max(phaneriteMeltingPoint,metaphaneriteMeltingPoint,aphaniteMeltingPoint,metaaphaniteMeltingPoint,vesiculiteMeltingPoint,metavesiculiteMeltingPoint,vitriteMeltingPoint,metavitriteMeltingPoint) + 100,
							"tempLow": -Infinity, //cosmetic info
							"stateLow": [aphaniteName,phaneriteName,vitriteName],
							"tempHigh": magmaBoilingPoint,
							"stateHigh": "vaporized_" + magmaName,
							"viscosity": magmaViscosity,
							"hidden": true,
							"state": "liquid",
							"category": "magmas",
							"density": magmaDensity,
							"_data": [compositionFamilyName,"magma","liquid"],
						};
						if(magmaOldReactions) {
							elements[magmaName].reactions = magmaOldReactions;
						};

						if(magmaName == "magma") {
							elements.magma.color = magmaOldColor;
						};

						elements[magmaName].reactions.foam = { "elem1": vesiculiteName, "elem2": vesiculiteName };

						elements["vaporized_" + magmaName] = {
							color: magmavaporizeToHex(elements[magmaName].color),
							behavior: behaviors.GAS,
							reactions: {
								["vaporized_" + magmaName]: { elem1: null, elem2: magmaCloudName, chance:0.3, "y":[0,15], "setting":"clouds" }
							},
							density: magmaDensity * 0.0028,
							temp: magmaBoilingPoint + 100,
							tempLow: magmaBoilingPoint,
							stateLow: magmaName,
							category: "magma vapor",
							state: "gas",
							hidden: true,
							_data: [compositionFamilyName,"magma","vaporized"],
						};

						vaporizedMagmas.push("vaporized_" + magmaName);

						elements[magmaCloudName] = {
							color: magmacloudizeToHex(elements[magmaName].color),
							behavior: [
								"XX|XX|XX",
								"M1%7|CH:" + magmaName + "%0.05|M1%7",
								"XX|XX|XX",
							],
							density: magmaDensity * 0.0021,
							temp: magmaBoilingPoint + 100,
							tempLow: Math.min(phaneriteMeltingPoint,metaphaneriteMeltingPoint,aphaniteMeltingPoint,metaaphaniteMeltingPoint,vesiculiteMeltingPoint,metavesiculiteMeltingPoint,vitriteMeltingPoint,metavitriteMeltingPoint) - 50,
							stateLow: rockCloudName,
							category: "magma cloud",
							state: "gas",
							_data: [compositionFamilyName,"magma","cloud"],
						};

						magmaClouds.push(magmaName + "_cloud");

						elements[rockCloudName] = {
							color: rockcloudizeToHex(elements[magmaName].color),
							behavior: [
								"XX|XX|XX",
								"M1%7|CH:" + [aphaniteName,aphaniteGravelName,aphaniteDustName].join(",") + "%0.05|M1%7",
								"XX|XX|XX",
							],
							density: magmaDensity * 0.0024,
							temp: Math.min(phaneriteMeltingPoint,metaphaneriteMeltingPoint,aphaniteMeltingPoint,metaaphaniteMeltingPoint,vesiculiteMeltingPoint,metavesiculiteMeltingPoint,vitriteMeltingPoint,metavitriteMeltingPoint) - 300,
							tempHigh: Math.min(phaneriteMeltingPoint,metaphaneriteMeltingPoint,aphaniteMeltingPoint,metaaphaniteMeltingPoint,vesiculiteMeltingPoint,metavesiculiteMeltingPoint,vitriteMeltingPoint,metavitriteMeltingPoint) - 50,
							stateHigh: magmaCloudName,
							category: "rock cloud",
							state: "gas",
							_data: [compositionFamilyName,"magma","cloud"],
						};

						rockClouds.push(rockCloudName);

				};

				function standaloneBrokenFormMaker(elementName,suffixWithoutUnderscore,addBreakIntoToSourceElement=false,category=null,density=null,tempHigh=null,stateHigh=null,breakInto=null) {	
					var newName = elementName + "_" + suffixWithoutUnderscore;
					elements[newName] = {
						color: gravelizeToHex(elements[elementName].color),
						behavior: behaviors.POWDER,
						state: "solid",
					};
					if(density !== null) {
						if(density == "auto") {
							elements[newName].density = (elements[elementName].density ?? 2000) * 0.55;
						} else {
							elements[newName].density = density;
						};
					};
					if(category !== null) {
						elements[newName].category = category;
					};
					if(tempHigh !== null) {
						if(tempHigh == "auto") {
							elements[newName].tempHigh = elements[elementName].tempHigh;
						} else {
							elements[newName].tempHigh = tempHigh;
						};
					};
					if(stateHigh !== null) {
						if(stateHigh == "auto") {
							elements[newName].stateHigh = elements[elementName].stateHigh;
						} else {
							elements[newName].stateHigh = stateHigh;
						};
					};
					if(breakInto !== null) {
						elements[newName].breakInto = breakInto;
					};

					if(addBreakIntoToSourceElement) {
						if(!elements[elementName].breakInto) {
							elements[elementName].breakInto = newName;
						} else {
							if(!(elements[elementName].breakInto instanceof Array)) {
								elements[elementName].breakInto = [elements[elementName].breakInto];
							};
							elements[elementName].breakInto.push(newName);
						};
					};

					return elements[newName];
				};

				function makeSandstoningElements(sandName) {

					var sandInfo = elements[sandName];
					if(!sandInfo) {
						throw new Error("No such element '" + sandName + "'");
					};

					var suspensionName, wetSandName, sedimentName, sandstoneName, dustName;

					switch(sandName) {
						case "dirt":
							suspensionName = "muddy_water";
							wetSandName = "mud"; //needs special code to not generate a wet dirt and instead use vanilla mud as the wet "sand" here
							sedimentName = "soil_sediment";
							sandstoneName = "soilstone"; //differentiated from mudstone, which is actually the *packed* dirt (analogously to sand's relationship to packed sand)
							dustName = "dirt_dust";
							break;
						case "rainbow_dirt":
							suspensionName = "rainbow_muddy_water";
							wetSandName = "rainbow_mud";
							sedimentName = "rainbow_soil_sediment";
							sandstoneName = "rainbow_soilstone";
							dustName = "rainbow_dirt_dust";
							break;
						case "crimsoil":
							suspensionName = "crimmuddy_water";
							wetSandName = "crimmud";
							sedimentName = "crimsoil_sediment";
							sandstoneName = "crimsoilstone";
							dustName = "crimsoil_dust";
							break;
						case 143: //sorry, i had to
						default:
							suspensionName = sandName + "y_water";
							wetSandName = "wet_" + sandName;
							sedimentName = sandName + "_sediment";
							sandstoneName = sandName + "stone";
							dustName = sandName.replace("_sand","_dust");
					};
					//console.log(sandName,suspensionName);

					//Water reaction to pick up the fine material (this is very simplified)

						elements.water.reactions[wetSandName] = {
							"elem1": suspensionName,
							"elem2": [wetSandName,wetSandName,wetSandName,suspensionName],
							chance: 0.01
						};

					//Sediment suspension

						//Color generation

						var sandColor = sandInfo.color;
						if(!(sandColor instanceof Array)) {
							sandColor = [sandColor];
						};

						var waterColor = "#2167ff";

						//console.log(sandColor);

						suspensionColor = sandColor.map(sandSubcolor => lerpColors(waterColor,sandSubcolor,"hex",weight1=0.5)); //lerp all with half water

						var sedimentColor = sandColor.map(sandSubcolor => convertHslObjects(sedimentHslOffset(normalizeColorToHslObject(sandSubcolor)),"hex"));

						//console.log(sandInfo);

						elements[suspensionName] = {
							color: suspensionColor,
							behavior: behaviors.LIQUID,
							tempHigh: 100,
							stateHigh: ["steam","steam",sandName],
							//tempLow: 0,
							//stateLow: "sandy_ice",
							category: "suspensions",
							reactions: {
								"dirt": { // React with (water reacts with dirt to make mud)
									"elem1": [null,null,wetSandName], // First element transforms into; in this case, water deletes itself
									"elem2": "mud", // Second element transforms into; in this case, dirt turns to mud
								},
								"water": { "elem1":"water", "elem2":suspensionName, "chance":0.025 }, //swap reaction
								"sand": { "elem1": [null,null,wetSandName], "elem2": wetSandName, }, 
								[suspensionName]: { "elem1":"water", "elem2":sedimentName, "chance": 0.001 }, 
								[wetSandName]: { "elem1": "water", "elem2":sedimentName, "chance": 0.0005 },
								//"salt": { "elem1": "salt_water", "elem2": null },
								//"sugar": { "elem1": "sugar_water", "elem2": null, },
								"dust": { "elem1": "dirty_water", "elem2": null, },
								"ash": { "elem1": "dirty_water", "elem2": null, },
								"cyanide": { "elem1": "dirty_water", "elem2": null, },
								//"carbon_dioxide": { "elem1": "seltzer", "elem2": null, "oneway":true },
								"sulfur": { "elem1": "dirty_water", "elem2": null, },
								"rat": { "elem1": "dirty_water", chance:0.005 },
								"plague": { "elem1": "dirty_water", "elem2": null, },
								"rust": { "elem1": "dirty_water", chance:0.005 },
								"fallout": { "elem1": "dirty_water", chance:0.25 },
								"radiation": { "elem1": "dirty_water", chance:0.25 },
								"uranium": { "elem1": "dirty_water", chance:0.25 },
								"rotten_meat": { "elem1": "dirty_water", chance:0.25 },
								"quicklime": { "elem1": [null,null,wetSandName], "elem2": "slaked_lime", },
								"rock": { "elem2": wetSandName, "chance": 0.00035 },
								"ruins": { "elem2": "rock", "chance": 0.00035 },
								"mudstone": { "elem2": "mud", "chance": 0.00035 },
								//"methane": { "elem1":"primordial_soup", "elem2":"primordial_soup", tempMin:60, charged:true },
								//"ammonia": { "elem1":"primordial_soup", "elem2":"primordial_soup", tempMin:60, charged:true },
								"fly": { "elem2":"dead_bug", "chance":0.1, "oneway":true },
								"firefly": { "elem2":"dead_bug", "chance":0.1, "oneway":true },
								"bee": { "elem2":"dead_bug", "chance":0.05, "oneway":true },
								"stink_bug": { "elem2":"dead_bug", "chance":0.1, "oneway":true },
							},
							state: "liquid",
							density: 1000 + (sandInfo.density * 0.06),
							conduct: 0.02,
							stain: 0.01,
							_data: [sandInfo?._data?.[0] ?? "unknown", sandInfo?._data?.[1] ?? "unknown", "suspension"],
						}

						if(elements[dustName]) {
							elements[dustName].reactions ??= {};
							elements[dustName].reactions.water = {
								elem1: null, elem2: suspensionName
							};
						};

					//Sediment element where lithification code resides

						elements[sedimentName] = {
							hidden: true,
							color: sedimentColor,
							hardness: 0.2,
							tick: function(pixel) {
								if(!tryMove(pixel,pixel.x,pixel.y+1)) {
									var newPixel = pixelMap[pixel.x]?.[pixel.y+1];
									if(!newPixel) {
										return;
									};
									var newElement = newPixel.element;
									var thisSandName = pixel.element.slice(0,-9); //ABCD_sand_sediment - _sediment
									var thisWetSandName = "wet_" + thisSandName;
									var thisSuspensionName = pixel.element.slice(0,-9) + "y_water";

									var sandstoneName = thisSandName + "stone";

									if(Math.random() < 0.005 && ["sediment","wet_particulate"].includes(elements[newElement]._data?.[2])) { //0.5% chance to swap with wet
										swapPixels(pixel,newPixel);
										return;
									};
									if(Math.random() < 0.001 && elements[newElement]._data?.[2] == "particulate") { //0.1% chance to give water away
										var newWetParticulateName = elements.water.reactions[newElement].elem2;
										if(elements[thisWetSandName] && elements[newWetParticulateName]) {
											//console.log(thisSandName);
											//console.log(newWetSandName);
											changePixel(pixel,thisSandName,false);
											changePixel(newPixel,newWetParticulateName,false);
										};
									};
									if(Math.random() < 0.001 && newElement == "water") { //0.1% chance to give dissolve in water
										if(elements[thisSuspensionName]) {
											//console.log(thisSuspensionName);
											changePixel(pixel,thisSuspensionName,false);
											changePixel(newPixel,thisSuspensionName,false);
										};
									};
									if(Math.random() < 0.001 && elements[newElement]._data?.[2] == "suspension") { //0.1% chance to sediment a suspension
										var newSedimentName = elements[newPixel.element].reactions[newPixel.element].elem2;
										//console.log(newSedimentName);
										if(elements[newSedimentName]) {
											changePixel(newPixel,newSedimentName,false);
										};
									};
								};

								//console.log(sandstoneName);
								sedimentation(pixel,sandstoneName)
							},
							tempHigh: sandInfo.tempHigh,
							stateHigh: sandInfo.stateHigh,
							category: "sediment",
							state: "solid",
							density: elements[wetSandName].density + 150,
							breakInto: sandName,
							_data: [sandInfo?._data?.[0] ?? "unknown", sandInfo?._data?.[1] ?? "unknown", "sediment"]
						};

					//Final rock

						//console.log(sandName);

						elements[sandstoneName] = {
							color: sandstonizeToHex(sandName), //["#b27853", "#d1a784", "#d1a784", "#d4996e"]
							behavior: behaviors.WALL,
							tempHigh: function() {
								switch(sandName) {
									case "dirt":
										return elements.dry_dirt.tempHigh;
									case "crimsoil":
										return elements.crimsoil.tempHigh;
									case "rainbow_dirt":
										return elements.rainbow_dirt.tempHigh;
									default:
										return elements[sandName].tempHigh
								}
							}(),
							stateHigh: function() {
								switch(sandName) {
									case "sand":
										return "glass";
									case "gabbro_sand":
										return "magma";
									case "dirt":
										return "hot_soilstone";
									case "rainbow_dirt":
										return "hot_rainbow_dirt";
									case "crimsoil":
										return "hot_crimsoilstone";
									default:
										var elementNameAssumingSandNameIsOfTheFormatFOOBAR_sand = sandName.slice(0,-5);
										var correspondingElementObject = elements[elementNameAssumingSandNameIsOfTheFormatFOOBAR_sand];
										if(!correspondingElementObject) {
											throw new Error(`makeSandstoningElements: Could not generate the sandstone for ${sandName} because this script couldn't find a corresponding parent rock ${elementNameAssumingSandNameIsOfTheFormatFOOBAR_sand}`);
										};
										return correspondingElementObject.stateHigh
								}
							}(), //apparently you can do this
							category: "solid rock",
							state: "solid",
							density: sandInfo.density * 1.5, //wide range
							hardness: 0.5,
							breakInto: sandName,
							maxColorOffset: 30,
							_data: [sandInfo?._data?.[0] ?? "unknown", (sandInfo?._data?.[1] ?? "unknown") + "_sandstone", "sedimentary_rock"],
						};
				};

				function makeNonSandSedimentationElements(particulateName,suspensionName,rockName) {

					var particulateInfo = elements[particulateName];
					if(!particulateInfo) {
						throw new Error("No such element '" + particulateName + "'");
					};

					var sedimentName = particulateName + "_sediment";

					//Water reaction to pick up the fine material (this is very simplified)

						elements.water.reactions[particulateName] = {
							"elem1": suspensionName,
							"elem2": [particulateName,particulateName,particulateName,suspensionName],
							chance: 0.001
						};

					//Sediment suspension

						//Color generation

						var particulateColor = particulateInfo.color;
						if(!(particulateColor instanceof Array)) {
							particulateColor = [particulateColor];
						};

						var waterColor = "#2167ff";

						//console.log(particulateColor);

						suspensionColor = particulateColor.map(sandSubcolor => lerpColors(waterColor,sandSubcolor,"hex",weight1=0.5)); //lerp all with half water

						var sedimentColor = particulateColor.map(sandSubcolor => convertHslObjects(sedimentHslOffset(normalizeColorToHslObject(sandSubcolor)),"hex"));

						//console.log(particulateInfo);

						elements[suspensionName] = {
							color: suspensionColor,
							behavior: behaviors.LIQUID,
							tempHigh: 100,
							stateHigh: ["steam","steam",particulateName],
							category: "suspensions",
							reactions: {
								"dirt": { // React with (water reacts with dirt to make mud)
									"elem1": [null,null,particulateName], // First element transforms into; in this case, water deletes itself
									"elem2": "mud", // Second element transforms into; in this case, dirt turns to mud
								},
								"water": { "elem1":"water", "elem2":suspensionName, "chance":0.025 }, //swap reaction
								"particulateName": { "elem1": [null,null,particulateName], "elem2": particulateName, }, 
								//"salt": { "elem1": "salt_water", "elem2": null },
								//"sugar": { "elem1": "sugar_water", "elem2": null, },
								"dust": { "elem1": "dirty_water", "elem2": null, },
								"ash": { "elem1": "dirty_water", "elem2": null, },
								"cyanide": { "elem1": "dirty_water", "elem2": null, },
								//"carbon_dioxide": { "elem1": "seltzer", "elem2": null, "oneway":true },
								"sulfur": { "elem1": "dirty_water", "elem2": null, },
								"rat": { "elem1": "dirty_water", chance:0.005 },
								"plague": { "elem1": "dirty_water", "elem2": null, },
								"rust": { "elem1": "dirty_water", chance:0.005 },
								"fallout": { "elem1": "dirty_water", chance:0.25 },
								"radiation": { "elem1": "dirty_water", chance:0.25 },
								"uranium": { "elem1": "dirty_water", chance:0.25 },
								"rotten_meat": { "elem1": "dirty_water", chance:0.25 },
								"quicklime": { "elem1": [null,null,particulateName], "elem2": "slaked_lime", },
								"rock": { "elem2": particulateName, "chance": 0.00035 },
								"ruins": { "elem2": "rock", "chance": 0.00035 },
								"mudstone": { "elem2": "mud", "chance": 0.00035 },
								//"methane": { "elem1":"primordial_soup", "elem2":"primordial_soup", tempMin:60, charged:true },
								//"ammonia": { "elem1":"primordial_soup", "elem2":"primordial_soup", tempMin:60, charged:true },
								"fly": { "elem2":"dead_bug", "chance":0.1, "oneway":true },
								"firefly": { "elem2":"dead_bug", "chance":0.1, "oneway":true },
								"bee": { "elem2":"dead_bug", "chance":0.05, "oneway":true },
								"stink_bug": { "elem2":"dead_bug", "chance":0.1, "oneway":true },
							},
							state: "liquid",
							density: 1000 + (particulateInfo.density * 0.06),
							conduct: 0.02,
							stain: 0.01,
							_data: [particulateInfo._data[0], particulateInfo._data[1], "suspension"],
						}

						elements[suspensionName].reactions[suspensionName] = { "elem1":"water", "elem2":sedimentName, "chance": 0.001 }, 
						elements[suspensionName].reactions[particulateName] = { "elem1": "water", "elem2":sedimentName, "chance": 0.0005 },

					//Sediment element where lithification code resides

						elements[sedimentName] = {
							hidden: true,
							color: sedimentColor,
							hardness: 0.2,
							tick: function(pixel) {
								if(!tryMove(pixel,pixel.x,pixel.y+1)) {
									var newPixel = pixelMap[pixel.x]?.[pixel.y+1];
									if(!newPixel) {
										return;
									};
									var newElement = newPixel.element;

									var particulateName = pixel.element.slice(0,-9); //ABCD_sand_sediment - _sediment

									var thisSuspensionName = elements[pixel.element]._sedimentationPassToElement.correspondingSuspension
									var rockName = elements[pixel.element]._sedimentationPassToElement.finalRock;

									if(Math.random() < 0.005 && ["sediment","wet_particulate"].includes(elements[newElement]._data?.[2])) { //0.5% chance to swap with wet
										swapPixels(pixel,newPixel);
										return;
									};
									if(Math.random() < 0.001 && elements[newElement]._data?.[2] == "particulate") { //0.1% chance to give water away

										var newWetParticulateName = elements.water.reactions[newElement].elem2;
										if(elements[particulateName] && elements[newWetParticulateName]) {
											changePixel(pixel,particulateName,false);
											changePixel(newPixel,newWetParticulateName,false);
										};
									};
									if(Math.random() < 0.001 && newElement == "water") { //0.1% chance to dissolve in water
										if(elements[thisSuspensionName]) {
											//console.log(thisSuspensionName);
											changePixel(pixel,thisSuspensionName,false);
											changePixel(newPixel,thisSuspensionName,false);
										};
									};
									if(Math.random() < 0.001 && elements[newElement]._data?.[2] == "suspension") { //0.1% chance to sediment a suspension
										//new sediment should be the elem2 of a suspension's reaction with itself
										var newSedimentName = elements[newPixel.element].reactions[newPixel.element].elem2;
										//console.log(newSedimentName);
										if(elements[newSedimentName]) {
											changePixel(newPixel,newSedimentName,false);
										};
									};
								};

								//console.log(rockName);
								sedimentation(pixel,rockName)
							},
							tempHigh: particulateInfo.tempHigh,
							stateHigh: particulateInfo.stateHigh,
							_sedimentationPassToElement: {
								finalRock: rockName,
								correspondingSuspension: suspensionName,
							},
							category: "sediment",
							state: "solid",
							density: elements[particulateName].density + 150,
							breakInto: particulateName,
							_data: [particulateInfo._data[0], particulateInfo._data[1], "sediment"],
						};

					//Final rock

						//console.log(particulateName);

						if(rockName !== "limestone") {
							elements[rockName] = {
								color: sandstonizeToHex(particulateName), //["#b27853", "#d1a784", "#d1a784", "#d4996e"]
								behavior: behaviors.WALL,
								tempHigh: particulateInfo.tempHigh,
								stateHigh: particulateInfo.stateHigh, 
								category: "solid rock",
								state: "solid",
								density: particulateInfo.density * 1.5, //wide range
								hardness: 0.7,
								breakInto: particulateName,
								maxColorOffset: 30,
								_data: [particulateInfo._data[0], "rock", "sedimentary_rock"],
							};
						};
				};

				newPowder("calcite","#f5ecd0",2711,825,["carbon_dioxide","quicklime"],"calcium_carbonate_dust");
				newPowder("aragonite","#e3c58d",2830,825,["carbon_dioxide","quicklime"],"calcium_carbonate_dust");
				newPowder("vaterite","#e8ebd8",2540,825,["carbon_dioxide","quicklime"],"calcium_carbonate_dust");
				newPowder("calcium_carbonate_dust","#f7f7f5",2930,825,["carbon_dioxide","quicklime"]);

				//i forgot what data[1]s mean and at this point it doesn't really matter
				elements.calcite._data = ["calcium","crystalline","mineral"];
				elements.aragonite._data = ["calcium","crystalline","mineral"];
				elements.vaterite._data = ["calcium","crystalline","mineral"];
				elements.calcium_carbonate_dust._data = ["calcium","crystalline","particulate"];
				elements.limestone._data = ["calcium", "sedimentary", "sedimentary_rock"];

				elements.aragonite.tick = function(pixel) {
					if(Math.random() < (0.001 + Math.max(0,(pixel.temp - 300) / 100))) {
						changePixel(pixel,"calcite",false);
					};
				};

				elements.vaterite.tick = function(pixel) {
					if(Math.random() < (0.01 + Math.max(0,(pixel.temp - 30) / 10))) {
						changePixel(pixel,"calcite",false);
					};
				};

				makeNonSandSedimentationElements("calcium_carbonate_dust","calcium_carbonate_solution","limestone")

				var calcitoids = ["calcite","aragonite","vaterite"];
				for(i = 0; i < calcitoids.length; i++) {
					var mineral = calcitoids[i];
					elements.water.reactions[mineral] = {
						"elem1":"calcium_carbonate_solution",
						"elem2":[mineral,mineral,mineral,"calcium_carbonate_solution"],
						"chance":0.004
					};

					elements.seltzer.reactions[mineral] = {
						"elem1":"calcium_carbonate_solution",
						"elem2":[mineral,mineral,mineral,"calcium_carbonate_solution"],
						"chance":0.02
					};
				};

				runAfterLoad(function() {
					for(i = 0; i < sands.length; i++) {
						switch(sands[i]) {
							case "dirt":
								sandSuspensions.push("muddy_water");
								sandSediments.push("soil_sediment");
								sandstones.push("soilstone");
								break;
							case "crimsoil":
								sandSuspensions.push("crimmuddy_water");
								sandSediments.push("crimsoil_sediment");
								sandstones.push("crimsoilstone");
								break;
							case "rainbow_dirt":
								sandSuspensions.push("rainbow_muddy_water");
								sandSediments.push("rainbow_soil_sediment");
								sandstones.push("rainbow_soilstone");
								break;
							default:
								sandSuspensions.push(sands[i] + "y_water");
								sandSediments.push(sands[i] + "_sediment");
								sandstones.push(sands[i] + "stone");
						};

						makeSandstoningElements(sands[i]);
					};

					elements.sand.category = "sand";

					elements.gravel.category = "gravel";

					elements.rock.category = "rock";

					elements.wet_sand.category = "wet sand";

					elements.packed_sand.category = "packed sand";

					elements.clay._data = ["clay","clay","particulate"],
					makeNonSandSedimentationElements("clay","clay_water","shale");
					elements.shale.color = ["#787b80","#535557","#695e58", "#696969", "#6b5d5b"];
					elements.shale.maxColorOffset = 15;
					elements.shale.tempHigh = 200; //shale does get baked (https://pubs.usgs.gov/pp/0108a/report.pdf), but it feels wrong for it to happen so soon
					elements.shale.behavior = behaviors.POWDER;
					elements.shale.category = "solid rock";

					for(fei = 0; fei < sandSuspensions.length; fei++) {
						var suspensionToAddReactionTo = sandSuspensions[fei];
						//console.log(suspensionToAddReactionTo);
						if(!(elements[suspensionToAddReactionTo])) {
							console.error(`MI ${suspensionToAddReactionTo}`);
							continue
						};
						elements[suspensionToAddReactionTo].reactions ??= {};
						for(sei = 0; sei < sandSuspensions.length; sei++) {
							var suspensionToReactWith = sandSuspensions[sei];
							var firstSedimentName = suspensionToAddReactionTo.replace("y_water","_sediment");
							var secondSedimentName = suspensionToReactWith.replace("y_water","_sediment");
							elements[suspensionToAddReactionTo].reactions[suspensionToReactWith] = {
								elem1: "water", "elem2": [firstSedimentName,secondSedimentName], "chance": 0.001, 
							};
						};

						for(sej = 0; sej < wetSands.length; sej++) {
							var wetSandToReactWith = wetSands[sej];
							var firstSedimentName = suspensionToAddReactionTo.replace("y_water","_sediment");
							var secondSedimentName = wetSandToReactWith.replace("wet_","") + "_sediment";
							elements[suspensionToAddReactionTo].reactions[wetSandToReactWith] = {
								elem1: "water", "elem2": [firstSedimentName,secondSedimentName], "chance": 0.0005,
							};
						};
					};

					//lithificationElements = sandSediments.concat(sandstones);

					for(fei = 0; fei < vaporizedMagmas.length; fei++) {
						var vaporToAddReactionTo = vaporizedMagmas[fei];
						//console.log(vaporToAddReactionTo);
						elements[vaporToAddReactionTo].reactions ??= {};
						for(sei = 0; sei < vaporizedMagmas.length; sei++) {
							var vaporToReactWith = vaporizedMagmas[sei];
							var firstCloudName = vaporToAddReactionTo.replace("vaporized_","") + "_cloud";
							var secondCloudName = vaporToReactWith.replace("vaporized_","") + "_cloud";
							elements[vaporToAddReactionTo].reactions[vaporToReactWith] = {
								elem1: null, "elem2": [firstCloudName,secondCloudName], "chance": 0.3, y: [0,15] 
							};
						};

						for(sej = 0; sej < magmaClouds.length; sej++) {
							var cloudToReactWith = magmaClouds[sej];
							var firstCloudName = vaporToAddReactionTo.replace("vaporized_","") + "_cloud";
							elements[vaporToAddReactionTo].reactions[cloudToReactWith] = {
								elem1: firstCloudName, "chance": 0.4, y: [0,15]
							};
						};
					};

					newPowder("silica","#faf9f0",2196,1713).hardness = 0.7;
					elements.silica.reactions = {
						intermediate_felsic_magma: { elem1: "felsic_magma", elem2: "felsic_magma", chance: 0.9 },
						intermediate_magma: { elem1: "intermediate_felsic_magma", elem2: "intermediate_felsic_magma", chance: 0.9 },
						magma: { elem1: "intermediate_magma", elem2: "intermediate_felsic_magma", chance: 0.9 },
						ultramafic_magma: { elem1: "magma", elem2: "magma", chance: 0.9 },
					};

					elements.molten_silica = {
						tempHigh: 2950,
						viscosity: 1e14, //idk lol
						reactions: {
							intermediate_felsic_magma: { elem1: "felsic_magma", elem2: "felsic_magma", chance: 0.9 },
							intermediate_magma: { elem1: "intermediate_felsic_magma", elem2: "intermediate_felsic_magma", chance: 0.9 },
							magma: { elem1: "intermediate_magma", elem2: "intermediate_felsic_magma", chance: 0.9 },
							ultramafic_magma: { elem1: "magma", elem2: "magma", chance: 0.9 },
						},
					};

					elements.felsic_magma.reactions ??= {};
					elements.felsic_magma.reactions.intermediate_magma = {
						elem1: "intermediate_felsic_magma", elem2: "intermediate_felsic_magma", chance: 0.8,
					};

					elements.intermediate_felsic_magma.reactions ??= {};
					elements.intermediate_felsic_magma.reactions.magma = {
						elem1: "intermediate_magma", elem2: "intermediate_magma", chance: 0.7,
					};

					elements.felsic_magma.reactions ??= {};
					elements.felsic_magma.reactions.magma = { //mafic magma
						elem1: "intermediate_magma", elem2: "intermediate_magma", chance: 0.7,
					};

					elements.felsic_magma.reactions ??= {};
					elements.felsic_magma.reactions.ultramafic_magma = { //mafic magma
						elem1: "intermediate_magma", elem2: "magma", chance: 0.6,
					};

					elements.intermediate_magma.reactions ??= {};
					elements.intermediate_magma.reactions.ultramafic_magma = { //mafic magma
						elem1: "magma", elem2: "magma", chance: 0.6,
					};

					elements.molten_dirt.tempHigh = 3313;
					var rockStateHigh = JSON.parse(JSON.stringify(vaporizedMagmas));
					//only real magmas in dirt
					if(rockStateHigh.includes("vaporized_nellish_magma")) { 
						rockStateHigh.splice(rockStateHigh.indexOf("vaporized_nellish_magma"));
					};
					if(rockStateHigh.includes("vaporized_rainbow_magma")) { 
						rockStateHigh.splice(rockStateHigh.indexOf("vaporized_rainbow_magma"));
					};
					if(rockStateHigh.includes("vaporized_crimson_magma")) { 
						rockStateHigh.splice(rockStateHigh.indexOf("vaporized_crimson_magma"));
					};
					if(rockStateHigh.includes("vaporized_blackpinkinitic_magma")) { 
						rockStateHigh.splice(rockStateHigh.indexOf("vaporized_blackpinkinitic_magma"));
					};
					elements.molten_dirt.stateHigh = rockStateHigh; //assuming mixture

					for(var sandIndex in sands) {
						sandIndex = parseInt(sandIndex);
						var sandName = sands[sandIndex];
						var usedSandColor = elements[sandName].color;
						if(!(usedSandColor instanceof Array)) {
							usedSandColor = [usedSandColor];
						};

						var newSandyClayColor = usedSandColor.map(subcolor => lerpColors(subcolor,elements.clay.color,"hex",weight1=0.5));

						var newSandyLoamColor = [];
						for(var dirtSubcolorIndex in elements.dirt.color) {
							dirtSubcolorIndex = parseInt(dirtSubcolorIndex);
							dirtSubcolor = elements.dirt.color[dirtSubcolorIndex];
							//for each dirt subcolor, to the final new color concatenate the result of mapping each of the sand color's subcolors to one of dirt's subcolors
							newSandyLoamColor = newSandyLoamColor.concat(usedSandColor.map(subcolor => lerpColors(subcolor,dirtSubcolor,"hex",weight1=0.6)));
						};

						var newLoamySandColor = [];
						for(var dirtSubcolorIndex in elements.dirt.color) {
							dirtSubcolorIndex = parseInt(dirtSubcolorIndex);
							dirtSubcolor = elements.dirt.color[dirtSubcolorIndex];
							//for each dirt subcolor, to the final new color concatenate the result of mapping each of the sand color's subcolors to one of dirt's subcolors
							newLoamySandColor = newLoamySandColor.concat(usedSandColor.map(subcolor => lerpColors(subcolor,dirtSubcolor,"hex",weight1=0.4)));
						};

						var newSandyClayLoamColor = newSandyLoamColor.map(subcolor => lerpColors(subcolor,elements.clay.color,"hex",weight1=2/3));

						var newSandyLoamColor = elements.dirt.color.map(subcolor => lerpColors(subcolor,elements.clay.color,"hex",weight1=0.5));
					}

					var newClayLoamColor = elements.dirt.color.map(subcolor => changeHue(lerpColors(subcolor,elements.clay.color,"hex",weight1=0.5),0.9,"multiply","hex"));
					var newDryClayLoamColor = newClayLoamColor.map(x => changeSaturation(changeLuminance(x,15,"add","hsljson"),0.9,"multiply","hex"));
					newPowder("clay_loam",newClayLoamColor,1500,100,"dry_clay_loam",["dirt","clay_soil"]);

					elements.clay_loam._data = ["clay_loam","soil","particulate"];

					//manual addition due to autogen fuckery and i don't feel like calling in runAfterAutogen
					elements.molten_clay_loam = {
						"behavior": behaviors.MOLTEN,
						"hidden": true,
						"state": "liquid",
						"category": "states",
						"color": [ "rgb(255,217,75)", "rgb(255,174,75)", "rgb(255,130,0)", "rgb(255,205,70)", "rgb(255,164,70)", "rgb(255,123,0)", "rgb(255,202,68)", "rgb(255,162,68)", "rgb(255,121,0)", "rgb(255,210,72)", "rgb(255,168,72)", "rgb(255,126,0)" ].map(x => convertColorFormats(x,"hex")),
						"tempLow": 1250,
						"stateLow": "dry_clay_loam",
						"density": 1350,
						"viscosity": 10000
					};

					newPowder("dry_clay_loam",newDryClayLoamColor,1500,1250,"molten_clay_loam",["dry_dirt","clay_soil"]);

					elements.dry_clay_loam.data = ["clay_loam","dry_soil","particulate"];

					//newPowder(name,color,density=null,tempHigh=null,stateHigh=null,breakInto=null)
				});

		//Terrain

			//Soils

				//Wet

					//Wet Clay

						//TODO

					//Wet Silty clay

						//TODO

					//Wet Silty Clay Loam

						//TODO

					//Wet Silty Loam

						//TODO

					//Wet Silt

						//TODO

					//Wet Clay Loam

						//TODO

					//Wet Medium Loam

						//Mud exists

					//Wet Sandy Clay

						//TODO

					//Wet Sandy Clay Loam

						//TODO

					//Wet Sandy Loam

						//TODO

					//Wet Loamy Sand

						//TODO

					//Wet Sand

						//Wet Sand exists

				//Permafrost

					//Clay Permafrost

						//TODO

					//Silty clay Permafrost

						//TODO

					//Silty Clay Loam Permafrost

						//TODO

					//Silty Loam Permafrost

						//TODO

					//Silt Permafrost

						//TODO

					//Clay Loam Permafrost

						//TODO

					//Medium Loam Permafrost

						//Permafrost exists

					//Sandy Clay Permafrost

						//TODO

					//Sandy Clay Loam Permafrost

						//TODO

					//Sandy Loam Permafrost

						//TODO

					//Loamy Sand Permafrost

						//TODO

					//Sand Permafrost

						//TODO

				//Radioactive (unmoved/TODO)

					//Dry

						//Radioactive Clay

							//Clay exists

						//Radioactive Silty clay

							//TODO

						//Radioactive Silty Clay Loam

							//TODO

						//Radioactive Silty Loam

							//TODO

						//Radioactive Silt

							//TODO

						//Radioactive Clay Loam

							//Clay Soil exists

						//Radioactive Medium Loam

							//Dirt exists

						//Radioactive Sandy Clay

							//TODO

						//Radioactive Sandy Clay Loam

							//TODO

						//Radioactive Sandy Loam

							//TODO

						//Radioactive Loamy Sand

							//TODO

						//Radioactive Sand

							//Sand exists

					//Wet

						//Radioactive Wet Clay

							//TODO

						//Radioactive Wet Silty clay

							//TODO

						//Radioactive Wet Silty Clay Loam

							//TODO

						//Radioactive Wet Silty Loam

							//TODO

						//Radioactive Wet Silt

							//TODO

						//Radioactive Wet Clay Loam

							//TODO

						//Radioactive Wet Medium Loam

							//Mud exists

						//Radioactive Wet Sandy Clay

							//TODO

						//Radioactive Wet Sandy Clay Loam

							//TODO

						//Radioactive Wet Sandy Loam

							//TODO

						//Radioactive Wet Loamy Sand

							//TODO

						//Radioactive Wet Sand

							//Wet Sand exists

					//Permafrost

						//Radioactive Clay Permafrost

							//TODO

						//Radioactive Silty clay Permafrost

							//TODO

						//Radioactive Silty Clay Loam Permafrost

							//TODO

						//Radioactive Silty Loam Permafrost

							//TODO

						//Radioactive Silt Permafrost

							//TODO

						//Radioactive Clay Loam Permafrost

							//TODO

						//Radioactive Medium Loam Permafrost

							//Permafrost exists

						//Radioactive Sandy Clay Permafrost

							//TODO

						//Radioactive Sandy Clay Loam Permafrost

							//TODO

						//Radioactive Sandy Loam Permafrost

							//TODO

						//Radioactive Loamy Sand Permafrost

							//TODO

						//Radioactive Sand Permafrost

							//TODO

			//Rocks

				//Igneous

					//Felsic

						newIgneousCompositionFamily(
							"felsic",

							1e12, 2200, -85, -20, 2850,

							//Not much data on metamorphites besides gneiss
							"granite", ["#F3C3AD", "#F0AB75", "#DDA888", "#BD927E", "#998473", "#5C5E53", "#BD8366"], 1215, 2691,
							"gneiss", ["#C5C1B4", "#605A5E", "#424449", "#EDECE9", "#73503A", "#92866F"], 1215, 2750,

							"rhyolite", ["#A67153","#BF967E","#D9B5A0","#8C533E","#C99F86","#C5997E","#BB8A69"], 800, 1254,
							"metarhyolite", ["#C0C7D3","#CAD0D9","#AEB7B7","#728189","#798B96","#B09F98","#515155"], 800, 2584, //https://www.researchgate.net/figure/Physical-properties-of-the-metarhyolites_tbl2_245002845 also there are pictures yay

							"pumice", ["#ebe1c3", "#ada386", "#f0bd9e", "#ab846c", "#bfbebd", "#75726f", "#f5e595", "#ab9e60", "#ad683d", "#633d25", "#6e6d6d", "#3b3a39"], 1350, 641,
							//it is said to flatten out and have smaller vesicles but the color is pulled out of my ass
							"metapumice", ["#a6a295", "#787a6f", "#8f847e", "#917c6e", "#858382", "#696460", "#8a6d5c", "#6e5749", "#5c5b55", "#53594f"], 1350, 2328,

							vitreousFelsicName, ["#252422", "#171616", "#161915", "#161018"], 1000, 2488,
							//if metamorphism sometimes involves recrystallization and obsidian is the way it is due to being amorphous and lacking a crystal structure then perhaps obsidian might be somewhat like granite with its new crystals
							"meta" + vitreousFelsicName, ["#453f3c", "#1f1a18", "#36342b", "#1c1519", "#3d3133", "#1f1b1a", "#453a32"], 1000, 2513,

							7,3
						);

						elements.water.reactions.obsidian_shard.elem2 = ["obsidian_sand","obsidian_sand","obsidian_sand","sand","sand"]
						elements.obsidian_sand.color = ["#3b3730", "#211e1e", "#293321", "#31133b"];
						elements.obsidian_shard.desc = '<a href="https://ftbwiki.org/Crushed_Obsidian">crushed obsidian</a> my beloved';

					//Intermediate felsic

						newIgneousCompositionFamily(
							"intermediate_felsic",

							1e10, 2320, -95, -23, 2900,

							"granodiorite", ["#B1AB9D", "#262001", "#A6A292", "#D6C5BC", "#F2F2F2", "#DED8C2", "#978871", "#A8AAA7"], 1277, 2644, //Color from image: By Rudolf Pohl - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=7788350; melting point made-up/interpolated from granite and diorite; last 2 digits of density are made up again
							"metagranodiorite", ["#F3EDDC","#F0ECD8","#EDECDC","#D0C9A9","#BDB192","#BBA27A","#86744E","#323026","#262417","#202012"], 1277, 2711,

							"dacite", ["#D9CCC5", "#F2E9E4", "#877670", "#A69B97"], 1050, 2654, //https://books.google.ca/books?id=ObUPAAAAIAAJ&pg=PA181&lpg=PA181&dq=dacite+specific+gravity&source=bl&ots=qn8B4sirWi&sig=Wp_MHqPuUGPNQobcuNP5c5wqkpU&hl=en&sa=X&ei=cimtUaH8Eab7yAH8joDABQ#v=onepage&q=dacite%20specific%20gravity&f=false
							"metadacite", ["#91847d", "#e0c9bc", "#735a56", "#bfa59b", "#696563"], 1050, 2727,

							"intermediate_pumice", ["#dbd4bd", "#b5ad94", "#e3ceb6", "#bda891", "#c2c2c2", "#a1a1a1", "#e6c8a1", "#b8a48c"], 1190, 991,
							"intermediate_metapumice", ["#777868", "#5a5c51", "#82756f", "#6e6057", "#96918f", "#70665e"], 1190, 2623,

							vitreousInterfelsicName, ["#4f4b42", "#474646", "#4a4d49", "#342f36"], 1040, 2640,
							"meta" + vitreousInterfelsicName, ["#3d3c39", "#696262", "#313630", "#625966"], 1040, 2772,

							6,4
						);

					//Intermediate

						newIgneousCompositionFamily(
							"intermediate",

							1e8, 2450, -105, -26, 2950,

							"diorite", ["#E1E1E1","#B0A696","#707271","#434459","#242424"], 1300, 2822, //Extracted from image and blended; Michael C. Rygel - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=31124755 https://commons.wikimedia.org/w/index.php?curid=7788350; last 2 digits made up again
							"metadiorite", ["#D1D2D7","#C3C2AF","#AEACB1","#A1A29D","#C3C4BC","#C3C9CA","#B5AEA4","#B6AC91","#AEA582","#5A6992"], 1300, 2929,

							"andesite", ["#6F7575", "#C5C9CB", "#818787", "#797F7F", "#B5B9BA", "#6D7371", "#909696"], 1215, 2474, //https://books.google.ca/books?id=ObUPAAAAIAAJ&pg=PA181&lpg=PA181&dq=dacite+specific+gravity&source=bl&ots=qn8B4sirWi&sig=Wp_MHqPuUGPNQobcuNP5c5wqkpU&hl=en&sa=X&ei=cimtUaH8Eab7yAH8joDABQ#v=onepage&q=dacite%20specific%20gravity&f=false
							"metaandesite", ["#5b5c5b", "#a3a6a2", "#6e665e", "#b39b92", "#756763", "#91817d", "#73524d"], 1215, 2553,

							"scoria", ["#594545", "#573b31", "#522e28"], 1085, 2550,
							"metascoria", ["#403835","#75574c","#4f302b","#8a7c75"], 1085, 2670,

							vitreousIntermediateName, ["#636059", "#707070", "#5f615f", "#504b52"], 1085, 2710,
							"meta" + vitreousIntermediateName, ["#4a4845", "#75716e", "#43453f", "#5e4b53", "#66554d"], 1085, 2744,

							5,5
						);

						elements.scoria_gravel.density = 2790;

					//Mafic

						elements.rock.name = "Gabbro";
						elements.rock.tempHigh = 1200;
						elements.rock.density = 3300;
						elements.rock.breakInto = ["gravel"];
						elements.gravel.breakInto = ["gabbro_dust"];
						elements.gravel.name = "Gabbro Gravel";
						delete elements.wet_sand.reactions.gravel;
						elements.rock._data = ["mafic","phanerite","igneous_rock"],

						elements.magma.name = "mafic magma";
						elements.magma.density = 2650;
						elements.magma.category = "magmas";
						elements.magma._magmaCoolingPassToElement = {
							vitreous: [-115,vitreousMaficName],
							aphanitic: [-29,"basalt"],
							phaneritic: [Infinity,"gabbro"],
							meltingPoints: {
								vitreous: 1200,
								vesicular: 1298,
								aphanitic: 1122,
								phaneritic: 1200,
							},
						},

						elements.magma.tick = function(pixel) {
							magmaRateBasedCooling(pixel,1180,vitreousMaficName,-115,"basalt",-29,"rock");
						};
						elements.magma.temp = 1400;
						elements.magma.tempLow = -Infinity;
						elements.magma.stateLow = ["basalt","gabbro",vitreousMaficName]
						elements.magma.reactions ??= {};
						elements.magma.reactions.foam = { "elem1": "mafic_scoria", "elem2": "mafic_scoria" };
						elements.magma._data = ["mafic","magma","liquid"],

						elements.basalt.tempHigh = 1122;
						elements.basalt.density = 2949;
						elements.basalt.breakInto = "basalt_gravel",
						elements.rock._data = ["mafic","phanerite","igneous_rock"],
						elements.gravel._data = ["mafic","phanerite","igneous_gravel"],
						elements.basalt._data = ["mafic","aphanite","igneous_rock"],
						elements.sand._data = ["silica","crystalline","particulate"],
						elements.wet_sand._data = ["silica","crystalline","wet_particulate"],
						elements.packed_sand._data = ["silica","crystalline","packed_particulate"],

						newIgneousCompositionFamily(
							"mafic",
							
							10000, 2200, -115, -29, 3000,

							"rock", ["#808080","#4f4f4f","#949494"], 1474, 3300,
							"metagabbro", ["#F6F6F5", "#EEEFEC", "#E7E6DD","#C0BBA3","#A9ABA7", "#8A8C8C", "#727271", "#61635F", "#595A59", "#454641", "#4E514A"], 1474, 3350,

							"basalt", ["#2e2e2e","#333333","#3d3d3d"], 1122, 2949,
							"metabasalt", ["#292e26","#474d3d","#2e2e29","#4a574f"], 1122, 3070,

							"mafic_scoria", ["#756666", "#695751", "#737272"], 1298, 2717,
							"mafic_metascoria", ["#856d6d","#4f4139","#8c8373","#494a39"], 1298, 2773,

							vitreousMaficName, ["#6e615d", "#706767", "#6a6b63", "#6e5e68"], 1200, 2900,
							"meta" + vitreousMaficName, ["#7a685d", "#3c4235", "#7c7869", "#3f3138"], 1200, 2991,

							3,7
						);

						elements.mafic_scoria.tempHigh = 1298;
						elements.mafic_scoria.stateHigh = "magma";
						elements.mafic_scoria_gravel.density = 2993;
						elements.basalt.behavior = behaviors.STURDYPOWDER;
						elements.metabasalt.behavior = behaviors.STURDYPOWDER;

					//Ultramafic

						newIgneousCompositionFamily(
							"ultramafic",

							800, 2800, -125, -32, 3050,

							"peridotite", ["#848a5e","#68785b","#8a9967","#3f403d","#33312e","#4c4f45"], 1400, 3347, //appr from https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/GL003i009p00509#:~:text=Abstract,and%20the%20bulk%20rock%20analyses.
							"metaperidotite", ["#7d604f","#959c98","#454443","#363432","#5e4840"], 1400, 3404,

							"komatiite", ["#6e7d6e","#858c8a","#768270","#767a77"], 1600, 3100, 
							"metakomatiite", ["#AEB5AE","#A9B8B5","#7B8881","#858B87","#949F97","#66655d","#5e4d48"], 1600, 3066,

							"ultramafic_scoria", ["#636555", "#6a6751", "#828382"], 1400, 2924,
							"ultramafic_metascoria", ["#574e47", "#6a7357", "#3b3430", "#4d4939"], 1400, 3003,

							vitreousUltramaficName, ["#6e6d5e", "#5f6659", "#54574b", "#665d55"], 1300, 3200,
							"meta" + vitreousUltramaficName, ["#4a443d", "#5e5e4a", "#3a4036", "#4d524f"], 1300, 3266,

							2,8
						);

						elements.ultramafic_scoria_gravel.density = 3132;
						elements.basalt_gravel._data = ["mafic","aphanite","igneous_gravel"],

						elements.limestone_gravel = {
							color: ["#c7baa1", "#e8d8b7", "#fcf3d7", "#fffce6"],
							behavior: behaviors.POWDER,
							tempHigh: 825,
							stateHigh: "quicklime",
							category: "gravel",
							state: "solid",
							density: 1380,
							hardness: 0.16,
							breakInto: ["quicklime","calcium","dust"],
						}

						elements.limestone.breakInto = "limestone_gravel";

						elements.worm.reactions.limestone_gravel = { "elem2":"calcium", "chance":0.1 },
						elements.acid.reactions.limestone_gravel = { "elem1":"neutral_acid", "elem2":null },

						newPowder("aluminum_oxide","#f2f2f2",3987,2072).hardness = 0.93;

						elements.molten_aluminum_oxide = {
							tempHigh: 2977,
						};

						newPowder("sulfur_trioxide","#ededed",1995,16.9).reactions = {
							water: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
							steam: { elem1: "sulfuric_acid", elem2: "acid" },
							ice: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
							snow: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
							packed_snow: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
							slush: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
						}; elements.sulfur_trioxide.temp = 10;

						elements.molten_sulfur_trioxide = {
							color: "#c0c0c0",
							behavior: behaviors.LIQUID,
							density: 1920,
							viscosity: 5, //idk idc
							tempHigh: 45,
							reactions: {
								water: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" }, //no H2SO4, hydronium doesn't really seem to be its own substance
								steam: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								ice: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								snow: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								packed_snow: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								slush: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
							},
						};

						elements.sulfur_trioxide_gas = {
							color: "#c0c0c0",
							density: 2.3, //idk idc
							reactions: {
								water: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" }, //no H2SO4, hydronium doesn't really seem to be its own substance
								steam: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								ice: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								snow: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								packed_snow: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
								slush: { elem1: "sulfuric_acid", elem2: "sulfuric_acid" },
							},
						};

						var tempaaa = {
							sulfur_trioxide: "value doesn't matter",
							molten_sulfur_trioxide: "stan loona",
							sulfur_trioxide_gas: "aaaaaaa"
						};

						delete elements.concrete.tempHigh;
						delete elements.concrete.stateHigh;
						if(elements.hanging_concrete) {
							delete elements.hanging_concrete.tempHigh;
							delete elements.hanging_concrete.stateHigh;
						};
						if(elements.crumbling_concrete) {
							delete elements.crumbling_concrete.tempHigh;
							delete elements.crumbling_concrete.stateHigh;
						};
						if(elements.attach_concrete) {
							delete elements.attach_concrete.tempHigh;
							delete elements.attach_concrete.stateHigh;
						};
						delete elements.quicklime.stateHigh;
						elements.quicklime.tempHigh = 2572;
						elements.molten_quicklime = {
							tempHigh: 2850
						};
						elements.concrete.properties ??= {};
						elements.concrete.properties.composition = "mafic";
						elements.concrete.tick = function(pixel) {
							pixel.composition ??= "mafic";
							pixel.wet ??= Math.random() < 0.03 ? 1 : 0;
							pixel.frozen ??= false;
							pixel.didColorChange ??= 0;
							pixel.lastTemperatures ??= [];

							pixel.lastTemperatures.push(pixel.temp); //due to how it's structured, last temp will always equal pixel.temp;

							while(pixel.lastTemperatures.length > 2) {
								pixel.lastTemperatures.shift();
							};

							var overallTemperatureChangeRate = (pixel.temp - pixel.lastTemperatures[0]) / (pixel.lastTemperatures.length - 1);

							var magmaName = (pixel.composition == "mafic") ? "magma" : pixel.composition + "_magma";
							var magmaTempHigh = Math.max(...Object.values(elements[magmaName]._magmaCoolingPassToElement.meltingPoints));

							if(pixel.wet && !pixel.frozen && pixel.temp < 0) {
								if(Math.random() < (pixel.wet / 25)) { //if unfrozen, crack apart (freezing damage) with small chance, and then mark survivors as frozen
									//console.log("freezing");
									explodeAt(pixel.x,pixel.y,2,"ice");
									if(!pixel) { //if deleted
										return;
									};
									if(pixel.element !== "ice") { //chance to just change to ice after the fact if survivor
										if(Math.random() < (pixel.wet / 8)) {
											changePixel(pixel,"ice",false);
										};
									};
									if(pixel.element !== "concrete") { //if changed, stop execution
										return;
									};
								};
								//if unchanged and undeleted, mark as frozen
								pixel.frozen = true;
							};

							if(pixel.frozen && pixel.temp > 0) {
								pixel.frozen = false;
							};

							//console.log(pixel.temp,pixel.didColorChange);
							if(pixel.temp > 300 && pixel.didColorChange < 1) {
								if(Math.random() < 0.02) { breakPixel(pixel) };
								var colorWasHSL = pixel.color.startsWith("hsl");
								var oldColor = convertHslObjects(normalizeColorToHslObject(pixel.color),"rgbjson");
								if(oldColor == null) { oldColor = pixelColorPick(pixel) };
								oldColor.r += 81/2;
								oldColor.g += 60/2;
								oldColor.b += 56/2;
								pixel.color = convertHslObjects(normalizeColorToHslObject(oldColor),colorWasHSL ? "hsl" : "rgb");
								pixel.didColorChange = 1;
							} else if(pixel.temp > 500 && pixel.didColorChange < 2) {
								if(Math.random() < 0.04) { breakPixel(pixel) };
								var colorWasHSL = pixel.color.startsWith("hsl");
								var oldColor = convertHslObjects(normalizeColorToHslObject(pixel.color),"rgbjson");
								if(oldColor == null) { oldColor = pixelColorPick(pixel) };
								oldColor.r += 81/4;
								oldColor.g += 60/4;
								oldColor.b += 56/4;
								pixel.color = convertHslObjects(normalizeColorToHslObject(oldColor),colorWasHSL ? "hsl" : "rgb");
								pixel.didColorChange = 2;
							} else if(pixel.temp > 700 && pixel.didColorChange < 3) {
								if(Math.random() < 0.06) { breakPixel(pixel) };
								var colorWasHSL = pixel.color.startsWith("hsl");
								var oldColor = convertHslObjects(normalizeColorToHslObject(pixel.color),"rgbjson");
								if(oldColor == null) { oldColor = pixelColorPick(pixel) };
								oldColor.r += 81/7;
								oldColor.g += 60/7;
								oldColor.b += 56/7;
								pixel.color = convertHslObjects(normalizeColorToHslObject(oldColor),colorWasHSL ? "hsl" : "rgb");
								pixel.didColorChange = 3;
							} else if(pixel.temp > 900 && pixel.didColorChange < 4) {
								if(Math.random() < 0.08) { breakPixel(pixel) };
								var colorWasHSL = pixel.color.startsWith("hsl");
								var oldColor = convertHslObjects(normalizeColorToHslObject(pixel.color),"rgbjson");
								if(oldColor == null) { oldColor = pixelColorPick(pixel) };
								oldColor.r += 81/8;
								oldColor.g += 60/8;
								oldColor.b += 56/8;
								pixel.color = convertHslObjects(normalizeColorToHslObject(oldColor),colorWasHSL ? "hsl" : "rgb");
								pixel.didColorChange = 4;
							};

							pixel.role ??= randomChoice(["aggregate","aggregate","aggregate","aggregate","sand","sand","cement"]);
							if(pixel.role == "cement") {
								var chooserValue = Math.random();
								if(chooserValue < 0.65) {
									pixel.role = "lime";
								} else if(chooserValue < 0.85) {
									pixel.role = "silica";
								} else if(chooserValue < 0.91) {
									pixel.role = "alumina";
								} else if(chooserValue < 0.96) {
									pixel.role = "ferricOxide";
								} else {
									pixel.role = "sulfurTrioxide";
								};
							};

							if(pixel.wet && pixel.temp > 300) {
								if(overallTemperatureChangeRate > 25) { //if temp change is fast enough, always spall
									if(Math.random() < Math.max(0.1,0.35 - (pixel.wet/20))) { //decresingly less likely to spall as it gets wetter, for balance
										explodeAt(pixel.x,pixel.y,Math.random() < 1/3 ? 2 : 1,"steam,dust")
										if(!pixel || pixel.element !== "concrete") { //if destroyed or changed
											return;
										};
									};
									pixel.wet--;
								} else { //if exposed, continuously try to boil off to random neighbor
									if(exposedToAir(pixel)) {
										var randomNeighbor = adjacentCoords[Math.floor(Math.random() * adjacentCoords.length)]
										var rnx = randomNeighbor[0]
										var rny = randomNeighbor[1]
										if(isEmpty(pixel.x+rnx, pixel.y+rny, false)) {
											createPixel("steam", pixel.x+rnx, pixel.y+rny)
											pixel.wet--;
											var colorWasHSL = pixel.color.startsWith("hsl");
											pixel.color = changeLuminance(pixel.color,6,"+",colorWasHSL ? "hsl" : "rgb");
										};
									} else { //if surrounded, lower chance to spall and higher chance to dissipate
										if(Math.random() < 0.03) {
											if(Math.random() < 2/5) {
												explodeAt(pixel.x,pixel.y,Math.random() < 1/2 ? 2 : 3,"steam,dust")
												if(!pixel || pixel.element !== "concrete") { //if destroyed or changed
													return;
												};
											};
											pixel.wet--;
											var colorWasHSL = pixel.color.startsWith("hsl");
											pixel.color = changeLuminance(pixel.color,6,"+",colorWasHSL ? "hsl" : "rgb");
										};
									};
								};
								return;
							};

							if(Math.random() < 1/3) { //tick wetness behavior only 1/3 of the time, for performance
								var randomCoords = JSON.parse(JSON.stringify(adjacentCoords)); //so we don't need both an adjacentCoords for *and* a random coord iterator
								shuffleArray(randomCoords);
								for(i = 0; i < randomCoords.length; i++) {
									var coords = [
										pixel.x+randomCoords[i][0],
										pixel.y+randomCoords[i][1]
									];
									if(isEmpty(coords[0],coords[1],true)) {
										continue;
									} else {
										var newPixel = pixelMap[coords[0]]?.[coords[1]];
										if(newPixel?.element) {
											if(newPixel.element === "water" && pixel.wet < 4) {
												//console.log("touching water",pixel.wet);
												if(pixel.wet < 1) {
													var colorWasHSL = pixel.color.startsWith("hsl");
													pixel.color = changeLuminance(pixel.color,6,"-",colorWasHSL ? "hsl" : "rgb");
													pixel.wet = 1;
													if(Math.random() < 0.8) { deletePixel(newPixel.x,newPixel.y) };
													break;
												};
												if(pixel.wet == 1) {
													var colorWasHSL = pixel.color.startsWith("hsl");
													pixel.color = changeLuminance(pixel.color,6,"-",colorWasHSL ? "hsl" : "rgb");
													pixel.wet = 2;
													if(Math.random() < 0.6) { deletePixel(newPixel.x,newPixel.y) };
													break;
												};
												if(pixel.wet == 2) {
													var colorWasHSL = pixel.color.startsWith("hsl");
													pixel.color = changeLuminance(pixel.color,6,"-",colorWasHSL ? "hsl" : "rgb");
													pixel.wet = 3;
													if(Math.random() < 0.4) { deletePixel(newPixel.x,newPixel.y) };
													break;
												};
												if(pixel.wet == 3) {
													var colorWasHSL = pixel.color.startsWith("hsl");
													pixel.color = changeLuminance(pixel.color,6,"-",colorWasHSL ? "hsl" : "rgb");
													pixel.wet = 4;
													if(Math.random() < 0.2) { deletePixel(newPixel.x,newPixel.y) };
													break;
												};
											} else {
												//console.log(3);
												if(pixel.wet > 1 && !pixel.frozen && newPixel.element.endsWith("concrete") && newPixel.wet != undefined && newPixel.wet < pixel.wet) {
													if(pixel.wet <= 1) { break };
													pixel.wet--;
													var colorWasHSL = pixel.color.startsWith("hsl");
													pixel.color = changeLuminance(pixel.color,6,"+",colorWasHSL ? "hsl" : "rgb");

													newPixel.wet++;
													var newColorWasHSL = newPixel.color.startsWith("hsl");
													newPixel.color = changeLuminance(newPixel.color,6,"-",newColorWasHSL ? "hsl" : "rgb");
												};
											};
										};
									};
								};
							};

							if(pixel.role == "sand" && pixel.temp > elements.sand.tempHigh) {
								changePixel(pixel,"molten_glass",false);
								return;
							};

							if(pixel.role == "aggregate" && pixel.temp > magmaTempHigh) {
								changePixel(pixel,magmaName,false);
								return;
							};

							if(pixel.role == "alumina" && pixel.temp > elements.aluminum_oxide.tempHigh) {
								changePixel(pixel,"molten_aluminum_oxide",false);
								return;
							};

							if(pixel.role == "ferricOxide" && pixel.temp > elements.rust.tempHigh) {
								changePixel(pixel,"molten_iron",false);
								return;
							};

							if(pixel.role == "sulfurTrioxide" && pixel.temp > magmaTempHigh) { //arbitrary choice: leave when the aggregate leaves
								changePixel(pixel,"sulfur_trioxide_gas",false);
								return;
							};

							if(pixel.role == "lime" && pixel.temp > 550) {
								changePixel(pixel,"slaked_lime",false);
								return;
							};

							if(pixel.role == "silica") {
								pixel.didQuartzThermalExpansion ??= false;

								if(pixel.temp > 573 && !pixel.didQuartzThermalExpansion) {
									if(Math.random() < 0.13) {
										changePixel(pixel,"pop",false);
									};
									pixel.didQuartzThermalExpansion = true;
								};

								if(pixel.temp > elements.silica.tempHigh) {
									changePixel(pixel,"molten_silica",false);
									return;
								};
							};
						};

						newConcreteTick = elements.concrete.tick;

						runAfterLoad(function() { //mamma mia that's some tasty spaghetti
							if(elements.hanging_concrete) {
								elements.hanging_concrete.tick = function(pixel) {
									newConcreteTick(pixel);
								};
							};

							if(elements.attach_concrete) {
								oldAttachConcreteTick = elements.attach_concrete.tick ;
								elements.attach_concrete.tick = function(pixel) {
									oldAttachConcreteTick(pixel);
									newConcreteTick(pixel);
								};
							};

							if(elements.crumbling_concrete) {
								oldCrumblingConcreteTick = elements.crumbling_concrete.tick ;
								newConcreteTick = elements.concrete.tick ;
								elements.crumbling_concrete.tick = function(pixel) {
									oldCrumblingConcreteTick(pixel);
									newConcreteTick(pixel);
								}
							}
						});

					//Crimson
						//Made-up lore: Crimson naturally drives rocks towards a somewhat mafic comp.

							elements.crimglass = {
								color: ["#d1808d", "#b8a770", "#a4b386"],
								behavior: behaviors.WALL,
								tempHigh: 1765,
								category: "solids",
								state: "solid",
								density: 2711,
								breakInto: "crimglass_shard",
								noMix: true,
								tick: function(pixel) { crimSpread(pixel) }
							};

							elements.crimglass_shard = {
								color: gravelizeToHex(["#d1808d", "#b8a770", "#a4b386"]),
								behavior: behaviors.POWDER,
								tempHigh: 1765,
								stateHigh: "molten_crimglass",
								category: "powders",
								state: "solid",
								density: 2711 * 0.5,
								tick: function(pixel) { crimSpread(pixel) }
							};

							elements.molten_crimglass = {
								tick: function(pixel) { crimSpread(pixel) }
							};

							elements.crimson_grass = {
								color: ["#e82535","#cc471f","#d6153c","#c20e29","#b81a2c"],
								behavior: [
									"XX|CR:vicious_mushroom%0.01|XX",
									"XX|XX|XX",
									"XX|M1|XX",
								],
								tick: function(pixel) {
									crimSpread(pixel)
								},
								tempHigh: 100,
								stateHigh: "dead_plant",
								tempLow: -2,
								stateLow: "frozen_plant",
								burn:50,
								burnTime:20,
								category:"life",
								state: "solid",
								density: 1400,
							}

							elements.red_ice = {
								color: ["#f0ccc5", "#f7d8d2", "#eba39b"],
								behavior: behaviors.WALL,
								tick: function(pixel) {
									crimSpread(pixel)
								},
								temp: 0,
								tempHigh: 5,
								stateHigh: "crimwater",
								category: "solids",
								state: "solid",
								density: 917,
								breakInto: "crimsnow",
							}

							elements.crimwater = { //you shouldn't be able to purify ice by melting it
								color: "#e45c7c",
								behavior: behaviors.LIQUID,
								tick: function(pixel) {
									crimSpread(pixel)
								},
								tempLow: 0,
								stateLow: "red_ice",
								tempHigh: 100,
								stateHigh: "steam",
								viscosity: 1,
								category: "liquids",
								reactions: {
									"quicklime": { "elem1": null, "elem2": "slaked_lime", },
									"ruins": { "elem2": "rock", "chance": 0.00035 },
								},
								state: "liquid",
								density: 997,
								conduct: 0.02,
								stain: 0.02,
							}

							elements.crimsnow = { //BIG break from canon but you shouldn't be able to purify ice by grinding it either
								color: "#fce1e4",
								behavior: behaviors.POWDER,
								tick: function(pixel) {
									crimSpread(pixel)
								},
								temp: 0,
								tempHigh: 5,
								stateHigh: "crimwater",
								category: "land",
								state: "solid",
								density: 100,
							}

							elements.vicious_mushroom = {
								color: "#e36554",
								behavior: behaviors.POWDER,
								tick: function(pixel) {
									crimSpread(pixel)
								},
								category: "life",
								hidden: true,
								tempHigh: 225,
								stateHigh: "fire",
								burn: 10,
								burnTime: 65,
								state: "solid",
								density: 90.445,
							}

							elements.crimtane_ore = {
								color: ["#d83a3b", "#85242c", "#5d5d5d", "#540c14"],
								behavior: behaviors.POWDER,
								category: "land",
								tempHigh: 1552, //using palladium's melting point as an upper bound
								stateHigh: ["molten_slag","molten_slag","molten_crimtane"], //:sunglasses: can't turn things into slag if you're already slag
								state: "solid",
								density: 5854, //arbitrarily chosen, average of ((average of gold and palladium densities) + (crimstone density) + (crimstone density))
							}

							elements.crimtane = {
								color: ["#fc141e", "#C62A2F", "#903f3f", "#752E2E", "#5a1c1c", "#5B3C3C", "#5c5c5c"],
								behavior: behaviors.SOLID,
								category: "solids",
								tempHigh: 1200, //i want a behaviors.WALL form of crimtane... and I'm letting the game autogenerate molten_crimtane because I'm going to use it.
								//just pretend it got sintered somehow
								state: "solid",
								hidden: true,
								density: 15661,
							}

							elements.shadewood_tree_branch = {
								color: "#677a8f",
								behavior: [
									"CR:crimson_leaf,shadewood_tree_branch%2|CR:crimson_leaf,crimson_leaf,crimson_leaf,shadewood_tree_branch%2|CR:crimson_leaf,shadewood_tree_branch%2",
									"XX|XX|XX",
									"XX|XX|XX",
								],
								tempHigh: 400,
								stateHigh: ["fire","sap"],
								tempLow: -30,
								stateLow: "wood",
								category: "solids",
								burn: 40,
								burnTime: 50,
								burnInto: ["sap","ember","charcoal"],
								hidden: true,
								state: "solid",
								density: 1500,
								hardness: 0.15,
								breakInto: ["sap","sawdust"],
								hidden: true,
							}
							elements.crimson_vine = {
								color: "#de3323",
								behavior: [
									"XX|SP|XX",
									"XX|XX|XX",
									"XX|CL%1 AND M1|XX",
								],
								tick: function(pixel) {
									crimSpread(pixel)
								},
								tempHigh: 100,
								stateHigh: "dead_plant",
								tempLow: -2,
								stateLow: "frozen_plant",
								burn: 35,
								burnTime: 100,
								category: "life",
								state: "solid",
								density: 1050,
							}

							elements.shadewood = {
								color: "#677a8f",
								behavior: behaviors.WALL,
								tempHigh: 400,
								stateHigh: ["ember","charcoal","fire","fire","fire"],
								category: "solids",
								burn: 5,
								burnTime: 300,
								burnInto: ["ember","charcoal","fire"],
								state: "solid",
								hardness: 0.15,
								breakInto: "shadewood_sawdust",
								density: 930, //used tigerwood
							}

							elements.shadewood_sapling = {
								color: ["#e64029", "#d43b26"],
								behavior: [
									"XX|M2%2|XX",
									"XX|L2:shadewood,shadewood_tree_branch%80|XX",
									"XX|M1|XX",
								],
								tempHigh: 100,
								stateHigh: "dead_plant",
								tempLow: -2,
								stateLow: "frozen_plant",
								burn: 65,
								burnTime: 15,
								category: "life",
								state: "solid",
								density: 1500,
							}

							elements.shadewood_sawdust = {
								color: ["#95abcf","#8190a3"],
								behavior: behaviors.POWDER,
								tempHigh: 400,
								stateHigh: "fire",
								category: "powders",
								burn: 25,
								burnTime: 150,
								burnInto: ["ash","fire","fire","fire"],
								state: "solid",
								density: 493,
								hidden: true,
							}

							elements.crimson_leaf = {
								color: "#de3323",
								behavior: behaviors.WALL,
								category:"life",
								tempHigh: 100,
								stateHigh: "dead_plant",
								tempLow: -1.66,
								stateLow: "frozen_plant",
								burn:65,
								burnTime:60,
								burnInto: "dead_plant",
								state: "solid",
								density: 500,
								hidden: true,
							}

							elements.ichor = {
								color: ["#ffec70", "#ffcb52"],
								behavior: behaviors.LIQUID,
								reactions: {
									"head": { "elem2":"meat" }, //sb has no defense to reduce so i just made it deadly (as in greek mythology)
									"body": { "elem2":"meat" },
								},
								category: "liquids",
								viscosity: 1,
								state: "liquid",
								density: 1010,
								stain: 0.02,
							}

							elements.vicious_goldfish = {
								color: "#e64230",
								behavior: [
									"SW:"+eLists.WHL+",blood%2|M2%5 AND SW:"+eLists.WHL+",blood%1|XX", //this is where M3 would have been useful
									"SW:"+eLists.WHL+",blood%40|FX%0.01|BO%1", //i have no idea what i'm doing
									"SW:"+eLists.WHL+",blood%2 AND M2|M1|XX",
								],
								reactions: {
									"algae": { "elem2":null, chance:0.5 },
									"plant": { "elem2":null, chance:0.125 },
									"fly": { "elem2":null, chance:0.5 },
									"firefly": { "elem2":null, chance:0.5 },
									"worm": { "elem2":null, chance:0.25 },
									"head": { "elem2":[null,"blood"], chance:0.25 },
									"body": { "elem2":[null,"blood"], chance:0.25 },
									"oxygen": { "elem2":"carbon_dioxide", chance:0.5 },
								},
								tick: function(pixel) {
									pixel.color = pixelColorPick(pixel)
								},
								temp: 20,
								tempHigh: 42,
								stateHigh: "meat",
								tempLow: -20,
								stateLow: "frozen_meat",
								category:"life",
								burn:40,
								burnTime:100,
								state: "solid",
								density: 1080,
								conduct: 0.2,
							}

							elements.crimsand = {
								color: ["#4c4c44", "#6c645c", "#5c544c", "#847c6c", "#24241c", "#4c4c44", "#6c645c", "#5c544c", "#847c6c", "#24241c", "#3c140c", "#842c24"],
								behavior: behaviors.POWDER,
								tempHigh: 1765,
								stateHigh: "molten_crimson_glass",
								category: "sand",
								state: "solid",
								density: 1602,
								_data: ["crimson","crystalline","sand"],
								tick: function(pixel) { crimSpread(pixel) }
							}

							elements.wet_crimsand = {
								color: sandizeToHex("crimsand","w"),
								behavior: behaviors.STURDYPOWDER,
								category: "wet sand",
								reactions: {
									"dirt": { "elem1":"crimsand", "elem2":"mud", "chance":0.0005, "oneway":true },
								},
								state: "solid",
								tempHigh: 100,
								stateHigh: "packed_crimsand",
								tempLow: -50,
								stateLow:"packed_crimsand",
								density: elements.crimsand.density * 0.595 + 150,
								_data: ["crimson","crystalline","wet_particulate"],
								nellfireImmune: true,
								tick: function(pixel) { crimSpread(pixel) }
							};

							elements.packed_crimsand = {
								color: sandizeToHex("crimsand","p"),
								behavior: behaviors.SUPPORT,
								category: "packed sand",
								state: "solid",
								tempHigh: 1911,
								stateHigh: "crimglass",
								density: elements.crimsand.density * 0.59,
								breakInto: "nellsand",
								_data: ["nellish","crystalline","packed_particulate"],
								nellfireImmune: true,
								tick: function(pixel) { crimSpread(pixel) }
							};

							function newDirtType(names,dirtColor,density,meltingPoint,frostingPoint) {
								if(!(dirtColor instanceof Array)) { dirtColor = [dirtColor] };

								var mudColor = dirtColor.map(x => colorToHsl(x,"json")); mudColor.forEach(function(x) { x.s *= (41/21); x.l *= (15/26) }); mudColor = mudColor.map(function(x) { return hslToHex(...Object.values(x)) });
								if(mudColor.length == 1) { mudColor = mudColor[0] };

								var mudstoneColor = dirtColor.map(x => colorToHsl(x,"json")); mudstoneColor.forEach(function(x) { x.h += 6; x.s *= (31/41); x.l *= (26/15); x.l += 5 }); mudstoneColor = mudstoneColor.map(function(x) { return hslToHex(...Object.values(x)) });
								if(mudstoneColor.length == 1) { mudstoneColor = mudstoneColor[0] };

								var dryDirtColor = dirtColor.map(x => colorToHsl(x,"json")); dryDirtColor.forEach(function(x) { x.h += 4; x.s *= (8/11); x.l *= (34/50); x.l += 5 }); dryDirtColor = dryDirtColor.map(function(x) {
									x = convertHslObjects(x,"rgbjson");
									x.r += 10;
									x.g += 5; //XG??!?!??!?!?!??!?!!??!?!?!?!??!?!?!?!/1/1/1?!/!?!?1?1??!/!1//!
									x.b -= 10;
									x.g *= 0.94;
									x.b *= 0.88;
									return convertColorFormats(x,"hex");
								});
								if(dryDirtColor.length == 1) { dryDirtColor = dryDirtColor[0] };

								var permafrostColor = dirtColor.map(x => colorToHsl(x,"json")); permafrostColor.forEach(function(x) { x.h -= 6; x.s *= (3/5); x.l -= 3 }); permafrostColor = permafrostColor.map(function(x) { return hslToHex(...Object.values(x)) });
								if(permafrostColor.length == 1) { permafrostColor = permafrostColor[0] };

								var dryPermafrostColor = dirtColor.map(x => colorToHsl(x,"json")); dryPermafrostColor.forEach(function(x) { x.h -= 6; x.s *= (3/5); x.s -= 5; x.l += 2 }); dryPermafrostColor = dryPermafrostColor.map(function(x) { return hslToHex(...Object.values(x)) });
								if(dryPermafrostColor.length == 1) { dryPermafrostColor = permafrostColor[0] };

								//names should b an obj w keys dirt,mud,mudstone,permafrost
								elements[names.dirt] = {
									color: dirtColor,
									behavior: behaviors.POWDER,
									tempHigh: 100,
									stateHigh: "dry_" + names.dirt,
									tempLow: frostingPoint,
									forceAutoGen: true,
									stateLow: names.permafrost,
									category:"land",
									state: "solid",
									density: density
								};

								elements["dry_" + names.dirt] = {
									color: dryDirtColor,
									behavior: behaviors.POWDER,
									tempHigh: meltingPoint,
									stateHigh: "molten_" + names.dirt,
									tempLow: frostingPoint,
									stateLow: "dry_" + names.permafrost,
									forceAutoGen: true,
									stateLow: names.permafrost,
									category:"land",
									state: "solid",
									density: density
								};

								elements[names.mud] = {
									color: mudColor,
									behavior: behaviors.STURDYPOWDER,
									reactions: {
										"dirt": { elem1:names.dirt, elem2:"mud", chance:0.0005, oneway:true },
										["dry_" + names.dirt]: { elem1:names.dirt, elem2: ["dry_" + names.dirt], chance:0.0005, oneway:true },
										"sand": { elem1:names.mud, elem2:"wet_sand", chance:0.0005, oneway:true },
										"sawdust": { elem1:"mulch", "elem2":null },
										"evergreen": { elem1:"mulch", "elem2":null },
									},
									tempHigh: 100,
									stateHigh: names.mudstone,
									tempLow: frostingPoint,
									stateLow: names.permafrost,
									category: "land",
									state: "solid",
									density: density * (17/12),
									stain: 0.02
								};

								elements[names.mudstone] = {
									color: mudstoneColor,
									behavior: behaviors.SUPPORT,
									tempHigh: meltingPoint,
									stateHigh: "molten_" + names.dirt,
									tempLow: frostingPoint,
									stateLow: names.permafrost,
									category: "land",
									state: "solid",
									density: density * 1.2,
									breakInto: "dirt"
								};

								elements[names.permafrost] = {
									color: permafrostColor,
									behavior: behaviors.SUPPORT,
									temp: frostingPoint - 20,
									tempHigh: frostingPoint,
									stateHigh: names.mud,
									category: "land",
									state: "solid",
									density: density / 1.7
								};

								elements["dry_" + names.permafrost] = {
									color: dryPermafrostColor,
									behavior: behaviors.POWDER,
									temp: frostingPoint - 20,
									tempHigh: 0,
									stateHigh: "dry_" + names.dirt,
									category: "land",
									state: "solid",
									density: density
								};

								elements.water.reactions[names.dirt] = { elem1: null, elem2: names.mud };
								elements.water.reactions["dry_" + names.dirt] = { elem1: null, elem2: names.dirt, chance: 0.005 };
								elements.mud.reactions["dry_" + names.dirt] = { elem1: "dirt", elem2: names.dirt, chance: 0.005 };
							};

							newDirtType(
								{dirt: "crimsoil", mud: "crimmud", permafrost: "crimson_permafrost", mudstone: "crimmudstone"},
								["#782b2e", "#8c2e26", "#86241d", "#9d2b20", "#632f52"],
								1260, 903, -70
							);

							elements.mud._data = ["loam","soil","wet_particulate"];
							elements.mudstone._data = ["loam","soil","packed_particulate"];
							elements.permafrost._data = ["loam","soil","icy_particulate"];
							elements.crimsoil._data = ["crimson","soil","particulate"];
							elements.dry_crimsoil._data = ["crimson","soil","particulate"];
							elements.crimmud._data = ["crimson","soil","wet_particulate"];
							elements.crimmudstone._data = ["crimson","soil","packed_particulate"];
							elements.crimson_permafrost._data = ["crimson","soil","icy_particulate"];
							elements.dry_crimson_permafrost._data = ["crimson","soil","particulate"];

							makeNonSandSedimentationElements("crimsand","crimsandy_water","crimsandstone");

							newIgneousCompositionFamily(
								"crimson",

								13000, 2420, -76, -17, 2877,

								"crimstone", ["#cb4444", "#953333", "#611c1c", "#b43434", "#752424"], 1223, 4234,
								"metacrimstone", ["#b31010","#8f1111","#80282a","#b31010","#8f1111","#80282a","#bb4e45"], 1223, 4544,

								"crimsalt", ["#9e5041", "#a33345"], 1151, 3226,
								"metacrimsalt", ["#ab5c4f","#c25c4c","#cf4452"], 1151, 3044,

								"crimscoria", ["#914c57", "#ba7b85", "#6b2e38", "#b3626f"], 1032, 2903,
								"metacrimscoria", ["#bf2636","#961b12","#b84040"], 1032, 3534,

								"crimidian", ["#5a1b1c", "#622b33", "#762733", "#76322c"], 1122, 3050,
								"metacrimidian", ["#701b1c","#783628","#802419","#872323"], 1122, 3169,

								3,7
							);
							
							elements.crimsalt.behavior = behaviors.STURDYPOWDER;
							elements.metacrimsalt.behavior = behaviors.STURDYPOWDER;

							elements.crimson_magma.temp = elements.crimson_magma.tempHigh * 0.8;

							//var elems2 = Object.keys(elements);
							//var deltaElems = elems2.filter(function(name) { return !(elems1.includes(name)) });
							runAfterLoad(function() { runAfterAutogen(function() { runAfterAutogen(function() { //i need this to happen last
								var rockCrimmies = Object.keys(elements).filter(function(name) {return (name.match(/((wet|packed|hot|vaporized)_|meta|)crim/) && !(name.match(/crimtane/)))});
								lifeEaterWhitelist = lifeEaterWhitelist.concat(rockCrimmies); //crimson is alive, so LEV should eat it
								rockCrimmies.forEach(function(name) {
									var data = elements[name];
									if(data.tick) {
										var oldTick = data.tick;
										data.tick = function(pixel) {
											oldTick(pixel);
											if(pixel && !isEmpty(pixel.x,pixel.y)) {
												crimSpread(pixel);
											}
										}
									} else {
										data.tick = function(pixel) { crimSpread(pixel) }
									};
								});
								for(var key in elements) {
									var data = elements[key];
									if(!(elements[key].tick)) { continue };
									if(elements[key].tick.toString().includes("crimSpread")) {
										data.excludeRandom = true
									}
								};
							})})});

							elements.molten_crimsoil = {
								tempLow: elements.dry_crimsoil.tempHigh,
								stateLow: "dry_crimsoil",
								stateHigh: "crimson_magma"
							};
							crimsonObject.dirt = "crimsoil";
							crimsonObject.dry_dirt = "dry_crimsoil";
							crimsonObject.mud = "crimmud";
							crimsonObject.mudstone = "crimmudstone";
							crimsonObject.permafrost = "crimson_permafrost";
							crimsonObject.molten_dirt = "molten_crimsoil";
							crimsonObject.dry_permafrost = "dry_crimson_permafrost";
							runAfterLoad(function() {
								elements.molten_crimsoil.tempHigh = elements.crimson_magma.tempHigh;
								elements.crimsandy_water.color = ["#985460", "#a8606c", "#a05864", "#b46c74", "#84404c", "#985460", "#a8606c", "#a05864", "#b46c74", "#84404c", "#903844", "#b44450" ] //manual: use crimwater for the lerp in crimsand suspension's color
								elements.crimmuddy_water.color = ["#ed4154", "#f25259", "#f2444c", "#f25a62", "#df428d" ]; //same for crimsoil (crimmud) susp.
								elements.crimwater.reactions.crimsand = elements.water.reactions.crimsand;
								elements.crimwater.reactions.crimmud = elements.water.reactions.crimmud;
								for(var k in elements.crimsandy_water.reactions) {
									var reactionObject = elements.crimsandy_water.reactions[k];
									var e1 = reactionObject.elem1; var e2 = reactionObject.elem2;
									if(e1 == "water") { reactionObject.elem1 = "crimwater" };
									if(e2 == "water") { reactionObject.elem2 = "crimwater" };
								};
								for(var k in elements.crimmuddy_water.reactions) {
									var reactionObject = elements.crimmuddy_water.reactions[k];
									var e1 = reactionObject.elem1; var e2 = reactionObject.elem2;
									if(e1 == "water") { reactionObject.elem1 = "crimwater" };
									if(e2 == "water") { reactionObject.elem2 = "crimwater" };
								};
								elements.crimmuddy_water.reactions.crimmuddy_water.elem2 = "crimsoil_sediment";
								elements.rainbow_muddy_water.reactions.rainbow_muddy_water.elem2 = "rainbow_soil_sediment"
								elements.crimsoilstone.tempHigh = 800;
							});

					//Blackpinkinitic (why? because FU. that's why. jk lol it's because i want to test metamorphism but there's so little data on the melting of metamorphic rocks--especially clay-based ones--that it's proving to be a real PITA.

						newIgneousCompositionFamily(
							"blackpinkinitic",

							403, 2602, -95, -30, 4007,

							"blinkinite", ["#e39dc6", "#e378b7", "#d1589f", "#a1306e", "#6e274e", "#170e13", "#121212"], 1821, 3291,
							"lisaslate", ["#f26fbc", "#f26fbc", "#e344a1", "#d42686", "#b52daa", "#8a3683", "#5c324f", "#572c6e", "#421530", "#120c10", "#120c10"], 1821, 3333,

							"roselite", ["#eda4c6","#de90ae","#cf9db0","#cf9db0","#d97ca0"], 1715, 2551,
							"rosephyllite", ["#e895bb", "#c46286", "#a34b73", "#6e2e4b", "#301921", "#1a1315"], 1715, 3021,

							"jisoovesite", ["#bf56af", "#a15495", "#70416f", "#e87de6", "#381a47"], 1977, 719,
							"vesimelite", ["#a35097", "#854e72", "#8a5789", "#d47dd2", "#462452"], 1977, 1132,

							"jennitrite", ["#8a2966", "#801357", "#75074c", "#4a012f", "#360e27", "#1a0e15"], 2111, 2603,
							"harmonitrite", ["#9e3979", "#9e186c", "#a3146e", "#380324", "#2e1028", "#361a2a"], 2111, 2663,

							3,7
						);

						elements.roselite.name = elements.roselite.alias = "rosélite";
						elements.rosephyllite.colorPattern = [ // diagonal foliation
							"PPppDDddBBbbBBddDDpp",
							"ppDDddBBbbBBddDDppPP",
							"DDddBBbbBBddDDppPPpp",
							"ddBBbbBBddDDppPPppDD",
							"BBbbBBddDDppPPppDDdd",
							"bbBBddDDppPPppDDddBB",
							"BBddDDppPPppDDddBBbb",
							"ddDDppPPppDDddBBbbBB",
							"DDppPPppDDddBBbbBBdd",
							"ppPPppDDddBBbbBBddDD"
						];
						elements.rosephyllite.colorKey = {
							"P": "#e895bb",
							"p": "#c46286",
							"D": "#a34b73",
							"d": "#6e2e4b",
							"B": "#301921",
							"b": "#1a1315"
						};

						elements.blinkinite.behavior = behaviors.POWDER;
						elements.rosephyllite.name = elements.rosephyllite.alias = "roséphyllite";
						elements.jisoovesite.maxColorOffset = 30;

					//Rainbow (actually let's call them Iridian)

						//Required manual elements

							elements.rainbow_glass = {
								color: makeRegularRainbow(12,100,70,"hex"),
								behavior: behaviors.WALL,
								tempHigh: 1765,
								category: "solids",
								state: "solid",
								density: 2711,
								breakInto: "rainbow_glass_shard",
								noMix: true,
								nellfireImmune: true,
							};

							elements.rainbow_glass_shard = {
								color: makeRegularRainbow(18,70,65,"hex"),
								behavior: behaviors.POWDER,
								tempHigh: 1784,
								stateHigh: "molten_rainbow_glass",
								category: "powders",
								state: "solid",
								density: 2213,
								nellfireImmune: true,
							};

							elements.rainbow_sand = {
								color: makeRegularRainbow(7,100,94,"hex").concat("#fbfbfb"),
								behavior: behaviors.POWDER,
								tempHigh: 1731,
								stateHigh: "molten_rainbow_glass",
								category: "land",
								state: "solid",
								density: 1733,
								_data: ["iridian","crystalline","particulate"],
								nellfireImmune: true,
							};

							elements.wet_rainbow_sand = {
								color: makeRegularRainbow(7,100,84,"hex").concat("#e0e0e0"),
								behavior: behaviors.STURDYPOWDER,
								category: "land",
								reactions: {
									"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
								},
								state: "solid",
								tempHigh: 100,
								stateHigh: "packed_rainbow_sand",
								tempLow: -50,
								stateLow:"packed_rainbow_sand",
								density: 1733 * 0.595 + 150,
								_data: ["iridian","crystalline","wet_particulate"],
								nellfireImmune: true,
							};

							elements.packed_rainbow_sand = {
								color: makeRegularRainbow(7,70,86,"hex").concat("#dbdbdb"),
								behavior: behaviors.SUPPORT,
								category: "land",
								state: "solid",
								tempHigh: 1731,
								stateHigh: "rainbow_glass",
								density: 1733 * 0.59,
								breakInto: "rainbow_sand",
								_data: ["iridian","crystalline","packed_particulate"],
								nellfireImmune: true,
							};

							

							newDirtType(
								{dirt: "rainbow_dirt", mud: "rainbow_mud", permafrost: "rainbow_permafrost", mudstone: "rainbow_mudstone"},
								"#b09eac #b0bfa1 #d9c0b6 #b09eac #b0bfa1 #d9c0b6 #ed7777 #a7d975 #7bd4d4 #ab77e0 #e0cf77".split(" "),
								1533, 942, -110
							);

							elements.rainbow_dirt._data = ["iridian","soil","particulate"];
							elements.dry_rainbow_dirt._data = ["iridian","soil","particulate"];
							elements.rainbow_mud._data = ["iridian","soil","wet_particulate"];
							elements.rainbow_mudstone._data = ["iridian","soil","packed_particulate"];
							elements.rainbow_permafrost._data = ["iridian","soil","icy_particulate"];
							elements.dry_rainbow_permafrost._data = ["iridian","soil","particulate"];

							elements.molten_rainbow_dirt = {
								tempLow: elements.dry_rainbow_dirt.tempHigh,
								stateLow: "dry_rainbow_dirt",
								stateHigh: "rainbow_magma"
							};
							runAfterLoad(function() {
								elements.rainbow_soilstone.tempHigh = 800;
								elements.molten_rainbow_dirt.tempHigh = elements.rainbow_magma.tempHigh;
							});

						//Vanilla changes
							elements.water.reactions.rainbow_sand = { elem1: null, elem2: "wet_rainbow_sand" };
							elements.water.reactions.wet_rainbow_sand = { "elem1": "rainbow_sand_water", "elem2": [ "rainbow_sand", "rainbow_sand", "rainbow_sand", "rainbow_sand_water" ], "chance": 0.01 };

						//Actual rock generation

							//Sedimentary

								makeNonSandSedimentationElements("rainbow_sand","rainbow_sand_water","rainbow_sandstone");
								elements.rainbow_sandstone.color = makeRegularRainbow(14,90,71,"hex").concat("#b5b5b5");

							//Igneous

								newIgneousCompositionFamily(
									"rainbow",

									133487, 5512, -71, -17, 4555,

									"phirite", makeRegularRainbow(6,70,45,"hex"), 1671, 4004,
									"metaphirite", makeRegularRainbow(7,60,35,"hex"), 1671, 4244,

									"aphirite", makeRegularRainbow(24,63,75,"hex").concat("#bfbfbf"), 1685, 3951,
									"metaaphirite", makeRegularRainbow(23,83,65,"hex").concat("#afafaf"), 1685, 4191,

									"vesirite", makeRegularRainbow(7,55,30,"hex").concat(makeRegularRainbow(7,75,70,"hex")), 1712, 2918,
									"metavesirite", makeRegularRainbow(5,66,80,"hex").concat(makeRegularRainbow(5,56,60,"hex")), 1712, 3118,

									"vitirite", makeRegularRainbow(30,70,35,"hex").concat("#595959"), 2054, 3741,
									"metavitirite", makeRegularRainbow(15,60,45,"hex").concat("#494949").concat(makeRegularRainbow(15,60,55,"hex")).concat("#797979"), 2054, 3941,

									3,7
								);

							//Nellfire immunization
								var immunes = ["rainbow_sand_water", "rainbow_sand_sediment", "rainbow_sandstone", "phirite", "phirite_wall", "phirite_gravel", "aphirite", "aphirite_wall", "phirite_sand", "wet_phirite_sand", "packed_phirite_sand", "aphirite_gravel", "aphirite_sand", "wet_aphirite_sand", "packed_aphirite_sand", "vesirite", "vesirite_wall", "vesirite_gravel", "vesirite_sand", "wet_vesirite_sand", "packed_vesirite_sand", "vitirite", "vitirite_wall", "vitirite_shard", "vitirite_sand", "wet_vitirite_sand", "packed_vitirite_sand", "rainbow_magma", "vaporized_rainbow_magma", "rainbow_magma_cloud", "phirite_sandy_water", "phirite_sand_sediment", "phirite_sandstone", "aphirite_sandy_water", "aphirite_sand_sediment", "aphirite_sandstone", "vesirite_sandy_water", "vesirite_sand_sediment", "vesirite_sandstone", "vitirite_sandy_water", "vitirite_sand_sediment", "vitirite_sandstone", "phirite_dust", "aphirite_dust", "vesirite_dust", "vitirite_dust" ];

								runAfterLoad(function() {
									for(i = 0; i < immunes.length; i++) {
										if(!elements[immunes[i]]) {
											console.error(immunes[i]);
											continue;
										}
										elements[immunes[i]].nellfireImmune = true;
									};
								});

					//Nellish

						//Manuals

							elements.nellglass = {
								color: ["#a161b0", "#b06177", "#b59159"],
								behavior: behaviors.WALL,
								tempHigh: 1765,
								category: "solids",
								state: "solid",
								density: 5012,
								breakInto: "nellglass_shard",
								noMix: true,
								nellfireImmune: true,
							};

							elements.nellglass_shard = {
								color: ["#7d5f8c","#875966","#9e7b47"],
								behavior: behaviors.POWDER,
								tempHigh: 1765,
								stateHigh: "molten_nellglass",
								category: "powders",
								state: "solid",
								density: 4212,
								nellfireImmune: true,
							};

							elements.nellsand = {
								color: ["#906fa8", "#80747a", "#b08464"],
								behavior: behaviors.POWDER,
								tempHigh: 1911,
								stateHigh: "molten_nellglass",
								category: "sand",
								state: "solid",
								density: 3742,
								_data: ["nellish","crystalline","particulate"],
								nellfireImmune: true,
							};

							elements.water.reactions.nellsand = { elem1: null, elem2: "wet_nellsand" };
							elements.water.reactions.wet_nellsand = { "elem1": "nellsand_water", "elem2": [ "nellsand", "nellsand", "nellsand", "nellsand_water" ], "chance": 0.01 };

							elements.wet_nellsand = {
								color: sandizeToHex("nellsand","w"),
								behavior: behaviors.STURDYPOWDER,
								category: "wet sand",
								reactions: {
									"dirt": { "elem1":"sand", "elem2":"mud", "chance":0.0005, "oneway":true },
								},
								state: "solid",
								tempHigh: 100,
								stateHigh: "packed_nellsand",
								tempLow: -50,
								stateLow:"packed_nellsand",
								density: 3742 * 0.595 + 150,
								_data: ["nellish","crystalline","wet_particulate"],
								nellfireImmune: true,
							};

							elements.packed_nellsand = {
								color: sandizeToHex("nellsand","p"),
								behavior: behaviors.SUPPORT,
								category: "packed sand",
								state: "solid",
								tempHigh: 1911,
								stateHigh: "nellglass",
								density: 3742 * 0.59,
								breakInto: "nellsand",
								_data: ["nellish","crystalline","packed_particulate"],
								nellfireImmune: true,
							};

							nellburnObject = {
								"dirt": "nellsand",
								"dry_dirt": "nellsand",
								"mud": "wet_nellsand",
								"mudstone": "packed_nellsand",
								"brass": ["nell_ash","zinc"],
								"thermite": ["nell_ash","zinc"],
								"rose_gold": ["nell_ash","gold"],
								"electrum": ["nell_ash","gold"],
								"molten_brass": ["nell_ash","molten_zinc"],
								"molten_thermite": ["nell_ash","molten_zinc"],
								"molten_rose_gold": ["nell_ash","molten_gold"],
								"molten_electrum": ["nell_ash","molten_gold"],
								"sun": "nellsun",
							};

							var otherImmunes = ["fire","smoke","plasma","cold_fire","radiation","light","proton","neutron","electron","positron","antimatter","cold_smoke","rad_fire","rad_smoke","laser","liquid_fire","liquid_smoke","liquid_plasma","liquid_cold_fire","liquid_cold_smoke","liquid_rad_fire","liquid_rad_fire","liquid_rad_smoke","le_liquid_light","liquid_laser","pure_ice","pure_water","pure_steam","magic","gold","zinc","molten_gold","molten_zinc","pyreite","infernium","molten_pyreite","molten_infernium","infernyrite","molten_infernyrite","infernyreitheum","molten_infernyreitheum","pyrinfernyreitheum","molten_pyrinfernyreitheum","stellar_plasma","liquid_stellar_plasma","hydrogen","liquid_hydrogen","hydrogen_ice","neutronium","molten_neutronium","liquid_neutronium","neutronium_gas","liquid_degenerate_neutronium","gaseous_degenerate_neutronium"].concat(["water","ice","slush","snow","packed_snow","steam",   "heavy_steam","heavy_water","heavy_ice","heavy_snow",	"hydrogen_ice","liquid_hydrogen","hydrogen","ionized_hydrogen",   "liquid_helium","helium","ionized_helium",   "tralphium","liquid_tralphium","ionized_tralphium", "carbon","charcoal","diamond","molten_carbon",   "carbon_monoxide","liquid_carbon_monoxide","carbon_monoxide_ice",   "carbon_dioxide","dry_ice","seltzer","seltzer_ice","foam","cloud","rain_cloud","snow","snow_cloud","snow_cloud_floater","ice","thunder_cloud","hail_cloud"]);

							for(let i = 0; i < otherImmunes.length; i++) {
								var element = otherImmunes[i];
								if(elements[element]) { elements[element].nellfireImmune = true };
							};

							elements.nell_ash = {
								color: ["#ab9393","#947873"],
								behavior: behaviors.POWDER,
								/*reactions: {
									"steam": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
									"rain_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
									"cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
									"snow_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
									"hail_cloud": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
									"acid_cloud": { "elem1": "pyrocumulus", "chance":0.05, "y":[0,12], "setting":"clouds" },
									"pyrocumulus": { "elem1": "pyrocumulus", "chance":0.08, "y":[0,12], "setting":"clouds" },
									"stench": { "elem2":null, "chance":0.1 }
								},*/
								category:"powders",
								state: "solid",
								nellfireImmune: true,
								tick: function(pixel) {
									if(Math.random() < 0.01 && pixel.temp < 25) {
										pixel.temp++;
									};
								},
								density: 810,
								//tempHigh: 2000,
								//forceAutoGen: true,
								//stateHigh: ["molten_ash","smoke","smoke","smoke"]
							};

							elements.wall.nellfireImmune = true;

							//Massive block of code that programatically assigns nellburn results to rocks based on their properties
							runAfterLoad(function() {
								var rockdataElements = Object.keys(elements).filter(function(name) {
									return (
										elements[name]._data && 
										!["blackened_carbonate","nellish","nellsand_material"].includes(elements[name]._data[0])
									)
								});

								for(i = 0; i < rockdataElements.length; i++) {
									var name = rockdataElements[i];
									var info = elements[name];
									switch(info._data[1]) {
										case "phanerite":
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "gehennite"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "gehennite_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "gehennite_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "gehennite_sand"
													break;
												case "dust":
													nellburnObject[name] = "gehennite_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_gehennite_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_gehennite_sand"
													break;
												case "sediment":
													nellburnObject[name] = "gehennite_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "gehennite_sandy_water"
													break;
											};
											break;
										case "aphanite":
											//console.log(info._data[2]);
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "nellrock"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "nellrock_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "nellrock_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "nellrock_sand"
													break;
												case "dust":
													nellburnObject[name] = "nellrock_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_nellrock_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_nellrock_sand"
													break;
												case "sediment":
													nellburnObject[name] = "nellrock_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "nellrock_sandy_water"
													break;
											};
											break;
										case "vesiculite":
											//console.log(info._data[2]);
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "hadean_sponge"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "hadean_sponge_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "hadean_sponge_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "hadean_sponge_sand"
													break;
												case "dust":
													nellburnObject[name] = "hadean_sponge_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_hadean_sponge_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_hadean_sponge_sand"
													break;
												case "sediment":
													nellburnObject[name] = "hadean_sponge_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "hadean_sponge_sandy_water"
													break;
											};
											break;
										case "vitrite":
											//console.log(info._data[2]);
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "gehidian"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "gehidian_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "gehidian_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "gehidian_sand"
													break;
												case "dust":
													nellburnObject[name] = "gehidian_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_gehidian_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_gehidian_sand"
													break;
												case "sediment":
													nellburnObject[name] = "gehidian_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "gehidian_sandy_water"
													break;
											};
											break;
										case "metaphanerite":
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "sheolite"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "sheolite_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "sheolite_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "sheolite_sand"
													break;
												case "dust":
													nellburnObject[name] = "sheolite_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_sheolite_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_sheolite_sand"
													break;
												case "sediment":
													nellburnObject[name] = "sheolite_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "sheolite_sandy_water"
													break;
											};
											break;
										case "metaaphanite":
											//console.log(info._data[2]);
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "nellbolite"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "nellbolite_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "nellbolite_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "nellbolite_sand"
													break;
												case "dust":
													nellburnObject[name] = "nellbolite_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_nellbolite_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_nellbolite_sand"
													break;
												case "sediment":
													nellburnObject[name] = "nellbolite_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "nellbolite_sandy_water"
													break;
											};
											break;
										case "metavesiculite":
											//console.log(info._data[2]);
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "metahadiculite"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "metahadiculite_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "metahadiculite_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "metahadiculite_sand"
													break;
												case "dust":
													nellburnObject[name] = "metahadiculite_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_metahadiculite_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_metahadiculite_sand"
													break;
												case "sediment":
													nellburnObject[name] = "metahadiculite_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "metahadiculite_sandy_water"
													break;
											};
											break;
										case "metavitrite":
											//console.log(info._data[2]);
											switch(info._data[2]) {
												case "igneous_rock":
													nellburnObject[name] = "metagehitrite"
													break;
												case "solid_igneous_rock":
													nellburnObject[name] = "metagehitrite_wall"
													break;
												case "igneous_gravel":
													nellburnObject[name] = "metagehitrite_gravel"
													break;
												case "particulate":
													nellburnObject[name] = "metagehitrite_sand"
													break;
												case "dust":
													nellburnObject[name] = "metagehitrite_dust"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_metagehitrite_sand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_metagehitrite_sand"
													break;
												case "sediment":
													nellburnObject[name] = "metagehitrite_sand_sediment"
													break;
												case "suspension":
													nellburnObject[name] = "metagehitrite_sandy_water"
													break;
											};
											break;
										case "phanerite_sandstone":
											nellburnObject[name] = "gehennite_sandstone"
											break;
										case "aphanite_sandstone":
											nellburnObject[name] = "nellrock_sandstone"
											break;
										case "vesiculite_sandstone":
											nellburnObject[name] = "hadean_sponge_sandstone"
											break;
										case "vitrite_sandstone":
											nellburnObject[name] = "gehidian_sandstone"
											break;
										case "metaphanerite_sandstone":
											nellburnObject[name] = "sheolite_sandstone"
											break;
										case "metaaphanite_sandstone":
											nellburnObject[name] = "nellbolite_sandstone"
											break;
										case "metavesiculite_sandstone":
											nellburnObject[name] = "metahadiculite_sandstone"
											break;
										case "metavitrite_sandstone":
											nellburnObject[name] = "metagehitrite_sandstone"
											break;
										case "silica_sandstone":
											nellburnObject[name] = "nellsandstone"
											break;
										case "magma":
											switch(info._data[2]) {
												case "liquid":
													nellburnObject[name] = "nellish_magma"
													break;
												case "vaporized":
													nellburnObject[name] = "vaporized_nellish_magma"
													break;
												case "cloud":
													nellburnObject[name] = "nellish_magma_cloud"
													break;
											};
											break;
										case "rock":
											switch(name) {
												case "shale":
													nellburnObject[name] = "nellsandstone"
													break;
												case "limestone":
													nellburnObject[name] = "black_limestone"
													break;
											};
											break;
										case "calcium":
											switch(info._data[2]) {
												case "particulate":
												case "mineral":
													nellburnObject[name] = bccd
													break;
												case "sediment":
													nellburnObject[name] = "blackened_calcium_carbonate_dust_sediment"
													break;
												case "suspension":
													nellburnObject[name] = bccs
													break;
											};
											break;
										case "silica":
										case "crystalline":
											switch(info._data[2]) {
												case "particulate":
													nellburnObject[name] = "nellsand"
													break;
												case "wet_particulate":
													nellburnObject[name] = "wet_nellsand"
													break;
												case "packed_particulate":
													nellburnObject[name] = "packed_nellsand"
													break;
												case "suspension":
													nellburnObject[name] = "nellsand_water"
													break;
												case "sediment":
													nellburnObject[name] = "nellsand_sediment"
													break;
											};
											break;
										case "soil":
										case "dry_soil":
										case "clay":
											switch(info._data[2]) {
												case "particulate":
													nellburnObject[name] = "nellsand"
													break;
												case "suspension":
													nellburnObject[name] = "nellsand_water"
													break;
												case "sediment":
													nellburnObject[name] = "nellsand_sediment"
													break;
											};
											break;
										case "crystalline_sandstone":
										case "soil_sandstone":
											nellburnObject[name] = "nell_ash"
											break;
										case "sedimentary":
											if(info._data[0] == "calcium") {
												nellburnObject[name] = "black_limestone";
												break
											};
										default:
											console.log("Nellburn assignment: Unknown _data[1] value for element",name,info._data);
									};
								};

								nellfireSpawnBlacklist = ["nellfire"];
							});
							//Glad that's over

						//Rocks

							makeNonSandSedimentationElements("nellsand","nellsand_water","nellsandstone");

							newIgneousCompositionFamily(
								"nellish",

								10, 3012, -96, -12, 3812,

								"gehennite", ["#857c71", "#b5a98d", "#91847c", "#948b68", "#8a834a", "#adad34"], 2011, 3432,
								"sheolite", ["#785848","#8c7e5b","#9c745c","#80463b"], 2011, 3852,

								"nellrock", ["#a15a42","#997849","#946043","#8c533e","#a66658"], 2036, 3371,
								"nellbolite", ["#7a3017","#693d21","#8a673a"], 2036, 3671,

								"hadean_sponge", ["#e66785", "#b54761", "#cc8156", "#dbc760", "#ab9a44"], 2213, 1012,
								"metahadiculite", ["#a0a35d","#665d37", "#7b804d", "#869151", "#6e443f"], 2213, 1412,

								"gehidian", ["#754c2f", "#855d3a", "#702a1c", "#691a41"], 2054, 3112,
								"metagehitrite", ["#5e4f2a","#53544e", "#68787a", "#454f46", "#5e584b"], 2054, 3312,

								1,9
							);

							//More sedimentaries because nellfire also transforms limestone, and the associated baggage
							var bccd = "blackened_calcium_carbonate_dust";
							var bccs = "blackened_calcium_carbonate_solution";

							elements.calcium_carbonate_dust.reactions ??= {};
							elements.calcium_carbonate_dust.reactions.charcoal = {
								elem1: bccd, elem2: [bccd, "charcoal", "charcoal"]
							};


							newPowder(bccd,"#948e87",2771,804,["carbon_dioxide","quicklime","charcoal","ash"]);
							elements[bccd]._data = ["blackened_carbonate","blackened_carbonate","particulate"];
							elements[bccd].nellfireImmune = true;

							makeNonSandSedimentationElements(bccd,bccs,"black_limestone");

							elements.blackened_calcium_carbonate_dust_sediment.color = "#756e64";
							elements.blackened_calcium_carbonate_dust_sediment.nellfireImmune = true;

							elements.black_limestone.color = "#575148";
							elements.black_limestone.stateHigh = ["fire","smoke","charcoal","carbon_dioxide","quicklime","quicklime","quicklime","quicklime","quicklime","quicklime","quicklime","quicklime"];
							elements[bccs].nellfireImmune = true;

							elements.water.reactions[bccd] = {
								"elem1":"calcium_carbonate_solution",
								"elem2":[bccd,bccd,bccd,bccs],
								"chance":0.004
							};

							elements.seltzer.reactions[bccd] = {
								"elem1":bccs,
								"elem2":[bccd,bccd,bccd,bccs],
								"chance":0.02
							};

							var resultingAutoElems = [ "gehennite", "gehennite_wall", "gehennite_gravel", "nellrock", "nellrock_wall", "gehennite_sand", "gehennite_dust", "wet_gehennite_sand", "packed_gehennite_sand", "nellrock_gravel", "nellrock_sand", "nellrock_dust", "wet_nellrock_sand", "packed_nellrock_sand", "hadean_sponge", "hadean_sponge_wall", "hadean_sponge_gravel", "hadean_sponge_sand", "hadean_sponge_dust", "wet_hadean_sponge_sand", "packed_hadean_sponge_sand", "gehidian", "gehidian_wall", "gehidian_shard", "gehidian_sand", "gehidian_dust", "wet_gehidian_sand", "packed_gehidian_sand", "nellish_magma", "vaporized_nellish_magma", "nellish_magma_cloud", "gehennite_sandy_water", "gehennite_sand_sediment", "gehennite_sandstone", "nellrock_sandy_water", "nellrock_sand_sediment", "nellrock_sandstone", "hadean_sponge_sandy_water", "hadean_sponge_sand_sediment", "hadean_sponge_sandstone", "gehidian_sandy_water", "gehidian_sand_sediment", "gehidian_sandstone", bccd, bccs, "black_limestone" ] //hard-coded

							runAfterLoad(function() {
								for(i = 0; i < resultingAutoElems.length; i++) {
									if(!elements[resultingAutoElems[i]]) {
										console.error(resultingAutoElems[i]);
										continue;
									}
									elements[resultingAutoElems[i]].nellfireImmune = true;
								};
							});
					
					//Crimson transformation assigner
						runAfterAutogen(function() {
							crimsonAssignmentUnknownData1Errors = {};
							
							var rockdataElements = Object.keys(elements).filter(function(name) {
								return (
									elements[name]._data && 
									!["crimson"].includes(elements[name]._data[0])
								)
							});

							for(i = 0; i < rockdataElements.length; i++) {
								var name = rockdataElements[i];
								var info = elements[name];
								switch(info._data[1]) {
									case "phanerite":
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "crimstone"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "crimstone_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "crimstone_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "crimstone_sand"
												break;
											case "dust":
												crimsonObject[name] = "crimstone_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_crimstone_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_crimstone_sand"
												break;
											case "sediment":
												crimsonObject[name] = "crimstone_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "crimstone_sandy_water"
												break;
										};
										break;
									case "aphanite":
										//console.log(info._data[2]);
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "crimsalt"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "crimsalt_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "crimsalt_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "crimsalt_sand"
												break;
											case "dust":
												crimsonObject[name] = "crimsalt_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_crimsalt_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_crimsalt_sand"
												break;
											case "sediment":
												crimsonObject[name] = "crimsalt_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "crimsalt_sandy_water"
												break;
										};
										break;
									case "vesiculite":
										//console.log(info._data[2]);
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "crimscoria"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "crimscoria_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "crimscoria_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "crimscoria_sand"
												break;
											case "dust":
												crimsonObject[name] = "crimscoria_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_crimscoria_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_crimscoria_sand"
												break;
											case "sediment":
												crimsonObject[name] = "crimscoria_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "crimscoria_sandy_water"
												break;
										};
										break;
									case "vitrite":
										//console.log(info._data[2]);
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "crimidian"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "crimidian_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "crimidian_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "crimidian_sand"
												break;
											case "dust":
												crimsonObject[name] = "crimidian_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_crimidian_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_crimidian_sand"
												break;
											case "sediment":
												crimsonObject[name] = "crimidian_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "crimidian_sandy_water"
												break;
										};
										break;
									case "metaphanerite":
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "metacrimstone"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "metacrimstone_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "metacrimstone_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "metacrimstone_sand"
												break;
											case "dust":
												crimsonObject[name] = "metacrimstone_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_metacrimstone_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_metacrimstone_sand"
												break;
											case "sediment":
												crimsonObject[name] = "metacrimstone_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "metacrimstone_sandy_water"
												break;
										};
										break;
									case "metaaphanite":
										//console.log(info._data[2]);
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "metacrimsalt"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "metacrimsalt_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "metacrimsalt_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "metacrimsalt_sand"
												break;
											case "dust":
												crimsonObject[name] = "metacrimsalt_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_metacrimsalt_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_metacrimsalt_sand"
												break;
											case "sediment":
												crimsonObject[name] = "metacrimsalt_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "metacrimsalt_sandy_water"
												break;
										};
										break;
									case "metavesiculite":
										//console.log(info._data[2]);
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "metacrimscoria"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "metacrimscoria_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "metacrimscoria_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "metacrimscoria_sand"
												break;
											case "dust":
												crimsonObject[name] = "metacrimscoria_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_metacrimscoria_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_metacrimscoria_sand"
												break;
											case "sediment":
												crimsonObject[name] = "metacrimscoria_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "metacrimscoria_sandy_water"
												break;
										};
										break;
									case "metavitrite":
										//console.log(info._data[2]);
										switch(info._data[2]) {
											case "igneous_rock":
												crimsonObject[name] = "metacrimidian"
												break;
											case "solid_igneous_rock":
												crimsonObject[name] = "metacrimidian_wall"
												break;
											case "igneous_gravel":
												crimsonObject[name] = "metacrimidian_gravel"
												break;
											case "particulate":
												crimsonObject[name] = "metacrimidian_sand"
												break;
											case "dust":
												crimsonObject[name] = "metacrimidian_dust"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_metacrimidian_sand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_metacrimidian_sand"
												break;
											case "sediment":
												crimsonObject[name] = "metacrimidian_sand_sediment"
												break;
											case "suspension":
												crimsonObject[name] = "metacrimidian_sandy_water"
												break;
										};
										break;
									case "phanerite_sandstone":
										crimsonObject[name] = "crimstone_sandstone"
										break;
									case "aphanite_sandstone":
										crimsonObject[name] = "crimsalt_sandstone"
										break;
									case "vesiculite_sandstone":
										crimsonObject[name] = "crimscoria_sandstone"
										break;
									case "vitrite_sandstone":
										crimsonObject[name] = "crimidian_sandstone"
										break;
									case "phanerite_sandstone":
										crimsonObject[name] = "metacrimstone_sandstone"
										break;
									case "aphanite_sandstone":
										crimsonObject[name] = "metacrimsalt_sandstone"
										break;
									case "vesiculite_sandstone":
										crimsonObject[name] = "metacrimscoria_sandstone"
										break;
									case "vitrite_sandstone":
										crimsonObject[name] = "metacrimidian_sandstone"
										break;
									case "crystalline_sandstone":
									case "silica_sandstone":
									case "sedimentary":
									case "rock":
									case "sandstone":
									case "silica":
										crimsonObject[name] = "crimsandstone"
										break;
									case "soil_sandstone":
										crimsonObject[name] = "crimsoilstone"
										break;
									case "rainbow_soil_sandstone":
										crimsonObject[name] = "crimsoilstone"
										break;
									case "magma":
										switch(info._data[2]) {
											case "liquid":
												crimsonObject[name] = "crimson_magma"
												break;
											case "vaporized":
												crimsonObject[name] = "vaporized_crimson_magma"
												break;
											case "cloud":
												crimsonObject[name] = "crimson_magma_cloud"
												break;
										};
										break;
									case "crystalline":
										switch(info._data[2]) {
											case "particulate":
												crimsonObject[name] = "crimsand"
												break;
											case "wet_particulate":
												crimsonObject[name] = "wet_crimsand"
												break;
											case "packed_particulate":
												crimsonObject[name] = "packed_crimsand"
												break;
											case "suspension":
												crimsonObject[name] = "crimsand_water"
												break;
											case "sediment":
												crimsonObject[name] = "crimsand_sediment"
												break;
										};
										break;
									case "soil":
									case "dry_soil":
									case "clay":
										switch(info._data[2]) {
											case "particulate":
												crimsonObject[name] = "crimsand"
												break;
											case "suspension":
												crimsonObject[name] = "crimsand_water"
												break;
											case "sediment":
												crimsonObject[name] = "crimsand_sediment"
												break;
										};
										break;
									default:
										crimsonAssignmentUnknownData1Errors[name] = info._data;
								};
							};
							
							
							//Manual overrides because JavaScript sees the cases and pretends it doesn't know what they mean
							crimsonObject.soilstone = "crimsoilstone";
							crimsonObject.sandstone = "crimsandstone";
							crimsonObject.rosephyllite = "metacrimsalt";
						});
					//Assigner end



		/*	//Rocks

				//Igneous

					//Phaneritic

						//Ultramafic: peridotite

							var molten_olivine = ["molten_fayalite","molten_forsterite","molten_forsterite"];

							//apparently olivine sand exists
							elements.olivine_sand = {
								color: ['#b5a773', '#b5af78', '#b2b471', '#bab07b', '#b4ae74', '#b4b471', '#b5a970', '#b4b476'],
								behavior: behaviors.POWDER,
								tempHigh: 1750, //https://www.indiamart.com/olivineindia/olivine-sand.html
								stateHigh: molten_olivine,
								category: "land",
								state: "solid",
								density: 1720,
							};

							elements.wet_olivine_sand = {
								color: ["#a08d4b","#918949","999c49","#aa9b50","#8f8743","#adad53","#9d8f48","#838f43"],
								behavior: behaviors.STURDYPOWDER,
								reactions: {
									"sand": { "elem1":"sand", "elem2":"wet_olivine_sand", "chance":0.0005, "oneway":true },
									"olivine_sand": { "elem1":"olivine_sand", "elem2":"wet_olivine_sand", "chance":0.0005, "oneway":true },
									"dirt": { "elem1":"olivine_sand", "elem2":"mud", "chance":0.0005, "oneway":true },
								},
								tempHigh: 100,
								stateHigh: "packed_olivine_sand",
								tempLow: -50,
								stateLow: "packed_olivine_sand",
								category: "land",
								state: "solid",
								density: 2002,
							};

							elements.packed_olivine_sand = {
								color: ["#968f64","#969669","#8d9362","#9d996c","#959465","#8f9362","#949061","#909366"],
								behavior: behaviors.SUPPORT,
								tempHigh: 1700,
								stateHigh: molten_olivine,
								category: "land",
								state: "solid",
								density: 1811,
								breakInto: "olivine_sand",
							};

							elements.water.reactions.olivine_sand = { "elem1": null, "elem2": "wet_olivine_sand" };

							newPowder("fayalite",["#bf7432","#ad8e3e"],4390,1200,null,null);

							newPowder("forsterite","#cccccc",3270,1890,null,null);

							elements.molten_forsterite = {
								reactions: {
									"molten_fayalite": { elem1: "olivine", elem2: ["molten_fayalite","olivine"], tempMax: 1890 },
								},
							};

							elements.olivine = {
								color: ["#7fa14f","#7dba52"],
								behavior: behaviors.POWDER,
								tempHigh: 1890,
								stateHigh: molten_olivine,
								category: "solids",
								state: "solid",
								density: 2700,
								breakInto: "olivine_shard",
							},

							newPowder("olivine_shard",["#97ba65","#7a994e","#99d96c","#7cb553"],2811,1890,molten_olivine,null);						
		*/
			//Gems
			  //There is a mineral classification scheme, but it will take a while to implement if I ever get around to it.
			  //We're assuming that the crystal structures reform properly because I don't want to have to research and implement refrozen amorphous forms.

				//Emerald

					elements.emerald = {
						color: ["#31e31e", "#88fa5a", "#28d419", "#54e823", "#64f235"],
						tempHigh: 1287,
							//1: I can't be arsed to find out what happens to emerald in extreme heat. Apparently, neither can anyone else, and Google is useless for this.
							//2: So I'm just assuming that the chromium impurities are polite and remain in suspension with the molten beryl.
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 2710, //within natural variation
						hardness: 0.8, //Mohs scaled to diamond
					};

				//Amethyst

					elements.amethyst = {
						color: ["#c569e0", "#bd43e0", "#e37aeb", "#ab2fe0", "#b05bd4", "#9b2cdb"],
						tempHigh: 1650,
						//1: Gee, another quartz-like...
						//2: Like with emerald, I'm trusting the impurities to stay dissolved because I don't exactly have any amethyst laying around to melt.
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 2650,
						hardness: 0.7,
					};

					standaloneBrokenFormMaker("iron","scrap",true,"powders","auto","auto","molten_iron",null).hidden = true;
					standaloneBrokenFormMaker("chromium","scrap",true,"powders","auto","auto","molten_chromium",null).hidden = true;
					standaloneBrokenFormMaker("amethyst","shard",true,"powders","auto","auto","molten_amethyst",["silica","silica","silica","silica","silica","silica","silica","silica","silica","iron_scrap"]).hidden = true;

				//Corundum

					elements.corundum = {
						color: ["#e3e3e3", "#d9d9d9", "#cccccc", "#dbdbdb", "#f2f2f2"],
						tempHigh: 2072,
						stateHigh: "molten_alumina",
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 4020,
						hardness: 0.9,
						breakInto: "alumina",
					};


					elements.molten_titanium ??= {}; elements.molten_titanium.tempHigh = 3287;
					elements.molten_iron ??= {}; elements.molten_iron.tempHigh = 2861;
					elements.molten_chromium ??= {}; elements.molten_chromium.tempHigh = 2671;
					elements.molten_copper ??= {}; elements.molten_copper.tempHigh = 4700;
					elements.molten_alumina ??= {};
					elements.molten_alumina.tempHigh = 5400;
					elements.molten_alumina.state = "liquid";
					elements.molten_alumina.autoType = "gas";
					elements.molten_alumina.reactions ??= {};
					elements.molten_alumina.reactions.iron_scrap = {elem1: "molten_sapphire", elem2: ["molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron",null] };
					elements.molten_alumina.reactions.molten_iron = {elem1: "molten_sapphire", elem2: ["molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron","molten_iron",null] };
					elements.molten_alumina.reactions.titanium_scrap = {elem1: "molten_sapphire", elem2: ["molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium",null] };
					elements.molten_alumina.reactions.molten_titanium = {elem1: "molten_sapphire", elem2: ["molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium","molten_titanium",null] };
					elements.molten_alumina.reactions.chromium_scrap = {elem1: "molten_ruby", elem2: ["molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium",null] };
					elements.molten_alumina.reactions.molten_chromium = {elem1: "molten_ruby", elem2: ["molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium","molten_chromium",null] };
					elements.molten_alumina.stateLow = "corundum";

				//Sapphire

					elements.sapphire = {
						color: ["#2d43e3", "#4d5fe3", "#1f30cc", "#375fdb", "#2d39e3"],
						tempHigh: 2040,
							//1: You can actually grow corundum-based gems through the Verneuil process
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 3980,
						hardness: 0.9,
					};

					elements.molten_sapphire ??= {}; elements.molten_sapphire.tick = function(pixel) {
						if(pixel.temp >= 5040) {
							if(Math.random() < 0.005) { //the real proportion of 0.01% is so low that you'd just melt sapphire and get back corundum
								changePixel(pixel,Math.random() < 0.5 ? "titanium_gas" : "iron_gas",false)
							} else {
								changePixel(pixel,"alumina_gas",false)
							}
						};
					};

					standaloneBrokenFormMaker("sapphire","shard",true,"powders","auto","auto","molten_sapphire",["alumina","alumina","alumina","alumina","alumina","alumina","alumina","alumina","alumina","iron_scrap","titanium_scrap"]).hidden = true;

				//Ruby

					elements.ruby = {
						//Corundum with different impurities, so I can copy/paste everything but the color
						color: ["#ff1222", "#ff4545", "#e30b13", "#fa253b", "#f2120f"],
						tempHigh: 2040,
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 3980,
						hardness: 0.9,
					};

					elements.molten_ruby ??= {}; elements.molten_ruby.tick = function(pixel) {
						if(pixel.temp >= 5040) {
							if(Math.random() < 0.02) { //1% also too low
								changePixel(pixel,"chromium_gas",false)
							} else {
								changePixel(pixel,"alumina_gas",false)
							}
						};
					};

					standaloneBrokenFormMaker("ruby","shard",true,"powders","auto","auto","molten_sapphire",["alumina","alumina","alumina","alumina","alumina","alumina","alumina","alumina","alumina","chromium_scrap"]).hidden = true;

				//Spinel (kek)

					elements.spinel = {
						color: ["#ff1261", "#db2776", "#f20732", "#f71670", "#f7167f"],
						tempHigh: 2130,
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 3600,
						hardness: 0.85,
					}

				//Topaz

					elements.topaz = {
						color: ["#f7f431", "#ffff5c", "#f7e048", "#fae43e", "#fff86e", "#ede321"],
						tempHigh: 1340,
						stateHigh: "mullite", //thermal decomposition
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 3500,
						hardness: 0.8,
					};

				//Mullite

					elements.mullite = {
						color: ["#f2d7bf", "#f5cbdc", "#f2dfd3"], //hardly a gemstone, but i will color it like the others regardless
						tempHigh: 1840,
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 3110,
						hardness: 0.7,
					};

				//Onyx

					elements.onyx = {
						color: ["#1a1919", "#070605", "#111313"],
						tempHigh: 1650, //another  silicate  mineral
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 2650,
						hardness: 0.7,
					};

				//Opal

					elements.opal = {
						color: ["#ffcfcf", "#fff0d9", "#fcf7c5", "#e4ffd4", "#d1fff5", "#dcecfa", "#dfdbff", "#f5e0ff", "#f7d0f1"],
						tempHigh: 100,
						stateHigh: ["broken_opal", "broken_opal", "broken_opal", "broken_opal", "broken_opal", "broken_opal", "broken_opal", "broken_opal", "broken_opal", "steam"],
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 2090,
						hardness: 0.6,
						breakInto: ["quartz", "quartz", "quartz", "quartz", "quartz", "quartz", "quartz", "quartz", "quartz", "water"],
					};

					elements.broken_opal = {
						color: ["#f5e6e6", "#ebe2d5", "#f7f6ed", "#e4eddf", "#d8ebe7", "#d8e0e8", "#e4e3e8", "#f4edf7", "#ebebeb"],
						tempHigh: 1650,
						stateHigh: "molten_quartz",
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 2322,
						hardness: 0.55, //it cracks
					};

				//Quartz

					elements.quartz = { //silicates, silicates, and more silicates
						color: ["#f0f0f0", "#e3e3e3", "#f7f7f7"],
						tempHigh: 1650, 
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 2650,
						hardness: 0.7,
					};

					//Re-add molten quartz because it stopped auto-generating

					elements.molten_quartz = {"behavior":behaviors.MOLTEN,"hidden":true,"state":"liquid","category":"states","color":['#ffff78', '#fff078', '#ffb400', '#ffff71', '#ffe371', '#ffaa00', '#ffff7b', '#fff77b', '#ffb900'],"temp":1650,"tempLow":1550,"stateLow":"quartz","density":2385,"viscosity":10000,"reactions":{"ash":{"elem1":null,"elem2":"molten_slag"},"dust":{"elem1":null,"elem2":"molten_slag"},"magma":{"elem1":null,"elem2":"molten_slag"}},"movable":true}

					//Use in glass

					elements.molten_quartz.reactions = {
						quicklime: { elem1: "molten_glass", elem2: ["quicklime", "quicklime", "quicklime", "quicklime", "quicklime", "quicklime", "quicklime", "quicklime", "quicklime", null]} //lack of vanilla washing soda, lack of tripartite reactions
					};
					/*
					elements.elem1.reactions = {
						elem2: { elem1: "elem1_becomes", elem2: "elem2_becomes"}
					};
					*/

				//Pearl (not a mineral)

					elements.pearl = {
						color: ["#e3e3e3", "#e3e0d1", "#eddbce", "#eef2c9", "#d5f5dc", "#d8f2ec", "#fadcf9", "#e3d1c1", "#f2edc9", "#e0f5d7", "#e2beeb", "#e3e3e3", "#e3e0d1", "#eddbce", "#eef2c9", "#d5f5dc", "#d8f2ec", "#fadcf9", "#e3d1c1", "#f2edc9", "#e0f5d7", "#e2beeb", 	"#38332e"],
						tempHigh: 1340, //yay, more thermal decomposition elements
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 772, //It is partly made of proteins and is said to burn, but I can't find an ignition point, so here it melts.
						hardness: 0.45,
					};

				//Jade

					elements.jadeite = {
						color: ["#3D7D31", "#2D6D1F", "#538A2F", "#6A9A37"],
						tempHigh: 1000,
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 3400,
						hardness: 0.65,
					};

			//Soil

				//Dry dirt

					elements.dirt.forceAutoGen = true;

					elements.dry_dirt = {
						color: ["#a88e5e","#8f7950","#8a7045","#9e804c"],
						behavior: [
							"XX|SW:dirt%3 AND SW:mud%6|XX",
							"XX|XX|XX",
							"M2|M1|M2",
						],
						tempHigh: 1200,
						stateHigh: "molten_dirt",
						tempLow: -50,
						stateLow: "dry_permafrost",
						category:"land",
						state: "solid",
						density: 1100,
						_data: ["loam","dry_soil","particulate"]
					},

					elements.water.reactions.dry_dirt = {
						elem1: null, elem2: "dirt", chance: 0.005
					};

					elements.mud.reactions.dry_dirt = {
						elem1: "dirt", elem2: "dirt", chance: 0.005
					};

					elements.dirt._data = ["loam","soil","particulate"];

					elements.molten_dirt = { //added manually because the change to dirt will prevent molten_dirt from being auto-generated
						"behavior": behaviors.MOLTEN,
						"name": "molten_loam",
						"hidden": true,
						"state": "liquid",
						"category": "states",
						"color": ["#EC6A15", "#EC5515", "#EC3F00", "#B85210", "#B84210", "#B83100", "#AE4B0D", "#AE3C0D", "#AE2D00", "#D65A0F", "#D6480F", "#D63600"],
						"temp": 1200,
						"tempLow": 1100,
						"stateLow": "dry_dirt",
						"density": 1098,
						"viscosity": 10000
					}

					elements.molten_dirt.tempHigh = 3000;
					elements.molten_dirt.stateHigh = "vaporized_rock";

					elements.dry_permafrost = {
						color: ["#5B7870","#535D51","#52746A","#5A7A6F"],
						behavior: behaviors.POWDER, //not enough water for cementing
						temp: -50,
						tempHigh: 10,
						stateHigh: "dry_dirt",
						category: "land",
						state: "solid",
						state: "solid",
						density: 1200,
						_data: ["loam","soil","particulate"]
					}

					elements.dirt.tempHigh = 110;
					elements.dirt.stateHigh = "dry_dirt";

			//Land Element Cults
				/*
				"Cult" is used similarly to its EoD sense; here, it signifies a set of elements that systematically replicates another set of elements except for a given modification.
				In this case, they replicate some land elements; a "yellow" cult, for example, would have yellow_dirt, yellow_mud, yellow_mudstone, yellow_permafrost, yellow_sand...
				*/

				//Radiation

					//System with new special_property_library.js to replace the old mass manual recreation

					radioactiveTransforms = {
						steam: "rad_steam",
						glass: "rad_glass",
						molten_glass: "molten_rad_glass"
					};

					radioactiveTransforms.fire = "rad_fire"
					radioactiveTransforms.torch = "rad_torch"

					specialProperties.radioactive = {
						specialColorFunction: function(pixel,oldColor) {
							var colorJSON = convertColorFormats(oldColor,"json");
							colorJSON.r *= 0.85;
							colorJSON.r += 8;
							colorJSON.g *= 1.4;
							colorJSON.g += 16;
							colorJSON.b *= 0.4;
							colorJSON.b -= 0.4;
							return convertColorFormats(colorJSON,"rgb");
						},
						specialFunction: function(pixel) {
							if(radioactiveTransforms[pixel.element]) {
								var result = radioactiveTransforms[pixel.element];
								while(result instanceof Array) {
									result = result[Math.floor(Math.random() * result.length)]
								};
								changePixel(pixel,result,false);
								return
							};
							for(var i in adjacentCoords) {
								if(Math.random() < 0.005) {
									pixel.temp++;
									var newCoords = [
										pixel.x+adjacentCoords[i][0],
										pixel.y+adjacentCoords[i][1]
									];
									if(isEmpty(newCoords[0],newCoords[1],false)) {
										createPixel("radiation",newCoords[0],newCoords[1])
									}
								};
							};
							if(Math.random() < 0.05) {
								pixel.temp+=0.5;
								if(Math.random() < 0.005) {
									delete pixel.radioactive
								}
							}
						}
					}
					console.log("Radioactive property defined");

				//Main elements

					elements.liquid_irradium = {
						color: "#5499FF",
						behavior: behaviors.LIQUID,
						tick: function(pixel) {
							for(var i = 0; i < adjacentCoords.length; i++) {
								//transformAdjacent(pixel,radioactiveTransforms)
								var newCoords = [
									pixel.x+adjacentCoords[i][0],
									pixel.y+adjacentCoords[i][1]
								];
								var newPixel = pixelMap[newCoords[0]]?.[newCoords[1]];
								if(newPixel && newPixel.element !== pixel.element) {
									newPixel.radioactive = true
								}
							}
						},
						//Becomes rainbow sand by water or poison, as well as by protocite, or bio-ooze
						//Becomes sulfuric acid on contact with it
						//Becomes corrupt slime by elder fluid
						//Converts black tar and organic soup into itself
						//Turns either grav liquid into aether dust, as well as liquid crystal
						//Turns blood into bloodstone
						//Turns blue slime into black slime
						//Made by {mercury or bio-ooze} and protocite
						category:"liquids",
						state: "liquid",
						density: 18180,	//Cherry-picked from a Tumblr headcanon
										//https://omniblog-of-starbound.tumblr.com/post/188424072728/starbound-element-headcannon-modded-metals
						viscosity: 80.1,	//probably misinterpreting tickDelta, and w/o the game assets, I can't compare against water, so this is in relation to H2SO4 scaled to its density in cP and under the assumption that water visc = 1
					}

					/*
						//Metamorphism will be driven using solely temperature. 
						//Pressure simulation, due to how the game is coded, will be limited to requiring the rock to be surrounded.

					elements.slate = {
						color: ["#787B80", "#535557", "#695E58", "#696969", "#6B5D5B"],
						tempHigh: 200,
						stateHigh: "felsic_magma",
						category: "solid rock",
						state: "solid",
						density: 2640,
						hardness: 0.7,
						maxColorOffset: 15,
						hardness: 0.3,
						_data: ["clay", "rock", "sedimentary_rock"]
					};

					elements.shale.onTryMoveInto = function(pixel,otherPixel) {
						var otherData = elements[otherPixel.element];
						if(otherData.category == "magmas" && Math.random() < 0.005 && pixel.temp > 650) {
							var around = getCirclePixels(pixel.x,pixel.y,2);
							around.forEach(pixel => changePixel(pixel,"hornfels"));
							return
						};
						if(pixel.exposedToAir) { return };
						if(pixel.temp > 800 && Math.random () < 0.0007) {
							changePixel(pixel,"migmatite")
						} else if(pixel.temp > 600 && Math.random () < 0.001) {
							changePixel(pixel,"gneiss")
						} else if(pixel.temp > 400 && Math.random () < 0.001) {
							changePixel(pixel,"schist")
						} else if(pixel.temp > 200 && Math.random () < 0.001) {
							changePixel(pixel,"slate")
						};
						return
					};*/

					runAfterLoad(function() {
						rocksSandsAndSoilsToGiveHotForms = Object.keys(elements).filter(
							function(elemName) {
								//console.log(elemName,elements[elemName]._data?.[2]);
								return (!(["clay","limestone","black_limestone"].includes(elemName)) && ["igneous_rock","solid_igneous_rock","igneous_gravel","sedimentary_rock","particulate","packed_particulate","metamorphic_rock","solid_metamorphic_rock","metamorphic_gravel"].includes(elements[elemName]._data?.[2]))
							}
						);
						if(rocksSandsAndSoilsToGiveHotForms.includes("clay")) { rocksSandsAndSoilsToGiveHotForms.splice(rocksSandsAndSoilsToGiveHotForms.indexOf("clay"),1) };
						hotRockFunction(); //needs to happen after dry dirt is defined
						elements.hot_crimsoilstone.stateHigh = "molten_crimsoil";
						elements.crimsoil.tempHigh = 100;
						elements.crimsoil.stateHigh = "dry_crimsoil";
						elements.hot_rainbow_soilstone.stateHigh = "molten_rainbow_dirt";
						elements.rainbow_dirt.tempHigh = 100;
						elements.rainbow_dirt.stateHigh = "dry_rainbow_dirt";
					});

		//Generation

			//TNT world

				//Supplementary elements

					elements.oil_cloud = {
						color: "#8c4331",
						behavior: [
							"XX|XX|XX",
							"XX|CH:oil%0.05|M1%2.5 AND BO",
							"XX|XX|XX",
						],
						category:"gases",
						temp: 30,
						state: "gas",
						density: 0.5,
						burn: 60,
						burnTime: 15,
						burnInto: "explosion", //atomization moment
						ignoreAir: true,
						stain: 0.02,
					};

					elements.oil_cloud_floater = {
						color: "#8c4331",
						behavior: [
							"M2|M1|M2",
							"M1%80|CH:oil_cloud%0.2|M1%80",
							"M%60|XX|M2%60",
						],
						reactions: {
							"oil_cloud_floater": { elem1: "oil_cloud", elem2: "oil_cloud", chance: 0.003 },
							"oil_cloud": { elem1: "oil_cloud", elem2: "oil_cloud", chance: 0.01 }
						},
						category:"gases",
						temp: 30, //otherwise identical
						state: "gas",
						density: 0.5,
						burn: 60,
						burnTime: 15,
						burnInto: "explosion", //atomization moment
						stain: 0.02,
					};

				//Main preset

					worldgentypes.tnt_world = {
						name: "TNT World", //unimplemented
						layers: [
							[0.9, "red_gas", 0.50],
							[0.9, "oil_cloud_floater"],
							[0.65, "coal", 0.1],
							[0.65, "nitro"],
							[0.55, "nitro", 0.5],
							[0.2, "coal", 0.2],
							[0.2, "tnt"],
							[0.05, "coal", 0.3],
							[0.05, "c4"],
							[0.0, "coal", 0.4],
							[0.0, "lamp_oil"]
						]
					};

			//Ice world

				//Supplementary elements

					elements.snow_cloud_floater = {
						color: "#7e8691",
						behavior: [
							"M2|M1|M2",
							"M1%80|CH:snow_cloud%0.2|M1%80",
							"M%60|XX|M2%60",
						],
						reactions: {
							"snow_cloud_floater": { elem1: "snow_cloud", elem2: "snow_cloud", chance: 0.003 },
							"snow_cloud": { elem1: "snow_cloud", elem2: "snow_cloud", chance: 0.01 }
						},
						category:"gases",
						temp:-10,
						tempHigh:30,
						stateHigh:"rain_cloud",
						tempLow:-200,
						stateLow:"hail_cloud",
						state:"gas",
						density:0.55,
						conduct:0.01,
						movable:true,
						isGas:true
					};

				//Main preset

					worldgentypes.ice = {
						layers: [
							//[0.95, "snow_cloud_floater"], //le cutting room floor has arrived
							[0.9, "snow"],
							[0.65, "ice"],
							[0.6, "gravel"],
							[0.35, "permafrost"],
							[0, "rock"]
						],
						temperature: -20
					};

					/*worldgentypes.nuclear_wasteland = {
						layers: [
							[0.9, "smoke", 0.5],
							[0.9, "rad_snow_cloud_floater", 0.75],
							[0.82, "fallout", 0.4],
							[0.7, "liquid_irradium", 0.05],
							[0.7, "dead_plant", 0.12],
							[0.55, "radioactive_dirt"],
							[0.45, "radioactive_rock"],
							[0.25, "uranium", 0.4],
							[0.35, "radioactive_rock", 0.5],
							[0.3, "radioactive_gravel", 0.5],
							[0.2, "uranium", 0.2],
							[0.05, "rock"],
							[0, "basalt"],
						],
						temperature: -5 //nuclear winter
					};*/

			//Dark world

			worldgentypes.dark = {
				layers: [
					[0.8, "carbon_dioxide"],
					[0.65, "ink"],
					[0.5, "charcoal"],
					[0, "basalt"]
				]
			};

			//Money world

			worldgentypes.money = {
				layers: [
					[0.9, "emerald"],
					[0.6, "diamond"],
					[0.3, "gold_coin"],
					[0.1, "ruby", 1/3],
					[0.1, "amethyst", 1/2],
					[0.1, "sapphire"],
					[-0.1, "pearl", 0.4],
					[-0.1, "onyx"]
				]
			};

			//Concrete

			worldgentypes.concrete = {
				layers: [
					[0.13, "concrete"],
					[0.1, "concrete", 0.5],
					[-0.1, "dirt"]
				],
				heightVariance: 0.00000000000000000000000000000001, //R74n didn't use the nullish ??, so 0 is disallowed.
			};

			//Star world
			//If GWSN can have a decidedly Earth-y name and a space concept, then I should be able to do the same

				//Supplementary elements

					elements.liquid_stellar_plasma = {
						color: "#ffffbd",
						colorOn: "#ffffbd",
						behavior: [
							"XX|M2%5 AND CR:plasma%1|XX",
							"M2|XX|M2",
							"M1|M1|M1",
						],
						behaviorOn: [
							"XX|M2%10 AND M1%0.5 AND CR:plasma%2.3|XX",
							"M2|XX|M2",
							"M1|M1|M1",
						],
						tick: function(pixel) {
							almostSun(pixel,0.6,stellarPlasmaSpreadWhitelist);
						},
						temp:5500,
						isGas: true,
						tempLow:2300,
						stateLow: "plasma",
						category: "liquids",
						state: "liquid",
						density: 1000, //density actually depends on depth in the star: https://astronomy.stackexchange.com/a/32734
						conduct: 0.5,
					};

					elements.stellar_plasma = {
						color: "#ffffbd",
						colorOn: "#ffffbd",
						behavior: [
							"M2|M1 AND CR:plasma%0.6|M2",
							"M1 AND CR:plasma%0.6|XX|M1 AND CR:plasma%0.6",
							"M2|M1 AND CR:plasma%0.6|M2",
						],
						behaviorOn: [
							"M2|M1 AND CR:plasma%1|M2",
							"M1 AND CR:plasma%1|XX|M1 AND CR:plasma%1",
							"M2|M1 AND CR:plasma%1|M2",
						],
						tick: function(pixel) {
							almostSun(pixel,0.5,stellarPlasmaSpreadWhitelist);
						},
						temp:5500,
						tempLow:2300,
						stateLow: "plasma",
						category: "gases",
						state: "gas",
						density: 10,
						conduct: 0.5,
					};

					elements.neutron_star = {
						color: "#e9eaf7",
						colorOn: "#ffffbd",
						behavior: [
							"XX|CR:neutron%0.1|XX", //no neutrinos though
							"CR:neutron%0.1|XX|CR:neutron%0.1",
							"XX|CR:neutron%0.1|XX"
						],
						tick: function(pixel) {
							nsTick(pixel,0.7,stellarPlasmaSpreadWhitelist);
						},
						temp: 1e12,
						category: "special",
						state: "gas",
						density: 1e17,
						insulate: true,
						conduct: 1,
					};

					elements.liquid_degenerate_neutronium = {
						color: "#e9eaf7",
						behavior: [
							"XX|M2%5 AND CR:neutron%0.6|XX",
							"M2|XX|M2",
							"M1|M1|M1",
						],
						behaviorOn: [
							"XX|M2%10 AND M1%0.5 AND CR:neutron%1.2|XX",
							"M2|XX|M2",
							"M1|M1|M1",
						],
						tick: function(pixel) {
							nsTick(pixel,0.7,stellarPlasmaSpreadWhitelist);
						},
						temp:1e6,
						isGas: true,
						tempLow:2300,
						stateLow: elements.liquid_neutronium ? "liquid_neutronium" : "neutron",
						category: "liquids",
						state: "liquid",
						density: 100000, //i'm not doing any more research on these neutron stars because google is useless
						conduct: 1,
					};

					elements.gaseous_degenerate_neutronium = {
						color: "#e9eaf7",
						behavior: [
							"M2|M1 AND CR:neutron%0.6|M2",
							"M1 AND CR:neutron%0.6|XX|M1 AND CR:neutron%0.6",
							"M2|M1 AND CR:neutron%0.6|M2",
						],
						behaviorOn: [
							"M2|M1 AND CR:neutron%1|M2",
							"M1 AND CR:neutron%1|XX|M1 AND CR:neutron%1",
							"M2|M1 AND CR:neutron%1|M2",
						],
						tick: function(pixel) {
							nsTick(pixel,0.6,stellarPlasmaSpreadWhitelist);
						},
						temp:1e6,
						isGas: true,
						tempLow:2300,
						stateLow: "neutron",
						category: "gases",
						state: "gas",
						density: 10000, //i'm not doing any more research on these neutron stars because google is useless
						conduct: 1,
					};

					elements.supernova.behavior = [
						"XX|XX|XX",
						"XX|EX:80>plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,molten_iron,molten_uranium,molten_lead AND CH:neutron_star,neutron_star,neutron_star,neutronium,quark_matter,void|XX",
						"XX|XX|XX",
					]

					elements.plasma.noConduct = ["plasma_torch","stellar_plasma","liquid_stellar_plasma","liquid_degenerate_neutronium","gaseous_degenerate_neutronium","neutron_star"]; //I can't suppress the charge overlay and keep the tick color, only effective with noConduct.js but not strictly required

					//Tangentially linked
					elements.rainbow_sun = {
						color: ["#ffbdbd", "#f2ffbd", "#bdffd7", "#bdd7ff", "#f2bdff"],
						tick: function(pixel) {
							starLightAndConduction(pixel,rainbowSunColor(pixel),["sun","nellsun","rainbow_sun"])
						},
						reactions: {
							"hydrogen": { "elem2":"helium", "temp1":5 },
							"helium": { "elem2":"carbon_dioxide", "temp1":5, "tempMax":3600 },
							"carbon_dioxide": { "elem2":"neon", "temp1":5, "tempMax":1800 },
						},
						temp: 5700,
						tempLow: -100,
						stateLow: "supernova",
						category: "special",
						state: "gas",
						//density: 1408, 
						insulate: true,
						nellfireImmune: true,
					};

					//Tangentially linked
					elements.rainbow_fire = {
						color: [
							{h: 330, s: 100, l: 56},
							{h: 0, s: 100, l: 59},
							{h: 22, s: 100, l: 58},
							{h: 42, s: 100, l: 57},
							{h: 60, s: 100, l: 55},
							{h: 73, s: 100, l: 49},
							{h: 120, s: 100, l: 49.5},
							{h: 159, s: 100, l: 52},
							{h: 159, s: 100, l: 52},
							{h: 180, s: 100, l: 49.5},
							{h: 197, s: 100, l: 59},
							{h: 240, s: 100, l: 58.5},
							{h: 280, s: 94, l: 53},
							{h: 307, s: 100, l: 55}
						].map(x => convertHslObjects(x,"hex")),
						behavior: behaviors.UL_UR,
						reactions: {
							"fire": { "elem1": "rainbow_fire" },
							"water": { "elem1": "color_smoke" },
							"steam": { "elem1": "color_smoke" },
							"carbon_dioxide": { "elem1": "color_smoke" },
							"dirty_water": { "elem1": "color_smoke" },
							"salt_water": { "elem1": "color_smoke" },
							"sugar_water": { "elem1": "color_smoke" },
						},
						nellfireImmune: true,
						fireSpawnChance: 0,
						temp:610,
						tempLow:102,
						stateLow: "color_smoke",
						tempHigh: 7000,
						stateHigh: "plasma",
						category: "energy",
						burning: true,
						burnTime: 44,
						burnInto: "color_smoke",
						state: "gas",
						density: 0.21,
						ignoreAir: true,
						noMix: true,
					};

					elements.color_smoke.tempHigh = 610;
					elements.color_smoke.stateHigh = "rainbow_fire";

					//I guess other worlds do still fall under the purview of TGJS
					function nellsunColor(pixel) {
						if (pixel.temp < 0) { pixel.color = pixelColorPick(pixel,"#615e5e"); var c=0 }
						else if (pixel.temp < 300) { pixel.color = pixelColorPick(pixel,"#664962"); var c=0 }
						else if (pixel.temp < 500) { pixel.color = pixelColorPick(pixel,"#714487"); var c=0.00004 }
						else if (pixel.temp < 850) { pixel.color = pixelColorPick(pixel,"#6a43bf"); var c=0.00015 }
						else if (pixel.temp < 1300) { pixel.color = pixelColorPick(pixel,"#c356db"); var c=0.0005 }
						else if (pixel.temp < 1800) { pixel.color = pixelColorPick(pixel,"#f04ac4"); var c=0.0015 }
						else if (pixel.temp < 2100) { pixel.color = pixelColorPick(pixel,"#f788c5"); var c=0.004 }
						else if (pixel.temp < 2400) { pixel.color = pixelColorPick(pixel,"#f7a3b8"); var c=0.007 }
						else if (pixel.temp < 3200) { pixel.color = pixelColorPick(pixel,"#ffd1d9"); var c=0.01 }
						else if (pixel.temp < 3900) { pixel.color = pixelColorPick(pixel,"#fce1e1"); var c=0.02 }
						else if (pixel.temp < 4600) { pixel.color = pixelColorPick(pixel,"#fff5f5"); var c=0.035 }
						else if (pixel.temp < 6100) { pixel.color = pixelColorPick(pixel,"#ffffff"); var c=0.05 }
						else if (pixel.temp < 7200) { pixel.color = pixelColorPick(pixel,"#f4fad9"); var c=0.075 }
						else if (pixel.temp < 8300) { pixel.color = pixelColorPick(pixel,"#e4f2c2"); var c=0.1 }
						else if (pixel.temp < 10400) { pixel.color = pixelColorPick(pixel,"#c6f2a2"); var c=0.125 }
						else if (pixel.temp < 12500) { pixel.color = pixelColorPick(pixel,"#90f277"); var c=0.15 }
						else if (pixel.temp < 15600) { pixel.color = pixelColorPick(pixel,"#75f754"); var c=0.175 }
						else if (pixel.temp < 18700) { pixel.color = pixelColorPick(pixel,"#5aff30"); var c=0.2 }
						else if (pixel.temp < 21800) { pixel.color = pixelColorPick(pixel,"#1df54f"); var c=0.25 }
						else if (pixel.temp < 28900) { pixel.color = pixelColorPick(pixel,"#3ce873"); var c=0.3 }
						else if (pixel.temp < 36000) { pixel.color = pixelColorPick(pixel,"#4fdb90"); var c=0.35 }
						else if (pixel.temp < 45600) { pixel.color = pixelColorPick(pixel,"#5dcfa7"); var c=0.4 }
						else if (pixel.temp < 52200) { pixel.color = pixelColorPick(pixel,"#4fe3af"); var c=0.45 }
						else if (pixel.temp < 58300) { pixel.color = pixelColorPick(pixel,"#3cfad4"); var c=0.5 }
						else if (pixel.temp < 63400) { pixel.color = pixelColorPick(pixel,"#26f8ff"); var c=0.6 }
						else if (pixel.temp < 68500) { pixel.color = pixelColorPick(pixel,"#19d9ff"); var c=0.7 }
						else if (pixel.temp < 73600) { pixel.color = pixelColorPick(pixel,"#08b1ff"); var c=0.8 }
						else { pixel.color = pixelColorPick(pixel,"#0099ff"); var c=0.9 }
						return c;
					};

					function nellSLAC(pixel,c,whitelist=["sun","nellsun"]) {
						for (var i = 0; i < adjacentCoords.length; i++) {
							var x = pixel.x+adjacentCoords[i][0];
							var y = pixel.y+adjacentCoords[i][1];
							if (isEmpty(x,y)) {
								if (Math.random() <= c) {
									createPixel("light", x, y);
									pixelMap[x][y].color = pixel.color;
								};
							} else if (!outOfBounds(x,y)) {
								var newPixel = pixelMap[x][y];
								//console.log(whitelist,newPixel.element,whitelist.includes(newPixel.element));
								if (pixel.temp!==newPixel.temp && whitelist.includes(newPixel.element)) {
									var avg = (pixel.temp + newPixel.temp)/2;
									pixel.temp = avg;
									newPixel.temp = avg;
									pixelTempCheck(pixel);
									pixelTempCheck(newPixel);
								}
							}
						}
					};

					elements.nellsun = {
						color: ["#ff26ac", "#ffb8e4", "#ffffff", "#b7ffa8", "#2df7b4"],
						tick: function(pixel) {
							nellSLAC(pixel,nellsunColor(pixel));
						},
						reactions: {
							"hydrogen": { "elem2":"helium", "temp1":5 },
							"helium": { "elem2":"carbon_dioxide", "temp1":5, "tempMax":3600 },
							"carbon_dioxide": { "elem2":"neon", "temp1":5, "tempMax":1800 },
						},
						temp: 5504,
						tempLow: -100,
						stateLow: "supernova",
						category: "special",
						state: "gas",
						//density: 1408, 
						insulate: true,
						nellfireImmune: true,
					};

					elements.nellfire = {
						excludeRandom: true,
						color: ["#ff8929","#ffb429","#ffde0a"],
						behavior: [
							"M1|M1|M1",
							"M1%10 AND M2|HT%2|M1%10 AND M2",
							"XX|M2|XX"
						],
						reactions: {
							"fire": { "elem2": "nellfire" },
							/*"water": { "elem1": "smoke" },
							"steam": { "elem1": "smoke" },
							"carbon_dioxide": { "elem1": "smoke" },
							"dirty_water": { "elem1": "smoke" },
							"salt_water": { "elem1": "smoke" },
							"sugar_water": { "elem1": "smoke" },*/
						},
						tick: function(pixel) {
							if(pixel.burning) {
								delete pixel.burning;
								delete pixel.burnStart;
								pixel.nellburn = true;
								pixel.nellburnStart ??= pixelTicks;
							};
						},
						nellBurningWhenConverted: true,
						temp:900,
						nellFireSpawnChance: 0,
						//tempLow:200,
						//stateLow: "smoke",
						category: "energy",
						burning: true,
						nellburnTime: 50,
						nellburnInto: null,
						state: "gas",
						density: 0.06,
						ignoreAir: true,
						noMix: true,
						desc: "Researchers first came into this place and thought it was Hell&mdash; it wasn't, so they renamed it Nell (Not Hell). They still named the materials in it hell references, though.",
					},

					elements.dantite = {
						color: ["#5effba", "#85edd1", "#62d9c7", "#3efa9c", "#21b9db"],
						tempHigh: 2000,
						behavior: behaviors.POWDER,
						category: "powders",
						state: "solid",
						density: 1442,
						hardness: 0.47,
						nellfireImmune: "torch",
					};

					elements.molten_dantite = {
						color: ["#5eff6b", "#70faaa", "#31e08b", "#a1f051"],
						density: 4012,
						hardness: 0.84,
						nellfireImmune: "torch",
					};

					elements.limtupyte = { //λίμνη τοῦ πυρός
						color: ["#212121", "#212121", "#40221f", "#611d14"],
						behavior: behaviors.WALL,
						category: "solids",
						state: "solid",
						density: 8012,
						hardness: 0.87,
						nellfireImmune: true,
						tempHigh: 9011,
						stateHigh: "alpha_limtupyte",
					};

					elements.alpha_limtupyte = {
						name: "α-limtupyte",
						color: ["#6e1414", "#8f210b", "#a34910", "#c27115"],
						density: 10112,
						behavior: behaviors.MOLTEN,
						category: "molten",
						hidden: true,
						state: "liquid",
						hardness: 0.91,
						nellfireImmune: true,
						temp: 9500,
						tempHigh: 11022,
						stateHigh: "beta_limtupyte",
						tempLow: 9011,
						stateLow: "limtupyte",
					};

					elements.beta_limtupyte = {
						name: "β-limtupyte",
						color: ["#e68917", "#ffbd24", "#ffe940", "#ffff61"],
						density: 13178,
						behavior: behaviors.MOLTEN,
						category: "molten",
						hidden: true,
						state: "liquid",
						hardness: 0.93,
						nellfireImmune: true,
						temp: 12000,
						tempHigh: 14316,
						stateHigh: "limtupyte_gas",
						tempLow: 11022,
						stateLow: "alpha_limtupyte",
					};

					elements.limtupyte_gas = {
						color: ["#ffff80", "#ffe940", "#feffd1", "#ffffff"],
						density: 17.12,
						behavior: behaviors.GAS,
						category: "states",
						hidden: true,
						state: "gas",
						hardness: 1,
						nellfireImmune: true,
						temp: 15000,
						tempLow: 14316,
						stateLow: "beta_limtupyte",
					};

				//Main preset

					worldgentypes.star = {
						layers: [
							[0.9, "stellar_plasma"],
							[0.65, "liquid_stellar_plasma"],
							[0.4, "liquid_stellar_plasma", 1/2],
							[0, "sun"],
						],
						complexity: 100,
						baseHeight: 0.3,
						temperature: 6500,
					};

			//Radioactive Desert

				//Main preset

					/*worldgentypes.nuclear_wasteland_desert = {
						layers: [
							[0.97, "fallout", 0.4],
							[0.95, "radioactive_gravel", 0.6],
							[0.65, "liquid_irradium", 0.01],
							[0.65, "cancer", 0.02],
							[0.65, "bone", 0.02],
							[0.65, "radioactive_sand"],
							[0.55, "cancer", 0.01],
							[0.55, "bone", 0.01],
							[0.3, "radioactive_sandstone"],
							[0.05, "radioactive_rock"],
							[-0.78, "radioactive_basalt"]
						],
						temperature: -13
					};*/

		runAfterLoad(function() {
			elements.muddy_water.reactions.mud.elem2 = "soil_sediment";
			elements.muddy_water.reactions.muddy_water.elem2 = "soil_sediment"
		});

	//PRIMITIVE IN-GAME CONSOLE ##
	//featuring stars
		customWorldTypes = {};
		if(localStorage.getItem("customWorldTypes") == null) {
			localStorage.setItem("customWorldTypes",JSON.stringify(customWorldTypes))	
		} else {
			customWorldTypes = JSON.parse(localStorage.getItem("customWorldTypes"));
			for(var name in customWorldTypes) {
				worldgentypes[name] = customWorldTypes[name]
			};
			runAfterLoad(rebuildWorldgenList)
		};

		var promptInputNullishes = ["null","none","","n/a"];
		var eightSpaces = " ".repeat(8);

		commandHelpObject = {
			"set": "Sets properties for every pixel of a given type.\nUsage: set [property] [element] [value] <type>\nDon't include framing characters []<>.\nThe element can be \"all\" to set the property for every pixel.\nNote: Strings can't have spaces because spaces are the separator used in the parsing split().\nArguments in [brackets] are required and ones in <angle brackets> are optional.",

			"test": "Test.",

			"setdimensions": "#This command clears the canvas#\nSets the width and height of the canvas and resets it to regenerate pixelMap.\nThis is offsetted so it doesn't count the OoB area on the top left; a 50x50 save will have a 50x50 usable area.\nUsage: setdimensions [width] [height] <pixel rendering size>.\nDon't include framing characters []<>.\nArguments in [brackets] are required and ones in <angle brackets> are optional.",

			"pixelsize": "Sets the size of the pixels on screen. If no size is given, it instead alerts the current pixel size. Usage: pixelsize <pixel rendering size>.\nDon't include framing characters <>.\nArguments in <angle brackets> are optional.",

			"dimensions": "Alerts the current dimensions. Usage: dimensions",

			"fill": "Fills the screen with the given pixel(s). Usage: fill [Overwrite pixels? (bool)] [element] <additional elements>.\nDon't include framing characters []<>.\nArguments in [brackets] are required and ones in <angle brackets> are optional.\nAdditional elements are separated by spaces.",

			"randomfill": "Fills the screen with pixels from randomChoices. Usage: randomfill <overwrite pixels? (bool, default: true)>\nDon't include framing characters []<>.\nArguments in <angle brackets> are optional.",

			"count": "Tells you the amount of a specified pixel on the screen. Usage: count [element]\nDon't include framing characters []<>.\nArguments in [brackets] are required.",

			"countall": "Logs to console a list of all elements on screen and their amounts. Usage: countall.",

			"worldgen": 
`Names or sets the current world type, lists all world types, or generates a given world type.
Usages:
${eightSpaces}Show the current worldgen setting: worldgen
${eightSpaces}List all available worldgen settings: worldgen list
${eightSpaces}Set the current worldgen setting: worldgen set [setting]
${eightSpaces}Generate a specified worldgen setting: worldgen generate [setting]`,

			"defineworldgen":
`Creates or replaces a worldgen preset.
Usage (See below for formats):  [name] [layers] <baseHeight> <heightVariance> <complexity> <temperature> <decor>.
Don't include framing characters []<>.
Arguments in [brackets] are required and ones in <angle brackets> are optional.",

(Required) name: String. Cannot have spaces. Example: grass
(Required) layers: 
${eightSpaces}Each layer is specified as [RelativeBottomStartPosition:ElementName]<:PixelProbability>
${eightSpaces}Layers are joined with the semicolon ;
${eightSpaces}Layer definitions must not have any spaces.
${eightSpaces}Example full layer definition: 0.85:grass;0.5:dirt;0.05:rock;-0.2:basalt`,

			"defineworldgen2":
`(Optional) baseHeight: Number (ideally between 0 and 1). Default: 0.5.
(Optional) heightVariance: Number. Default: 0.5.
(Optional) complexity: Number. Default: 20.
(Optional) temperature: Number. No default value (use null or none to skip).
(Optional) decor:
${eightSpaces}Each decor layer is specified as [ElementName:PixelProbability]<:DistanceFromTop><:ListOfHexColors>
${eightSpaces}Distance from top, if not specified, defaults to 5.
${eightSpaces}The fourth part (optional) is a list of hex codes (like #FFFFFF) separated by commas.
${eightSpaces}Example full decor definition: bird:0.04:10:#FF0000,#FFFF00,#00FF00;diamond:0.3:5`,

			"help": "Usage: help <command>\nDon't include framing characters []<>\nArguments in <angle brackets> are optional."
		};

		commandHelpObject.stars = "Clears the screen and replaces it with random stars. Usage: stars <density (number, default: 0.001)> <seed (string or number, no default value)>\nDon't include framing characters <>.\nArguments in <angle brackets> are optional."
		commandHelpObject.starseed = "Alerts the last used seed for stars. Usage: starseed";
		var lastStarSeed = "[None]";
		function seededCreateLargeStar(x,y,minRadius,maxRadius,minTemp,maxTemp,randomFunction) {
			//console.log("start");
			var sunPixels = fillCircleReturn("sun",x,y,seededRandBetween(minRadius,maxRadius,randomFunction),true);
			//console.log("filled");
			var randTemp = seededRandBetween(minTemp,maxTemp,randomFunction);
			//console.log("setting temps");
			for(pixelIndex = 0; pixelIndex < sunPixels.length; pixelIndex++) {
				//console.log("pixel " + pixelIndex, sunPixels[pixelIndex].element);
				sunPixels[pixelIndex].temp = randTemp;
			};
			//console.log(sunPixels.map(x => x.element));
			//console.log("finished");
			return true;
		};

			//G
		elements.red_giant = {
			color: "#f19898",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,3,4,1800,3300,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_giant = {
			color: "#a085eb",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,2,3,20000,80000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_giant = {
			color: "#fafad4",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,2,3,6000,11000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

			//SG
		elements.red_supergiant = {
			color: "#f48585",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,6,8,1700,3200,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_supergiant = {
			color: "#93b0ec",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,5,7,19000,83000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_supergiant = {
			color: "#f4f9ae",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,5,7,5500,10500,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

			//HG
		elements.red_hypergiant = {
			color: "#ee5d5d",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,9,12,1600,3100,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_hypergiant = {
			color: "#719df4",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,8,11,18000,84000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_hypergiant = {
			color: "#f7f990",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,8,11,5000,10000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		/*	//Commented debug code: Yellow ultragiant spawner definition but for after the game has already fully loaded
			elements.yellow_ultragiant = {
				color: convertColorFormats("#f7f990","rgb"),
				colorObject: convertColorFormats("#f7f990","json"),
				behavior: behaviors.WALL,
				tick: function(pixel) {
					seededCreateLargeStar(pixel.x,pixel.y,14,17,5000,11000,Math.random);
				},
				category: "stars",
				state: "gas",
				density: 1000,
				id: nextid,
			};
			nextid++;
			elementCount++;
			createElementButton("yellow_ultragiant");*/

		//luminosity class -I
		elements.red_ultragiant = {
			color: "#f04343",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,15,18,1500,3000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_ultragiant = {
			color: "#5488f0",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,14,17,17000,85000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_ultragiant = {
			color: "#fafc7e",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,14,17,4500,9500,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};


		//luminosity class -II
		elements.red_super_ultragiant = {
			color: "#f23329",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,21,25,1400,2900,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_super_ultragiant = {
			color: "#3b85ed",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,20,24,16000,86000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_super_ultragiant = {
			color: "#fcfc65",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,20,24,4000,9000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};


		//luminosity class -III
		elements.red_hyper_ultragiant = {
			color: "#f51a0f",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,28,31,1300,2800,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_hyper_ultragiant = {
			color: "#1b8bf2",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,27,30,15000,87000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_hyper_ultragiant = {
			color: "#faeb46",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,27,30,4000,8500,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};


		//luminosity class -IV
		elements.red_ultra_ultragiant = {
			color: "#e01a00",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,34,37,1200,2700,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.blue_ultra_ultragiant = {
			color: "#0782ed",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,33,36,14000,88000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		elements.yellow_ultra_ultragiant = {
			color: "#f7d52a",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				seededCreateLargeStar(pixel.x,pixel.y,33,36,4000,8000,Math.random);
			},
			category: "stars",
			state: "gas",
			excludeRandom: true,
			maxSize: 1,
			cooldown: defaultCooldown,
			density: 1000,
		};

		function rgbStringToUnvalidatedObject(string) {
			string = string.split(",");
			var red = parseFloat(string[0].substring(4));
			var green = parseFloat(string[1]);
			var blue = parseFloat(string[2].slice(0,-1));
			return {r: red, g: green, b: blue};
		}

		function hslStringToUnvalidatedObject(string) {
			string = string.split(",");
			var hue = parseFloat(string[0].substring(4));
			var saturation = parseFloat(string[1].slice(0,-1));
			var lightness = parseFloat(string[2].slice(0,-2));
			return {h: hue, s: saturation, l: lightness};
		}

		function rebuildWorldgenList() { //vanilla code
			document.getElementById("worldgenselect").innerHTML = '<option value="off">Disabled</option>';
			for (var key in worldgentypes) {
				document.getElementById("worldgenselect").innerHTML += "<option value='" + key + "'>" + key.replace(/_/g, " ").replace(/\b\w/g, l => l.toUpperCase()) + "</option>";
			};
		};

		function bareClear() {
			currentPixels = [];
			pixelMap = [];
			for (var i = 0; i < width; i++) {
				pixelMap[i] = [];
				for (var j = 0; j < height; j++) {
					pixelMap[i][j] = undefined;
				}
			}
		};

		function alertIfError(alertError,text) {
			if(alertError) {
				alert(text);
				return(true);
			} else {
				return(false);
			}
		}

		function alertIfOutput(alertOutput,text) {
			if(alertOutput) { 
				alert(text);
				return(true);
			} else {
				return(false);
			}
		}

		function parsefloatFirst(arr) { var array = arr; array[0] = parseFloat(array[0]); return array };
		function stringToLayers(string) {
			return string.split(";").map(x => x.split(":")).map(y => parsefloatFirst(y));
		};

		function validateSingleHexCode(hexCode) {
			return !!"#FFFFFF".match(/^#[0-9A-F]{6}$/);
		};

		function validateHexColorArray(colorsArray) {
			if(!(colorsArray instanceof Array)) {
				return false;
			};
			if(colorsArray.length == 0) {
				return false;
			};
			var colorsIsValid = 1;
			for(var i in colorsArray) {
				colorsIsValid *= validateSingleHexCode(colorsArray[i]);
			};
			return !!colorsIsValid;
		};

		function validateSingleLayer(layerArray) {
			if(layerArray.length < 2) {
				return false;
			};
			if(isNaN(layerArray[0])) {
				return false;
			};
			if(typeof(layerArray[1]) !== "string") {
				return false;
			};
			if(!elements[layerArray[1]]) {
				return false;
			};
			if(typeof(layerArray[2]) !== "undefined") {
				if(isNaN(layerArray[2])) {
					return false
				};
			};
			return true;
		};

		function validateLayersStructure(layersArray) {
			if(!(layersArray instanceof Array)) {
				return false;
			};
			if(layersArray.length == 0) {
				return false;
			};
			var layersIsValid = 1;
			for(var i in layersArray) {
				layersIsValid *= validateSingleLayer(layersArray[i]);
			};
			return !!layersIsValid;
		};

		function stringToDecor(string) {
			decorLayers = string.split(";").map(x => x.split(":"));
			for(var q in decorLayers) {
				var decorLayer = decorLayers[q];
				if(decorLayer[1]) {
					decorLayer[1] = parseFloat(decorLayer[1]);
				};
				if(decorLayer[2]) {
					decorLayer[2] = parseInt(decorLayer[2]);
				};
				if(decorLayer[3]) {
					decorLayer[3] = decorLayer[3].split(",");
				};
			};
			return decorLayers;
		};
		//console.log(stringToDecor("bird:0.025:10:#FF0000,#FFFF00,#00FF00"));

		function validateSingleDecorLayer(decorLayer) {
			if(decorLayer.length < 2) {
				return false;
			};
			if(!elements[decorLayer[0]]) {
				return false;
			};
			if(isNaN(decorLayer[1])) {
				return false;
			};
			if(typeof(decorLayer[2]) !== "undefined") {
				if(isNaN(decorLayer[2])) {
					return false;
				};
			};
			if(typeof(decorLayer[3]) !== "undefined") {
				if(!(validateHexColorArray(decorLayer[3]))) {
					return false;
				};
			};
			return true;
		};

		function validateDecorStructure(decorArraysArray) {
			if(!(decorArraysArray instanceof Array)) {
				return false;
			};
			if(decorArraysArray.length == 0) {
				return false;
			};
			var decorIsValid = 1;
			for(var i in decorArraysArray) {
				decorIsValid *= validateSingleDecorLayer(decorArraysArray[i]);
			};
			return !!decorIsValid;
		};

		function funniPrompt(argument=null,alertOutput=true,alertError=true) {
			argument === null ? inputText = prompt("Enter command") : inputText = argument;
			// replace spaces with underscores
			if(["",null].includes(inputText)) {
				console.log("Prompt canceled");
				return false;
			};
			//console.log(inputText);
			if(inputText.includes("|")) {
				var commands = inputText.split("|");
				var results = []; results.length = commands.length;
				for(var cmdi in commands) {
					commands[cmdi] = commands[cmdi].trim();
					try {
						results[cmdi] = funniPrompt(commands[cmdi]);
					} catch(error) {
						results[cmdi] = error;
					};
				};
				console.log(results);
				return results;
			};
			inputAsArray = inputText.split(" ");
			var firstItem = inputAsArray[0];

			switch(firstItem.toLowerCase()) {
				case "set":
					if(inputAsArray.length < 4) {
						alertIfError(alertError,"Usage: set [property] [element] [value] <type>\nDon't include framing characters []<>.\nThe element can be \"all\" to set the property for every pixel.\nNote: Strings can't have spaces because spaces are the separator used in the parsing split().\nArguments in [brackets] are required and ones in <angle brackets> are optional.");
						return false;
					};
					var property = inputAsArray[1];
					//console.log("Property gotten: " + property);
					var inputElement = inputAsArray[2];
					//console.log("Element gotten: " + inputElement);
					var value = inputAsArray[3];
					//console.log("Value gotten: " + value);
					var type = null; //dummy type for [value]-based assumption
					if(inputAsArray.length >= 5) {
						type = inputAsArray[4];
					};
					//console.log("Type gotten: " + type);

					if(type === null) {
						type = null; //catch null type
					} else if(numberSynonyms.includes(type.toLowerCase())) {
						type = "number";
					} else if(booleanSynonyms.includes(type.toLowerCase())) {
						type = "boolean";
					} else if(stringSynonyms.includes(type.toLowerCase())) {
						type = "string";
					} else if(arraySynonyms.includes(type.toLowerCase())) {
						type = "array";
					} else if(objectSynonyms.includes(type.toLowerCase())) {
						type = "object";
					};

					var typeWhitelist = [null,"string","number","boolean","array","object"];
					if(!typeWhitelist.includes(type)) {
						alertIfError(alertError,"Unrecognized type: \"" + type + "\".");
						return false;
					};

					if(type === null) {
						if(defaultStringTypeValues.includes(property)) {
							type = "string";
						} else if(defaultNumberTypeValues.includes(property)) {
							type = "number";
						} else if(defaultBooleanTypeValues.includes(property)) {
							type = "boolean";
						} else if(defaultArrayTypeValues.includes(property)) {
							type = "array";
						} else {
							alertIfError(alertError,"Type could not be assumed from property. Please specify the type as a fourth argument.");
							return false;
						}
					}

					if(type === "number") {
						value = parseFloat(value);
						if(isNaN(value)) {
							alertIfError(alertError,"Value is not a number!");
							return false;
						};
					} else if(type === "boolean") {
						if(synonymsOfTrue.includes(value.toLowerCase())) {
							value = true;
						} else if(synonymsOfFalse.includes(value.toLowerCase())) {
							value = false;
						} else {
							alertIfError(alertError,"Unrecognized boolean value: " + value + ".");
							return false;
						}
					} else if(type === "object") {
						try {
							value = JSON.parse(value);
						} catch (error) {
							alertIfError(alertError,"JSON is invalid! Note that it requires quotes around keys as well as those curly {} parentheses.");
							return false;
						};
					} else if(type === "array") {
						array = value.split(",");
						for(i = 0; i < array.length; i++) {
							if(array[i].startsWith("s")) { //String
								array[i] = array[i].substring(1);
							} else if(array[i].startsWith("n")) { //Number
								array[i] = array[i].substring(1);
								if(isNaN(parseFloat(array[i]))) {
									alert(array[i] + " is not a number!");
									return false;
								};
								array[i] = parseFloat(array[i]);
							} else if(array[i].startsWith("o")) { //Object
								array[i] = array[i].substring(1);
								try {
									array[i] = JSON.parse(array[i]);
								} catch (error) {
									alert(array[i] + " is not valid JSON!");
									return false;
								};
							} else if(array[i].startsWith("b")) { //Boolean
								array[i] = array[i].substring(1);
								if(synonymsOfTrue.includes(array[i].toLowerCase())) {
									array[i] = true;
								} else if(synonymsOfFalse.includes(array[i].toLowerCase())) {
									array[i] = false;
								} else {
									alert("Unrecognized boolean value: " + array[i] + ".");
									return false;
								};
							} else {
								alert(array[i] + ' must start with "s" for a string, "n" for a number, "o" for an object, or "b" for a boolean.');
								return false;
							};
						};
						value = array;
					}
					//The values start out as strings when split from the array, so string is kind of the default form.

					//Special validation

					if(property === "element") {
						var originalInput = value; //for error display
						value = mostSimilarElement(value);
						if(!elements[value]) {
							alertIfError(alertError,"Element " + originalInput + " does not exist!");
							return false;
						}
					};
					if(property === "x") {
						if(!Number.isSafeInteger(value)) {
							alertIfError(alertError,"X cannot be a decimal! And what are you doing trying to set position values anyway?");
							return false;
						}
					};
					if(property === "color") {
						if(!value.startsWith("rgb(")) { //if not RGB
							if(value.startsWith("hsl(")) { //if HSL
								if(!(value.split(",")[1].endsWith('%')) || !(value.split(",")[2].endsWith('%)'))) { //if missing percent symbols
									alertIfError(alertError,colorInvalidError);
									return false;
								};
							} else { //if not RGB and not HSL
								alertIfError(alertError,colorInvalidError);
								return false;
							};
						}
						if(value.split(",").length !== 3) { //if too short or long
							alertIfError(alertError,colorInvalidError);
							return false;
						}
						if(value.startsWith("rgb(")) { //if RGB
							var checkedColorObject = rgbStringToUnvalidatedObject(value); //RGB NaN checking
							if(isNaN(checkedColorObject.r) || isNaN(checkedColorObject.g) || isNaN(checkedColorObject.b)) {
								//console.log(checkedColorObject);
								alertIfError(alertError,"One or more color values are invalid!");
								return false;
							};
						} else if(value.startsWith("hsl(")) { //if HSL
							var checkedColorObject = hslStringToUnvalidatedObject(value); //HSL NaN checking
							if(isNaN(checkedColorObject.h) || isNaN(checkedColorObject.s) || isNaN(checkedColorObject.l)) {
								//console.log(checkedColorObject);
								alertIfError(alertError,"One or more color values are invalid!");
								return false;
							};
						} else { //if neither
							alertIfError(alertError,colorInvalidError);
							return false;
						};
					};

					//Actual setting code;
					var setCount = 0;
					for (var i = 1; i < width; i++) {
						for (var j = 1; j < height; j++) {
							if (!isEmpty(i,j)) {
								//console.log("Pixel (" + i + "," + j + ") exists")
								if(pixelMap[i][j].element === inputElement || inputElement === "all") {
									//console.log("Element is a match: " + inputElement + ", " + pixelMap[i][j].element)
									pixelMap[i][j][property] = value;
									if(property == "temp") { pixelTempCheck(pixelMap[i][j]) };
									setCount++;
								};
							};
						};
					};
					inputElement === "all" ? alertIfOutput(alertOutput,`Set ${property} of ${setCount} pixels to ${value}.`) : alertIfOutput(alertOutput,`Set ${property} of ${setCount} ${inputElement} pixels to ${value}.`)
					return true;
				case "test":
					alertIfOutput(alertOutput,"pong");
					console.log("qwertyuiopasdfghjklzxcvbnm");
					return true;
				case "setdimensions":
					if(inputAsArray.length < 3) {
						alertIfError(alertError,commandHelpObject.setdimensions);
						return false;
					};

					var argWidth = inputAsArray[1];

					var argHeight = inputAsArray[2];

					var argPixelSize = inputAsArray[3];

					if(argWidth == undefined) {
						alertIfError(alertError,commandHelpObject.setdimensions);
						return false;
					} else {
						argWidth = parseInt(argWidth);
						if(isNaN(argWidth)) {
							alert("Error: width was NaN");
							console.error("setdimensions: supplied width was NaN");
							return false;
						} else {
							if(argWidth < 1) {
								alert("Width must be greater than 0");
								console.error("setdimensions: supplied width was zero or negative");
								return false;
							};
						};
					};

					if(argHeight == undefined) {
						alertIfError(alertError,commandHelpObject.setdimensions);
						return false;
					} else {
						argHeight = parseInt(argHeight);
						if(isNaN(argHeight)) {
							alert("Error: height was NaN");
							console.error("setdimensions: supplied height was NaN");
							return false;
						} else {
							if(argHeight < 1) {
								alert("Height must be greater than 0");
								console.error("setdimensions: supplied height was zero or negative");
								return false;
							};
						};
					};

					if(argPixelSize == undefined) {
						argPixelSize = null;
					} else {
						argPixelSize = parseFloat(argPixelSize);
						if(isNaN(argPixelSize)) {
							argPixelSize = null;
							alert("pixelSize was NaN, ignoring");
							console.log("setdimensions: supplied pixelSize was NaN");
						} else {
							if(argPixelSize <= 0) {
								alert("Pixel size was non-positive, ignoring");
								console.error("setdimensions: supplied pixel size was zero or negative");
								argPixelSize = null;
							};
						};
					};

					width = argWidth + 1;

					height = argHeight + 1;

					if(typeof(argPixelSize) === "number" && argPixelSize !== null && !isNaN(argPixelSize)) {
						if(argPixelSize > 0) {
							pixelSize = argPixelSize;
						};
					};

					clearAll();

					return true;
				case "pixelsize":
					if(inputAsArray.length < 1) { //?
						alertIfError(alertError,commandHelpObject.setpixelsize);
						return false;
					};

					var argPixelSize = inputAsArray[1];

					if(argPixelSize == undefined) {
						argPixelSize = null;
					} else {
						argPixelSize = parseFloat(argPixelSize);
						if(isNaN(argPixelSize)) {
							alert("Error: size was NaN");
							console.error("setpixelsize: supplied pixel size was NaN");
							return false;
						};
					};

					if(typeof(argPixelSize) === "number" && argPixelSize !== null && !isNaN(argPixelSize)) {
						if(argPixelSize <= 0) {
							alert("Pixel size must be greater than 0");
							console.error("setpixelsize: supplied pixel size was zero or negative");
							return false;
						} else {
							pixelSize = argPixelSize;
						};
					} else {
						alert(pixelSize);
					};

					return pixelSize;
				case "dimensions":
					if(inputAsArray.length < 1) { //?
						alertIfError(alertError,commandHelpObject.dimensions);
						return false;
					};

					alert(`width: ${width}
height: ${height}
(Usable area is 1 pixel less in both dimensions)`);

					return [width,height];
				case "fill":
					if(inputAsArray.length < 3) {
						alertIfError(alertError,commandHelpObject.fill);
						return false;
					};

					var doOverwrite = inputAsArray[1];

					var elementList = inputAsArray.slice(2);
					//console.log(elementList);

					for(i = 0; i < elementList.length; i++) {
						var elementInConsideration = elementList[i]
						var originalElement = elementInConsideration; //also for error display
						elementInConsideration = mostSimilarElement(elementInConsideration);
						if(!elements[elementInConsideration]) {
							alertIfError(alertError,"Element " + originalElement + " does not exist!");
							return false;
						}
						elementList[i] = elementInConsideration;
					};
					//console.log(elementList);

					if(synonymsOfTrue.includes(doOverwrite.toLowerCase())) {
						doOverwrite = true;
					} else if(synonymsOfFalse.includes(doOverwrite.toLowerCase())) {
						doOverwrite = false;
					} else {
						alertIfError(alertError,"Unrecognized boolean value: " + doOverwrite + "\n Note that for this command, the boolean value goes first.");
						return false;
					}
					//console.log(doOverwrite);
					//console.log(elementList);

					//Fill code
					var fillCount = 0;
					for (var i = 1; i < width; i++) {
						for (var j = 1; j < height; j++) {
							var randomElement = elementList[Math.floor(Math.random() * elementList.length)];
							if(doOverwrite) {
								if(!isEmpty(i,j,true)) { deletePixel(i,j) };
							};
							if (isEmpty(i,j,false)) {
								createPixel(randomElement,i,j);
								fillCount++;
							};
						};
					};
					alertIfOutput(alertOutput,`Placed ${fillCount} pixels`);
					return fillCount;
				case "randomfill":
					if(inputAsArray.length < 1) { //somehow?
						alertIfError(alertError,"Usage: randomfill <overwrite (should be a bool) (default: true)>\nDon't include framing characters []<>.\nArguments in <angle brackets> are optional.");
						return false;
					};

					var doOverwrite = null;

					if(inputAsArray.length > 1) {
						var doOverwrite = inputAsArray[1];
						if(synonymsOfTrue.includes(doOverwrite.toLowerCase())) {
							doOverwrite = true;
						} else if(synonymsOfFalse.includes(doOverwrite.toLowerCase())) {
							doOverwrite = false;
						} else {
							alertIfError(alertError,"Unrecognized boolean value: " + value);
							return false;
						};
					} else {
						doOverwrite = true;
					};

					var elementList = randomChoices;

					//Fill code
					var fillCount = 0;
					for (var i = 1; i < width; i++) {
						for (var j = 1; j < height; j++) {
							var randomElement = elementList[Math.floor(Math.random() * elementList.length)];
							if(doOverwrite) {
								if(!isEmpty(i,j,true)) { deletePixel(i,j) };
							};
							if (isEmpty(i,j,false)) {
								createPixel(randomElement,i,j);
								fillCount++;
							};
						};
					};
					alertIfOutput(alertOutput,`Placed ${fillCount} random pixels`);
					return fillCount;
				case "count":
					if(inputAsArray.length < 2) {
						alertIfError(alertError,"Usage: count [element]\nDon't include framing characters []<>.\nNote: The element name can't have a space in it because spaces are the separator used in the parsing split().\nArguments in [brackets] are required.");
						return false;
					};
					var inputElement = inputAsArray[1];
					//console.log("Element gotten: " + inputElement);

					var originalInput = inputElement; //for error display
					inputElement = mostSimilarElement(inputElement);
					//console.log("Element gotten: " + inputElement);
					if(typeof(elements[inputElement]) === "undefined") {
						alertIfError(alertError,"Element " + originalInput + " does not exist!");
						return false;
					}

					//Actual counting code;
					var count = 0;
					for (var i = 1; i < width; i++) {
						for (var j = 1; j < height; j++) {
							if (!isEmpty(i,j)) {
								//console.log("Pixel (" + i + "," + j + ") exists")
								if(pixelMap[i][j].element === inputElement) {
									//console.log("Element is a match: " + inputElement + ", " + pixelMap[i][j].element)
									count++;
								};
							};
						};
					};
					alertIfOutput(alertOutput,`There are ${count} pixels of ${inputElement}`);
					return count;
				case "countall":
					var listObject = {};

					//Listing code;
					for (var i = 1; i < width; i++) {
						for (var j = 1; j < height; j++) {
							if (!isEmpty(i,j)) {
								var pixel = pixelMap[i][j];
								var element = pixel.element;
								if(!listObject[pixel.element]) {
									listObject[pixel.element] = 1;
								} else {
									listObject[pixel.element]++;
								}
							};
						};
					};

					var formattedList = "";
					var zelements = Object.keys(listObject);
					for(k = 0; k < zelements.length; k++) {
						var elementName = zelements[k];
						var elementCount = listObject[elementName];
						formattedList += `${elementName}: ${elementCount}\n`;
					};

					alertIfOutput(alertOutput,"Elements counts logged to console");
					console.log(formattedList);
					return listObject;
				case "wg":
				case "worldgen":
					if(inputAsArray.length < 1) {
						alertIfError(alertError,commandHelpObject.worldgen);
						return false;
					};

					var action = inputAsArray[1];
					if(!action) {
						alertIfOutput(`Current worldgen setting: ${settings.worldgen}`);
						return settings.worldgen;
					};

					var worldgenTypesList = Object.keys(worldgentypes).concat(["off"]);
					if(["list","lst","ls","l"].includes(action)) {
						alertIfOutput(alertOutput,worldgenTypesList.join(",  "));
						console.log(worldgenTypesList.map(x => "   " + x).join("\n"));
						return worldgenTypesList;
					} else {
						var targetPreset = inputAsArray[2];
						if(!targetPreset) {
							alertIfError(alertError,commandHelpObject.worldgen);
							return false;
						};
						if(!(worldgenTypesList.includes(targetPreset))) {
							alertIfError(alertError,`No such preset ${targetPreset}!`);
							return false;
						};
						if(["select","sel","pick","set","s"].includes(action)) {
							settings.worldgen = targetPreset;
							alertIfOutput(alertOutput,`Worldgen setting set to ${targetPreset}`);
							return true;
						} else if(["generate","gen","make","worldgen","wg","g","w","world","run","do","world"].includes(action)) {
							bareClear();
							if(targetPreset == "off") {
								alertIfOutput(alertOutput,`Cleared world`);
							} else {
								console.log(targetPreset,worldgentypes[targetPreset].complexity);
								worldGen(worldgentypes[targetPreset]);
								alertIfOutput(alertOutput,`Generated preset ${targetPreset}`);
							};
							return true;
						} else {
							alertIfError(alertError,commandHelpObject.worldgen);
							return false;
						};
					};
					return true;
				case "dwg":
				case "defineworldgen":
					if(inputAsArray.length < 3) {
						alertIfError(commandHelpObject.defineworldgen);
						alertIfError(commandHelpObject.defineworldgen2);
						return false;
					};
					var presetName = inputAsArray[1];
					//overwrite confirm below

					var newPreset = {};

					var layers = stringToLayers(inputAsArray[2]);
					if(!validateLayersStructure(layers)) {
						alertIfError(alertError,"Layers definition is invalid or malformed!");
						return false;
					};
					newPreset.layers = layers;

					var baseHeight = inputAsArray[3];
					if(typeof(baseHeight) !== "undefined") {
						if(promptInputNullishes.includes(baseHeight)) {
							baseHeight = "0.5";
						};
						baseHeight = parseFloat(baseHeight);
						if(isNaN(baseHeight)) {
							alertIfError(alertError,"Invalid baseHeight!");
							return false;
						};
						newPreset.baseHeight = baseHeight;
					};

					var heightVariance = inputAsArray[4];
					if(typeof(heightVariance) !== "undefined") {
						if(promptInputNullishes.includes(heightVariance)) {
							heightVariance = "0.5";
						};
						heightVariance = parseFloat(heightVariance);
						if(isNaN(heightVariance)) {
							alertIfError(alertError,"Invalid heightVariance!");
							return false;
						};
						newPreset.heightVariance = heightVariance;
					};

					var complexity = inputAsArray[5];
					if(typeof(complexity) !== "undefined") {
						if(promptInputNullishes.includes(complexity)) {
							complexity = "20";
						};
						complexity = parseFloat(complexity);
						if(isNaN(complexity)) {
							alertIfError(alertError,"Invalid complexity!");
							return false;
						};
						newPreset.complexity = complexity;
					};

					var temperature = inputAsArray[6];
					if(typeof(temperature) !== "undefined") {
						if(promptInputNullishes.includes(temperature.toLowerCase())) {
							temperature = null;
						} else {
							temperature = parseFloat(temperature);
							if(isNaN(temperature)) {
								alertIfError(alertError,"Invalid temperature!");
								return false;
							};
							newPreset.temperature = temperature;
						};
					};

					var decor = inputAsArray[7];
					if(typeof(decor) !== "undefined") {
						decor = stringToDecor(decor);
						if(!validateDecorStructure(decor)) {
							alertIfError(alertError,"Decor definition is invalid or malformed!");
							return false;
						};
						newPreset.decor = decor;
					};

					if(worldgentypes[presetName]) {
						var doOverwrite = confirm(`Overwrite worldgen preset ${presetName}?`);
						if(!doOverwrite) {
							alertIfError(alertError,"defineworldgen canceled");
							return false;
						};
					};
					worldgentypes[presetName] = newPreset;
					customWorldTypes[presetName] = newPreset;
					localStorage.setItem("customWorldTypes",JSON.stringify(customWorldTypes));
					settings.worldgen = presetName;
					rebuildWorldgenList();

					alertIfOutput(alertOutput, `Defined worldgen preset ${presetName}.
Make sure to save your command in a file if you want to add this preset again.`
	);
					console.log(inputText);
					return [presetName,newPreset];
				case "stars":
					var starDensity = inputAsArray[1];

					var seed = inputAsArray[2]; //〜カクセイ〜

					if(starDensity == undefined) {
						starDensity = 0.001
					} else {
						starDensity = parseFloat(starDensity);
						if(isNaN(starDensity)) {
							alert("starDensity was NaN, defaulting to 0.001");
							starDensity = 0.001;
						};
					};

					var stringSeed = false;
					var seedString = null;

					if(seed === undefined) {
						seed = Math.random();
						stringSeed = false;
					} else {
						if(isNaN(parseFloat(seed))) {
							stringSeed = true;
							seedString = seed;
							seed = cyrb128(seed)[2];
						} else {
							stringSeed = false;
							seed = parseFloat(seed);
						};
					};

					lastStarSeed = stringSeed ? seedString : seed;
					//console.log(stringSeed);
					//console.log(lastStarSeed);
					var randomFunction = mulberry32(seed);

					clearAll();
					for(j = 1; j < height; j++) {
						for(i = 1; i < width; i++) {
							if(randomFunction() < starDensity) {
								if(isEmpty(i,j,false)) {
									var value = randomFunction() ** 4;

									if(value < 0.3) {
										createPixelReturn("sun",i,j).temp = seededRandBetween(1800,3300,randomFunction);
									} else if(value < 0.55) {
										createPixelReturn("sun",i,j).temp = seededRandBetween(3300,5500,randomFunction);
									} else if(value < 0.70) {
										createPixelReturn("sun",i,j).temp = seededRandBetween(5500,8000,randomFunction);
									} else if(value < 0.8) {
										createPixelReturn("sun",i,j).temp = seededRandBetween(8000,13000,randomFunction);
									} else if(value < 0.85) {
										createPixelReturn("sun",i,j).temp = seededRandBetween(13000,35000,randomFunction);
									} else if(value < 0.88) {
										createPixelReturn("sun",i,j).temp = seededRandBetween(35000,90000,randomFunction);
									} else { //other stuff
										var value2 = randomFunction();
										if(value2 < 0.5) { //giant stars
											var value3 = randomFunction();
											if(value3 < 0.6) { //favor red giants
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(3,4,randomFunction));
												var randTemp = seededRandBetween(1800,3300,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											} else if(value3 < 0.9) { //blue giants are rarer
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(2,3,randomFunction));
												var randTemp = seededRandBetween(20000,80000,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											} else { //yellows are even rarer
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(2,3,randomFunction));
												var randTemp = seededRandBetween(6000,11000,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											};
										} else if(value2 < 0.6) { //supergiants
											var value3 = randomFunction();
											if(value3 < 0.6) {
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(6,8,randomFunction));
												var randTemp = seededRandBetween(1700,3200,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											} else if(value3 < 0.9) {
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(5,7,randomFunction));
												var randTemp = seededRandBetween(19000,83000,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											} else {
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(5,6,randomFunction));
												var randTemp = seededRandBetween(5500,10500,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											};
										} else if(value2 < 0.65) { //hypergiants
											var value3 = randomFunction();
											if(value3 < 0.6) {
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(9,12,randomFunction));
												var randTemp = seededRandBetween(1600,3100,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											} else if(value3 < 0.94) {
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(8,11,randomFunction));
												var randTemp = seededRandBetween(18000,84000,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											} else {
												var sunPixels = fillCircleReturn("sun",i,j,seededRandBetween(8,11,randomFunction));
												var randTemp = seededRandBetween(5000,10000,randomFunction);
												for(pixel in sunPixels) {
													sunPixels[pixel].temp = randTemp;
												};
											};
										} else if(value2 < 0.8) { //white dwarfs/neutron stars
											if(randomFunction() < 0.8) { //favor white dwarfs
												createPixelReturn("sun",i,j).temp = seededRandBetween(100000,300000,randomFunction);
											} else {
												if(!elements.neutron_star) {
													createPixelReturn("sun",i,j).temp = seededRandBetween(100000,300000,randomFunction);
												} else {
													createPixelReturn("neutron_star",i,j).temp = seededRandBetween(2000000,10000000,randomFunction);
												};
											};
										} else { //brown dwarfs
											createPixelReturn("sun",i,j).temp = seededRandBetween(100,800,randomFunction);
										};
									};
								};
							};
						};
					};
					return true;
					break;
				case "kakusei":
				case "starseed":
					alertIfOutput(alertOutput,lastStarSeed);
					console.log(lastStarSeed);
					return lastStarSeed;
				case "help":
					var commandsWithoutDwg2 = Object.keys(commandHelpObject).filter(function(cmdName) { return cmdName !== "defineworldgen2" });
					if(inputAsArray.length < 1) { //somehow
						alertIfError(alertError,"Usage: help <command>\nDon't include framing characters []<>.\nArguments in <angle brackets> are optional.");
						return false;
					};
					if(inputAsArray.length < 2) {
						alertOutput ? alertIfOutput(alertOutput,"Commands: " + commandsWithoutDwg2.join("\n")) : console.log("Commands: " + commandsWithoutDwg2.join(", "));
					} else {
						var command = inputAsArray[1];

						if(typeof(commandHelpObject[command]) === "undefined" || command == "defineworldgen2") {
							alertIfError(alertError,"Cound not find help for " + command + ".");
							return false;
						} else {
							if(command == "defineworldgen") {
								if(alertOutput) {
									alert(commandHelpObject.defineworldgen);
									alert(commandHelpObject.defineworldgen2);
								} else {
									console.log(commandHelpObject.defineworldgen + "\n" + commandHelpObject.defineworldgen2);
								};
							} else {
								if(alertOutput) {
									alert(commandHelpObject[command]);
								} else {
									console.log(commandHelpObject[command]);
								};
							};
							return true;
						};
					};
					return true;
				default:
					alertIfError(alertError,`Command ${firstItem} not found!`);
					return false;
			};
		};

		document.addEventListener("keydown", function(e) { //prop prompt listener
			// , = propPrompt()
			if ([1,3].includes(shiftDown) && e.keyCode == 49) { //either shift + 1
				funniPrompt();
			};
		});

		elements.funni_prompt = {
			color: ["#000000","#00ff00","#000000","#00ff00","#000000","#00ff00","#000000","#00ff00","#000000","#00ff00"],
			behavior: behaviors.SELFDELETE,
			desc: "<span style='color:#FF00FF;' onClick=funniPrompt()>Click here or press Shift+1 to open the command prompt.</span>",
			category:"special",
		};

	//REPLACER TOOL ##

		changeTo = "sand";

		document.addEventListener("keydown", function(e) { //change prompt listener
			// r = changeElementPrompt()
			if (e.keyCode == 186) {
				e.preventDefault();
				changeElementPrompt();
			}
		});

		function changeElementPrompt() {
			var cmToElement = prompt("Enter what you want to change pixels to");
			if(cmToElement == null) { return };
			// replace spaces with underscores
			cmToElement = cmToElement.replace(/ /g, "_");
			cmToElementS = mostSimilarElement(cmToElement);
			if (cmToElementS === null || cmToElementS === undefined || cmToElementS === "") {
				alert("Element \"" + cmToElement + "\" not found! Defaulting to sand.");
				cmToElementS = "sand";
			};
			changeTo = cmToElementS;
			updateChangeDescriptions();	
		}

		function updateChangeDescriptions() {
			elements.change.desc = "Changes any pixels it is used on to a specified type.<br/>Currently replacing pixels with \"" + changeTo + "\".<br/><span onclick=changeElementPrompt() style=\"color: #ff00ff;\";>Press [;] or click here</span> to open the change prompt.";
			elements.alt_change.desc = "Changes any pixels it is used on to a specified type, but keeping their non-element-based properties.<br/>Currently replacing pixels with \"" + changeTo + "\".<br/><span onclick=changeElementPrompt() style=\"color: #ff00ff;\";>Press [;] or click here</span> to open the change prompt.";
			elements.alt_alt_change.desc = "Changes any pixels it is used on to a specified type, but keeping their non-element-based properties except for color.<br/>Currently replacing pixels with \"" + changeTo + "\".<br/><span onclick=changeElementPrompt() style=\"color: #ff00ff;\";>Press [;] or click here</span> to open the change prompt.";
		};

		elements.change = {
			color: ["#ff0000", "#ff0000", "#ff0000", "#7f00ff", "#0000ff", "#0000ff", "#0000ff"],
			tool: function(pixel) {
				changePixel(pixel,changeTo,true);
			},
			category: "tools",
			desc: "Changes any pixels it is used on to a specified type.<br/>Currently replacing pixels with \"" + changeTo + "\".<br/><span onclick=changeElementPrompt() style=\"color: #ff00ff;\";>Press [;] or click here</span> to open the change prompt.",
		};

		elements.alt_change = {
			color: ["#ffff00", "#ffff00", "#ffff00", "#cf7f4f", "#ff00ff", "#ff00ff", "#ff00ff"],
			tool: function(pixel) {
				pixel.element = changeTo;
			},
			category: "tools",
			desc: "Changes any pixels it is used on to a specified type, but keeping their non-element-based properties.<br/>Currently replacing pixels with \"" + changeTo + "\".<br/><span onclick=changeElementPrompt() style=\"color: #ff00ff;\";>Press [;] or click here</span> to open the change prompt.",
			hidden: true,
		};

		elements.alt_alt_change = {
			color: ["#00ff00", "#00ff00", "#00ff00", "#cfcf00", "#ff0000", "#ff0000", "#ff0000"],
			tool: function(pixel) {
				pixel.element = changeTo;
				pixel.color = pixelColorPick(pixel);
			},
			category: "tools",
			desc: "Changes any pixels it is used on to a specified type, but keeping their non-element-based properties except for color.<br/>Currently replacing pixels with \"" + changeTo + "\".<br/><span onclick=changeElementPrompt() style=\"color: #ff00ff;\";>Press [;] or click here</span> to open the change prompt.",
			hidden: true,
		};

	//ADDITIONAL RAYS ##

		runAfterAutogen(function() {
			snowAndIceCache = Object.keys(elements).filter(function(name) {
				return name.endsWith("snow") || name.endsWith("ice") || name == "rime"
			})
		});
		lightlikes = ["light","flash","laser","radiation","insulate_flash"];
		firelikes = ["fire","plasma","smoke","stellar_plasma","liquid_plasma","liquid_stellar_plasma"];
		grbBreakIntos = Object.keys(elements).filter(function(elemName) {
			var to = typeof(elements[elemName]);
			if(to == "undefined") {
				return false
			} else {
				var to2 = typeof(elements[elemName].breakInto);
				if(to2 == "undefined") {
					return false
				} else {
					if(elements[elemName].breakInto instanceof Array) {
						return elements[elemName].breakInto.includes("gamma_ray_burst")
					} else {
						return elements[elemName].breakInto == "gamma_ray_burst"
					}
				}
			}
		});

		elements.insulate_flash = {
			hidden: true,
			color: "#fffdcf",
			tick: function(pixel) {
				if (Math.random() < 0.75 && pixelTicks - pixel.start > 1) {
					deletePixel(pixel.x, pixel.y)
				}
			},
			reactions: {
				"blood": { elem1:"pointer" },
				"molten_stained_glass": { elem1:"rainbow" },
				"gray_goo": { elem1:"static" }
			},
			category: "energy",
			temp: 40,
			state: "gas",
			density: 1,
			tempLow: -270,
			stateLow: "light",
			hidden: true,
			noMix: true,
			insulate: true
		};

		elements.heat_ray.tick = function(pixel) {
			var x = pixel.x;
			for (var y = pixel.y; y < height; y++) {
				if (outOfBounds(x, y)) {
					break;
				}
				if (isEmpty(x, y)) {
					if (Math.random() > 0.025) { continue }
					createPixel("insulate_flash", x, y);
					pixelMap[x][y].color = "#ff0000";
				}
				else {
					if (elements[pixelMap[x][y].element].isGas) { pixelMap[x][y].temp -= 50; continue; }
					if (elements[pixelMap[x][y].element].id === elements.heat_ray.id) { break }
					pixelMap[x][y].temp += 100;
					break;
				}
			}
			deletePixel(pixel.x, pixel.y);
		};	

		elements.sun.isSun = true;
		if(elements.nellsun) { elements.nellsun.isSun = true };
		if(elements.rainbow_sun) { elements.rainbow_sun.isSun = true };

		elements.cold_ray = {
			color: ["#00ffae","#00ffff"],
			tick: function(pixel) {
				var x = pixel.x;
				for (var y = pixel.y; y < height; y++) {
					if (outOfBounds(x, y)) {
						break;
					}
					if (isEmpty(x, y)) {
						if (Math.random() > 0.05) { continue }
						createPixel("insulate_flash", x, y);
						pixelMap[x][y].color = "#00ffff";
					}
					else {
						if (elements[pixelMap[x][y].element].isGas) {
							if(elements[pixelMap[x][y].element].isSun) {
								pixelMap[x][y].temp -= 0.5;
							} else {
								pixelMap[x][y].temp -= 50;
							};
							continue; 
						};
						if (elements[pixelMap[x][y].element].id === elements.cold_ray.id) { break }
						pixelMap[x][y].temp -= 150;
						break;
					}
				}
				deletePixel(pixel.x, pixel.y);
			},
			temp: -200,
			category: "energy",
			state: "gas",
			excludeRandom: true,
			noMix: true
		};

		elements.freeze_ray = {
			color: ["#7fbfff","#bfffff"],
			tick: function(pixel) {
				var x = pixel.x;
				for (var y = pixel.y; y < height; y++) {
					if (outOfBounds(x, y)) {
						break;
					}
					if (isEmpty(x, y)) {
						if (Math.random() > 0.02) { continue }
						createPixel("insulate_flash", x, y);
						pixelMap[x][y].color = "#e1f8fc";
					}
					else {
						var otherPixel = pixelMap[x][y];
						var otherInfo = elements[otherPixel.element];
						//Gas: Freeze chance, cool, always penetrate
						if (otherInfo.isGas) {
							if(Math.random() < 0.05 && otherInfo.stateLow) {
								if(otherInfo.stateLow.includes("supernova") || otherInfo.stateLow.includes("gamma_ray_burst")) {
									//do nothing
								} else {
									freezePixel(otherPixel)
								};
							};
							if(elements[otherPixel.element].isSun) {
								otherPixel.temp -= 0.5;
							} else {
								otherPixel.temp -= 50;
							};
							continue;
						};

						//Self: Break
						if (otherInfo.id === elements.freeze_ray.id) { break }

						//Non-gas, Freeze chance, cool more, half penetrate
						if(Math.random() < 0.05 && otherInfo.stateLow) {
							if(otherInfo.stateLow.includes("supernova") || otherInfo.stateLow.includes("gamma_ray_burst")) {
								//do nothing
							} else {
								freezePixel(otherPixel)
							};
						};
						pixelMap[x][y].temp -= 150;

						if(Math.random() < 0.05) {

							if(!isEmpty(x,y-1,false)) {
								if(pixelMap[x]?.[y-1]?.element && lightlikes.includes(pixelMap[x][y-1].element)) {
									deletePixel(x,y-1);
								};
							};
							var newSnow = tryCreatePixelReturn("snow",x,y-1);
							if(newSnow) { newSnow.temp = -100 };
						};

						//Penetrate snow and ice
						if(snowAndIceCache && snowAndIceCache.includes(otherPixel.element)) {
							continue;
						};

						if(Math.random() < 0.7) { //thanks, I hate random continue
							continue;
						};
						break;
					}
				}
				deletePixel(pixel.x, pixel.y);
			},
			temp: -200,
			category: "energy",
			state: "gas",
			excludeRandom: true,
			noMix: true
		};

		elements.melt_ray = {
			color: ["#ffbf7f","#ffffbf"],
			tick: function(pixel) {
				var x = pixel.x;
				for (var y = pixel.y; y < height; y++) {
					if (outOfBounds(x, y)) {
						break;
					}
					if (isEmpty(x, y)) {
						if (Math.random() > 0.02) { continue }
						createPixel("insulate_flash", x, y);
						pixelMap[x][y].color = "#e1f8fc";
					}
					else {
						var otherPixel = pixelMap[x][y];
						var otherInfo = elements[otherPixel.element];
						//Gas: Heat, always penetrate
						if (otherInfo.isGas) {
							if(Math.random() < 0.05 && otherInfo.stateLow) {
								meltPixel(otherPixel)
							};
							if(elements[otherPixel.element].isSun) {
								otherPixel.temp += 0.5;
							} else {
								otherPixel.temp += 50;
							};
							continue;
						};

						//Self: Break
						if (otherInfo.id === elements.melt_ray.id) { break }

						//Non-gas, Melt chance, heat more, half penetrate
						if(Math.random() < 0.05 && !otherInfo.isGas) {
							meltPixel(otherPixel)
						};
						pixelMap[x][y].temp += 200;

						if(Math.random() < 0.05) {

							if(!isEmpty(x,y-1,false)) {
								if(pixelMap[x]?.[y-1]?.element && lightlikes.includes(pixelMap[x][y-1].element)) {
									deletePixel(x,y-1);
								};
							};
							var newPlasma = tryCreatePixelReturn("liquid_plasma",x,y-1);
							if(newPlasma) { newPlasma.temp += 500 };
						};

						//Penetrate snow and ice
						if(firelikes && firelikes.includes(otherPixel.element)) {
							continue;
						};

						if(Math.random() < 0.7) { //thanks, I hate random continue
							continue;
						};
						break;
					}
				}
				deletePixel(pixel.x, pixel.y);
			},
			temp: 4000,
			category: "energy",
			state: "gas",
			excludeRandom: true,
			noMix: true
		};

		elements.smash_ray = {
			color: ["#ff9999", "#8c8279"],
			tick: function(pixel) {
				var x = pixel.x;
				for (var y = pixel.y; y < height; y++) {
					if (outOfBounds(x, y)) {
						break;
					}
					if (isEmpty(x, y)) {
						if (Math.random() > 0.05) { continue }
						createPixel("flash", x, y);
						pixelMap[x][y].color = "#edd0c5";
					}
					else {
						var otherPixel = pixelMap[x][y]
						var otherInfo = elements[otherPixel.element];
						if (!(grbBreakIntos.includes(otherPixel.element))) {
							if (otherInfo.isGas) {
								if(Math.random() > ((otherInfo.hardness ?? 0) ** 2)) { breakPixel(otherPixel,false,false) };
								if(otherPixel && !(lightlikes.includes(otherPixel.element))) {
									var vels = [randomIntegerBetweenTwoValues(-7,7),randomIntegerBetweenTwoValues(-7,7)];
									otherPixel.vx = vels[0];
									otherPixel.vy = vels[1];
								};
								continue;

							};
							if (otherInfo.id === elements.heat_ray.id) { break }
							if(Math.random() > ((otherInfo.hardness ?? 0) ** 2)) { breakPixel(otherPixel,false,false) };
							if(otherPixel) {
								var vels = [randomIntegerBetweenTwoValues(-7,7),randomIntegerBetweenTwoValues(-5,0)];
								otherPixel.vx = vels[0];
								otherPixel.vy = vels[1];
							};
							if(Math.random() < Math.max(0.8,0.3 + ((1 - (otherInfo.hardness ?? 0)) / 2))) { //thanks, I hate random continue
								continue;
							};
							break;
						}
					}
				}
				deletePixel(pixel.x, pixel.y);
			},
			temp: 20,
			category: "energy",
			state: "gas",
			excludeRandom: true,
			noMix: true
		};

		//combines heat ray and smash ray
		elements.death_ray = {
			color: ["#a88d77", "#ff4a36"],
			tick: function(pixel) {
				var x = pixel.x;
				for (var y = pixel.y; y < height; y++) {
					if (outOfBounds(x, y)) {
						break;
					}
					if (isEmpty(x, y)) {
						if (Math.random() > 0.05) { continue }
						createPixel("insulate_flash", x, y);
						pixelMap[x][y].color = "#eb7b59";
					}
					else {
						var otherPixel = pixelMap[x][y]
						var otherInfo = elements[otherPixel.element];
						otherPixel.temp += (400 * (shiftDown + 1));
						if(otherPixel.del) { continue };
						if (!(grbBreakIntos.includes(otherPixel.element))) {
							if (otherInfo.isGas) {
								if(Math.random() > ((otherInfo.hardness ?? 0) ** (4 + shiftDown))) { breakPixel(otherPixel,false,false) };
								if(otherPixel && !(lightlikes.includes(otherPixel.element))) {
									var vels = [randomIntegerBetweenTwoValues(-7 - (shiftDown * 2),7 + (shiftDown * 2)),randomIntegerBetweenTwoValues(-7 - (shiftDown * 2),7 + (shiftDown * 2))];
									otherPixel.vx = vels[0];
									otherPixel.vy = vels[1];
								};
								continue;

							};
							if (otherInfo.id === elements[pixel.element].id) { break }
							if(Math.random() > ((otherInfo.hardness ?? 0) ** (2 + shiftDown))) { breakPixel(otherPixel,false,false) };
							if(otherPixel) {
								var vels = [randomIntegerBetweenTwoValues(-9 - (shiftDown * 2),9 + (shiftDown * 2)),randomIntegerBetweenTwoValues(-7 - (shiftDown * 2),0 + (shiftDown * 2))];
								otherPixel.vx = vels[0];
								otherPixel.vy = vels[1];
							};
							if(Math.random() < ((shiftDown / 20) + (Math.max(0.9,0.4 + ((1 - (otherInfo.hardness ?? 0)) / 2))))) { //thanks, I hate random continue
								continue;
							};
							break;
						}
					}
				}
				deletePixel(pixel.x, pixel.y);
			},
			temp: 4000,
			category: "energy",
			state: "gas",
			excludeRandom: true,
			noMix: true
		};

		elements.annihilation_ray = {
			color: ["#220c0c", "#c11515"],
			tick: function(pixel) {
				var x = pixel.x;
				for (var y = pixel.y; y < height; y++) {
					if (outOfBounds(x, y)) {
						break;
					}
					if (isEmpty(x, y)) {
						if (Math.random() > 0.05) { continue }
						createPixel("insulate_flash", x, y);
						pixelMap[x][y].color = "#292929";
					}
					else {
						var otherPixel = pixelMap[x][y];
						var otherInfo = elements[otherPixel?.element];

						if(otherPixel) {
							otherPixel.temp += 2500 * (shiftDown + 1);
							if(otherPixel.del || !otherPixel) { continue };

							if (otherPixel && grbBreakIntos.includes(otherPixel.element)) {
								if(Math.random() < 0.01 && otherPixel) {
									deletePixel(otherPixel.x,otherPixel.y);
								};
								continue;
							} else if(otherPixel) {
								breakPixel(otherPixel,false,false);
								if(otherPixel.del || !otherPixel) {
									continue
								};

								if(otherPixel ) {
									var vels = [randomIntegerBetweenTwoValues(-8,8),randomIntegerBetweenTwoValues(-6,0)];
									otherPixel.vx = vels[0];
									otherPixel.vy = vels[1];
								};

								if(otherPixel && Math.random() < (otherInfo.isGas ? 0.2 : 0.1)) {
									deletePixel(otherPixel.x,otherPixel.y);
									continue;
								};

								if(Math.random() > 0.8) {
									continue;
								};
							};
							if (otherInfo.id === elements[pixel.element].id) { break }
						};
					}
				}
				deletePixel(pixel.x, pixel.y);
			},
			temp: 150000000,
			category: "energy",
			state: "gas",
			excludeRandom: true,
			noMix: true
		};

		//bless falls within god ray
		elements.bless.reactions.dry_dirt = { elem2: "dirt" };
		elements.bless.reactions.dead_cum = { elem2: "cum" };
		elements.bless.reactions.dead_cum_water = { elem2: "cum_water" };
		elements.bless.reactions.dead_cum_ice = { elem2: "cum_ice" };
		elements.bless.reactions.dead_cum_water_ice = { elem2: "cum_water_ice" };
		elements.bless.reactions.dead_cum_snow = { elem2: "cum_snow" };
		elements.bless.reactions.dead_cummy_mud = { elem2: "cummy_mud" };
		elements.bless.reactions.dead_cummy_sand = { elem2: "cummy_sand" };
		elements.bless.reactions.dead_cummy_permafrost = { elem2: "cummy_permafrost" };
		elements.bless.reactions.burnt_cum = { elem2: null };
		elements.bless.reactions.poop = { elem2: null };
		elements.bless.reactions.dried_poop = { elem2: null };
		elements.bless.reactions.shit = { elem2: null };
		elements.bless.reactions.dried_shit = { elem2: null };
		elements.bless.reactions.frozen_shit = { elem2: null };
		elements.bless.reactions.diarrhea = { elem2: null };
		elements.bless.reactions.frozen_diarrhea = { elem2: null };
		elements.bless.reactions.piss = { elem2: null };
		elements.bless.reactions.vomit = { elem2: null };
		elements.bless.reactions.crimson_grass = { elem2: "grass" };
		elements.bless.reactions.crimstone = { elem2: "rock" };
		elements.bless.reactions.crimsand = { elem2: "sand" };
		elements.bless.reactions.red_ice = { elem2: "ice" };
		elements.bless.reactions.crimgravel = { elem2: "gravel" };
		elements.bless.reactions.crimwater = { elem2: "water" };
		elements.bless.reactions.crimsnow = { elem2: "snow" };
		elements.bless.reactions.vicious_mushroom = { elem2: null };
		elements.bless.reactions.crimson_vine = { elem2: "vine" };
		elements.bless.reactions.shadewood = { elem2: "wood" };
		elements.bless.reactions.shadewood_tree_branch = { elem2: "tree_branch" };
		elements.bless.reactions.shadewood_sapling = { elem2: "sapling" };
		elements.bless.reactions.shadewood_sawdust = { elem2: "sawdust" };
		elements.bless.reactions.crimson_leaf = { elem2: "leaf" };
		elements.bless.reactions.ichor = { elem2: null }; //per blood, absent the gods' immune systems (apparenly they don't need immune systems because of immortality anyway)
		elements.bless.reactions.virus_bomb = { elem2: null };
		elements.bless.reactions.life_eater_slurry = { elem2: null };
		elements.bless.reactions.life_eater_explosion = { elem2: null };
		elements.bless.reactions.life_eater_virus = { elem2: null };
		elements.bless.reactions.injector_poison = { elem2: null };
		elements.bless.reactions.dead_matter = { elem2: null };
		elements.bless.reactions.life_eater_infected_dirt = { elem2: "dirt" };
		elements.bless.reactions.poisoned_dirt = { elem2: "dirt" };
		elements.bless.reactions.vicious_goldfish = { elem2: "fish" };
		elements.bless.reactions.nellfire = { elem2: "bless" };
		elements.bless.reactions.gloomwind = { elem2: null };
		elements.bless.reactions.gloomfly = { elem2: null };
		elements.bless.reactions.meat_monster = { elem2: null };
		elements.bless.reactions.rotten_ravager = { elem2: null };
		elements.bless.reactions.bone_beast = { elem2: null };
		elements.bless.reactions.poisonwater = { elem2: "water" };
		elements.bless.reactions.poisoned_ice = { elem2: "ice" };
		elements.bless.reactions.poisoned_gas = { elem2: "steam" };
		elements.bless.reactions.corrupt_land = { elem2: "dirt" };
		elements.bless.reactions.corrupt_rock = { elem2: "rock" };
		elements.bless.reactions.withery = { elem2: null };
		elements.bless.reactions.withery_plant = { elem2: null };
		elements.bless.reactions.corrupt_solid_rock = { elem2: "rock_wall" };
		elements.bless.reactions.toxin = { elem2: "antidote" };
		elements.bless.reactions.dead = { elem2: null };
		elements.bless.reactions.brain = { elem2: null };
		elements.bless.reactions.bioooze = { elem2: null };
		elements.bless.tool = function(pixel) {
			if (elements.bless.ignore.indexOf(pixel.element) !== -1) { return; }
			if (pixel.burning) { // stop burning
				delete pixel.burning;
				delete pixel.burnStart;
			}
			if (pixel.nellburn) { // change: stop nellburn
				delete pixel.nellburn;
				delete pixel.nellburnStart;
			}
			if (pixel.temp > 100) {
				pixel.temp = (pixel.temp+100)/2;
				pixelTempCheck(pixel);
				if (pixel.del) {return}
			}
			if (pixel.origColor) {
				pixel.color = "rgb("+pixel.origColor.join(",")+")";
				delete pixel.origColor;
			}
			if (pixel.charge) {
				delete pixel.charge;
				pixel.chargeCD = 16;
			}
			if (elements.bless.reactions[pixel.element] && Math.random()<0.25) {
				var r = elements.bless.reactions[pixel.element];
				var elem2 = r.elem2;
				if (elem2 !== undefined) {
					if (Array.isArray(elem2)) { elem2 = elem2[Math.floor(Math.random()*elem2.length)]; }
					if (elem2 === null) { deletePixel(pixel.x,pixel.y) }
					else { changePixel(pixel, elem2); }
				}
			}
		};

		rayAbsorbElements = [];
		rayPassElements = [];

		function summonRay(element,xIn,intensity,radius) {
			var forMin = 0 - radius;
			var forMax = radius + 1;
			if(intensity < 1) { return };
			for(var i = forMin; i < forMax; i++) {
				for(var j = 1; j < intensity + 1; j++) {
					var pos = {x: xIn + i, y: j};
					if(isEmpty(pos.x,pos.y)) {
						createPixel(element,pos.x,pos.y)
					} else {
						if(outOfBounds(pos.x,pos.y)) {
							break
						} else {
							var pixel = pixelMap[pos.x][pos.y];
							var pElem = pixel.element;
							var data = elements[pElem];
							if(rayAbsorbElements.includes(pElem)) {
								break
							} else if(rayPassElements.includes(pElem)) {
								continue
							} else {
								if(data.state == "gas") {
									continue
								} else {
									break
								}
							}
						}
					}
				}
			}
		};

		elements.orbital_ray_beacon = {
			color: "#ebdf91",
			behavior: [
				"XX|M2 AND SA|XX",
				"SA|XX|SA",
				"XX|M1|XX"
			],
			breakInto: ["steel_scrap","iron_scrap","copper_scrap","gold_scrap","battery","sapphire","magic"],
			temp: 0,
			tempHigh: 5010,
			insulate: true,
			conduct: 1,
			stateHigh: ["molten_steel","molten_iron","molten_copper","molten_gold","acid_gas","titanium_gas","molten_sapphire","magic"],
			tick: function(pixel) {
				pixelTempCheck(pixel);
				if(!pixel.charge) { return };
				if(pixel.charge) {
					//var intensity = Math.max(1,Math.floor((pixel.temp + 1) / 100));
					if(isEmpty(pixel.x,pixel.y+1,true)) { return };
					var pixelUnder = pixelMap[pixel.x]?.[pixel.y+1];
					if(!pixelUnder) { return };
					switch(pixelUnder.element) {
						case "fire":
							summonRay("heat_ray",pixel.x,10,5);
							break;
						case "ultramafic_magma":
						case "magma":
						case "intermediate_magma":
						case "intermediate_felsic_magma":
						case "felsic_magma":
						case "nellish_magma":
						case "rainbow_magma":
						case "crimson_magma":
							summonRay("heat_ray",pixel.x,16,10);
							break;
						case "vaporized_ultramafic_magma":
						case "vaporized_magma":
						case "vaporized_intermediate_magma":
						case "vaporized_intermediate_felsic_magma":
						case "vaporized_felsic_magma":
						case "vaporized_nellish_magma":
						case "vaporized_rainbow_magma":
						case "vaporized_crimson_magma":
							summonRay("heat_ray",pixel.x,24,18);
							break;
						case "greek_fire":
							summonRay("heat_ray",pixel.x,16,25);
							break;
						case "quark_matter":
							summonRay("heat_ray",pixel.x,45,80);
							break;
						case "cold_fire":
							summonRay("cold_ray",pixel.x,10,5);
							break;
						case "chilly_water":
							summonRay("cold_ray",pixel.x,14,8);
							break;
						case "frostwind":
							summonRay("cold_ray",pixel.x,20,15);
							break;
						case "liquid_frostwind":
							summonRay("cold_ray",pixel.x,30,20);
							break;
						case "gelid_cryotheum":
							summonRay("cold_ray",pixel.x,36,25);
							break;
						case "snow":
							summonRay("freeze_ray",pixel.x,3,6);
							break;
						case "slush":
							summonRay("freeze_ray",pixel.x,4,6);
							break;
						case "packed_snow":
							summonRay("freeze_ray",pixel.x,4,7);
							break;
						case "ice":
							summonRay("freeze_ray",pixel.x,4,8);
							break;
						case "alcohol_ice":
							summonRay("freeze_ray",pixel.x,6,11);
							break;
						case "liquid_nitrogen":
							summonRay("freeze_ray",pixel.x,9,18);
							break;
						case "nitrogen_ice":
							summonRay("freeze_ray",pixel.x,11,19);
							break;
						case "liquid_hydrogen":
							summonRay("freeze_ray",pixel.x,14,26);
							break;
						case "hydrogen_ice":
							summonRay("freeze_ray",pixel.x,15,27);
							break;
						case "liquid_helium":
							summonRay("freeze_ray",pixel.x,18,34);
							break;
						case "tectonic_petrotheum":
							summonRay("smash_ray",pixel.x,2,10);
							break;
						case "bomb":
						case "tnt":
							summonRay("smash_ray",pixel.x,5,7);
							break;
						case "cluster_bomb":
							summonRay("death_ray",pixel.x,7,11);
							break;
						case "nuke":
							summonRay("annihilation_ray",pixel.x,20,40);
							break;
						case "cluster_nuke":
							summonRay("annihilation_ray",pixel.x,30,60);
							break;
						case "armageddon":
							summonRay("annihilation_ray",pixel.x,40,80);
							break;
					};
					//if(pixelUnder) { deletePixel(pixelUnder.x,pixelUnder.y) };
					delete pixel.charge;
					pixel.chargeCD = 4;
					return true;
				}
			},
			conduct: 1,
			category: "machines",
			hardness: 0.6
		};

	//PUSHERS ##

		elements.up_pusher = {
			color: "#9fafdf",
			properties: {
				range: 10,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushStrength ??= 1;

				for(h = 0; h < pixel.pushStrength; h++) {
					for(i=(pixel.range - 1); i>=0; i--) {
						if (!isEmpty(pixel.x,pixel.y-1-i,true)) {
							tryMove(pixelMap[pixel.x][pixel.y-1-i],pixel.x,pixel.y-2-i);
						};
					};
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.down_pusher = {
			color: "#9fafdf",
			properties: {
				range: 10,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushStrength ??= 1;

					for(h = 0; h < pixel.pushStrength; h++) {
						for(i=(pixel.range - 1); i>=0; i--) {
							if (!isEmpty(pixel.x,pixel.y+1+i,true)) {
								tryMove(pixelMap[pixel.x][pixel.y+1+i],pixel.x,pixel.y+2+i);
							};
						};
					};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum0", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.left_pusher = {
			color: "#9fafdf",
			properties: {
				range: 10,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushStrength ??= 1;

				for(h = 0; h < pixel.pushStrength; h++) {
					for(i=(pixel.range - 1); i>=0; i--) {
						if (!isEmpty(pixel.x-1-i,pixel.y,true)) {
							tryMove(pixelMap[pixel.x-1-i][pixel.y],pixel.x-2-i,pixel.y);
						};
					};
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.right_pusher = {
			color: "#9fafdf",
			properties: {
				range: 10,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushStrength ??= 1;

				for(h = 0; h < pixel.pushStrength; h++) {
					for(i=(pixel.range - 1); i>=0; i--) {
						if (!isEmpty(pixel.x+1+i,pixel.y,true)) {
							tryMove(pixelMap[pixel.x+1+i][pixel.y],pixel.x+2+i,pixel.y);
						};
					};
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.up_e_pusher = {
			color: "#9f9f6f",
			properties: {
				range: 10,
				pushTime: 0,
				pushLength: 5,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushTime ??= 0;
				pixel.pushLength ??= 5;
				pixel.pushStrength ??= 1;
				if(isNaN(pixel.pushTime) || pixel.pushTime < 0) { pixel.pushTime = 0 };

				if(pixel.charge) {
					pixel.pushTime = pixel.pushLength;
				};

				if(pixel.pushTime > 0) {
					for(h = 0; h < pixel.pushStrength; h++) {
						for(i=(pixel.range - 1); i>=0; i--) {
							if (!isEmpty(pixel.x,pixel.y-1-i,true)) {
								tryMove(pixelMap[pixel.x][pixel.y-1-i],pixel.x,pixel.y-2-i);
							};
						};
					};
					pixel.pushTime--;
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.down_e_pusher = {
			color: "#9f9f6f",
			properties: {
				range: 10,
				pushTime: 0,
				pushLength: 5,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushTime ??= 0;
				pixel.pushLength ??= 5;
				pixel.pushStrength ??= 1;
				if(isNaN(pixel.pushTime) || pixel.pushTime < 0) { pixel.pushTime = 0 };

				if(pixel.charge) {
					pixel.pushTime = pixel.pushLength;
				};

				if(pixel.pushTime > 0) {
					for(h = 0; h < pixel.pushStrength; h++) {
						for(i=(pixel.range - 1); i>=0; i--) {
							if (!isEmpty(pixel.x,pixel.y+1+i,true)) {
								tryMove(pixelMap[pixel.x][pixel.y+1+i],pixel.x,pixel.y+2+i);
							};
						};
					};
					pixel.pushTime--;
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum0", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.left_e_pusher = {
			color: "#9f9f6f",
			properties: {
				range: 10,
				pushTime: 0,
				pushLength: 5,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushTime ??= 0;
				pixel.pushLength ??= 5;
				pixel.pushStrength ??= 1;
				if(isNaN(pixel.pushTime) || pixel.pushTime < 0) { pixel.pushTime = 0 };

				if(pixel.charge) {
					pixel.pushTime = pixel.pushLength;
				};

				if(pixel.pushTime > 0) {
					for(h = 0; h < pixel.pushStrength; h++) {
						for(i=(pixel.range - 1); i>=0; i--) {
							if (!isEmpty(pixel.x-1-i,pixel.y,true)) {
								tryMove(pixelMap[pixel.x-1-i][pixel.y],pixel.x-2-i,pixel.y);
							};
						};
					};
					pixel.pushTime--;
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

		elements.right_e_pusher = {
			color: "#9f9f6f",
			properties: {
				range: 10,
				pushTime: 0,
				pushLength: 5,
				pushStrength: 1
			},
			tick: function(pixel) { 
				pixel.range ??= 10;
				pixel.pushTime ??= 0;
				pixel.pushLength ??= 5;
				pixel.pushStrength ??= 1;
				if(isNaN(pixel.pushTime) || pixel.pushTime < 0) { pixel.pushTime = 0 };

				if(pixel.charge) {
					pixel.pushTime = pixel.pushLength;
				};

				if(pixel.pushTime > 0) {
					for(h = 0; h < pixel.pushStrength; h++) {
						for(i=(pixel.range - 1); i>=0; i--) {
							if (!isEmpty(pixel.x+1+i,pixel.y,true)) {
								tryMove(pixelMap[pixel.x+1+i][pixel.y],pixel.x+2+i,pixel.y);
							};
						};
					};
					pixel.pushTime--;
				};

				doDefaults(pixel);
			},
			category: "machines",
			breakInto: ["metal_scrap", "steel", "iron", "glass", "uranium", "tin"],
			tempHigh: 2400,
			stateHigh: ["molten_aluminum", "molten_steel", "molten_iron", "molten_glass", "molten_uranium", "molten_tin"],
			density: 10000,
			hardness: 0.85,
			conduct: 1,
			state: "solid",
			movable: false
		}

	//PORTALS ##
	//heehee haha melanie martinez

		//https://stackoverflow.com/a/60922255
		headBodyObject = {
			"head": "body",
		};

		elements.portal_in = {
			color: "#ee7f00",
			properties: {
				_channel: 0,
				_correspondingPortals: null
			},
			insulate: true,
			onTryMoveInto: function(pixel,otherPixel) {
			  try {
				if(pixel._correspondingPortals == null) {
					return;
				};
				if(pixel._correspondingPortals.length <= 0) {
					return;
				};

				var portal = randomChoice(pixel._correspondingPortals);

				var offset = {x: pixel.x - otherPixel.x, y: pixel.y - otherPixel.y}; //teleport destination's offset, inverted by subtraction

				var destination = {x: portal.x + offset.x, y: portal.y + offset.y};

				var otherElement = otherPixel.element;
				var isHead = (typeof(headBodyObject[otherElement]) !== "undefined");
				var isBody = (typeof(getKeyByValue(headBodyObject,otherElement)) !== "undefined");
				var isBipartite = xor(isHead,isBody); //a head being its own body will break the code	

				if(isBipartite) {
					if(isHead) {
						var dead = otherPixel.dead;
						var body = pixelMap[otherPixel.x][otherPixel.y+1];
						if(body == undefined) { body = null };
						if(!dead && (body !== null)) {
							if(offset.y == -1) {
								offset.y--;
								destination.y--;
							};
							var headSpotIsEmpty = isEmpty(destination.x,destination.y,false);
							var bodySpotIsEmpty = isEmpty(destination.x,destination.y+1,false);
							if(headSpotIsEmpty && bodySpotIsEmpty) {
								tryMove(otherPixel,destination.x,destination.y);
								tryMove(body,destination.x,destination.y+1);
							};
						} else {
							tryMove(otherPixel,destination.x,destination.y);
						};
					} else if(isBody) {
						var dead = otherPixel.dead;
						var head = pixelMap[otherPixel.x][otherPixel.y-1];
						if(head == undefined) { head = null };
						if(!dead && (head !== null)) {
							if(offset.y == 1) {
								offset.y++;
								destination.y++;
							};
							var headSpotIsEmpty = isEmpty(destination.x,destination.y-1,false);
							var bodySpotIsEmpty = isEmpty(destination.x,destination.y,false);
							if(headSpotIsEmpty && bodySpotIsEmpty) {
								tryMove(head,destination.x,destination.y-1);
								tryMove(otherPixel,destination.x,destination.y);
							};
						} else {
							tryMove(otherPixel,destination.x,destination.y);
						};
					};
				} else {
					tryMove(otherPixel,destination.x,destination.y);
				};
			  } catch(error) {
				//ignore stack overflows
			    if(error.toString().includes("call stack")) {
				} else {
					throw new Error("error")
				}
			  }
			},
			tick: function(pixel) {
				pixel._channel = Math.floor(pixel.temp / 100);

				pixel._correspondingPortals = currentPixels.filter(function(pixelToCheck) {
					return (
						pixelToCheck.element == "portal_out" &&
						pixelToCheck._channel == pixelChannel
					);
				},pixelChannel=pixel._channel);

				for(i = 0; i < pixel._correspondingPortals.length; i++) {
					pixel._correspondingPortals[i] = {x: pixel._correspondingPortals[i].x, y: pixel._correspondingPortals[i].y};
				};

				//pixel.tempdebug = JSON.stringify(pixel._correspondingPortals);
			},
			category: "machines",
			state: "solid",
			breakInto: ["radiation","laser","iridium","essence","ionized_deuterium","electron","magic","steel","pop","unstable_mistake","explosion","magic","steel","proton","electron","radiation","laser","iridium"],
			hardness: 0.999
		},

		elements.portal_out = {
			color: "#2222ee",
			insulate: true,
			tick: function(pixel) {
				pixel._channel = Math.floor(pixel.temp / 100);
			},
			behavior: behaviors.WALL,
			category: "machines",
			state: "solid",
			insulate: true,
			breakInto: ["radiation","laser","iridium","essence","ionized_deuterium","electron","magic","steel","pop","unstable_mistake","explosion","magic","steel","proton","electron","radiation","laser","iridium"],
			hardness: 0.999
		}

	//MOBS ##

		//Prerequisite Functions and Variables

		minimumCreeperTries = 3;
		maximumCreeperTries = 3;

		minimumZombieTries = 3;
		maximumZombieTries = 3;

		minimumSkeletonTries = 3;
		maximumSkeletonTries = 3;

		eLists.CREEPER = ["creeper", "angelic_creeper", "hell_creeper", "bombing_creeper", "baby_creeper"];

		headBodyObject = {
			"head": "body",
			"creeper_head": "creeper_body",
			"angelic_creeper_head": "angelic_creeper_body",
			"hell_creeper_head": "hell_creeper_body",
			"bombing_creeper_head": "bombing_creeper_body",
			"zombie_head": "zombie_body",
			"skeleton_head": "skeleton_body",
			"nothing_there_phase_3_head": "nothing_there_phase_3_body",
		};

		var style = document.createElement('style'); //Initialize CSS for creeper spawning's status indicator
		style.type = 'text/css';
		style.id = 'creeperStatusStylesheet';
		//initial style conditional branch
		if(typeof(settings.creeperSpawning) === "undefined") { //undefined (falsy but it needs special handling)
			style.innerHTML = '.creeperStatus { color: #E11; text-decoration: none; }';
		} else {
			if(!settings.creeperSpawning) { //falsy: red
				style.innerHTML = '.creeperStatus { color: #E11; text-decoration: none; }';
			} else if(settings.creeperSpawning) { //truthy: green
				style.innerHTML = '.creeperStatus { color: #1E1; text-decoration: none; }';
			};
		};
		document.getElementsByTagName('head')[0].appendChild(style);

		function coordPyth(xA,yA,xB,yB) { //Distance function, used for explosion trigger
			var a = Math.abs(xB - xA);
			var b = Math.abs(yB - yA);
			var c = Math.sqrt(a**2 + b**2);
			return c;
		};

		function pythSpeed(number1,number2) {
			return Math.sqrt(number1**2 + number2**2);
		};

		function rgbColorBound(number) { //RGB bounding function, used for safety checking color changes
			return Math.min(255,Math.max(0,number));
		};

		function slBound(number) { //SL bounding function (not hue), same use as above
			return Math.min(100,Math.max(0,number));
		};

		function angelicUpwardVelocity(pixel,x,y,radius,fire,smoke,power) { //Angelic Creeper's effect, "compatible" with velocity.js by including the modified version of its code in itself
			var info = elements[pixel.element]
			//console.log("yeet");
			// set the pixel.vx and pixel.vy depending on the angle and power
			if (!elements[pixel.element].excludeRandom) {
				//console.log("LOOKS LIKE IT'S YEETING TIME!");
				var angle = Math.atan2(pixel.y-y,pixel.x-x);
				//console.log(`angle calculated (${angle})`);
				pixel.vx = Math.round((pixel.vx|0) + Math.cos(angle) * (radius * power/10));
				//console.log(`vx calculated (${pixel.vx}) for pixel (${pixel.x},${pixel.y})`);
				pixel.vy = 0 - Math.abs(Math.round((pixel.vy|0) + Math.sin(angle) * (radius * power/4)) + 4); //massively increased Y velocities even for objects below
				//pixel.color = "rgb(255,0,0)";
				//console.log(`vy calculated (${pixel.vy}) for pixel (${pixel.x},${pixel.y})`);
			};
			//console.log(`Velocities set`);
			//console.log(`end`);
		};

		//afterFunction(pixel,x,y,radius,fire,smoke,power,damage);
		function hellExplosionFire(pixel,x,y,radius,fire,smoke,power,damage) { //Angelic Creeper's effect, "compatible" with velocity.js by including the modified version of its code in itself
			var coords = circleCoords(pixel.x,pixel.y,radius);
			for (var i = 0; i < coords.length; i++) {
				var x = coords[i].x;
				var y = coords[i].y;
				if(!isEmpty(x,y,true)) {
					var pixel = pixelMap[x][y];
					var info = elements[pixel.element]
					if (info.burn) { //Light everything on fire
						pixel.burning = true;
						pixel.burnStart = pixelTicks;
						pixel.temp += 10; //smoke prevention
					} else if(Math.random() < 0.05) { //5%/px cursed burning
						pixel.burning = true;
						pixel.burnStart = pixelTicks;
						pixel.temp += 10;
					};
				} else if(isEmpty(x,y)) { //if there's space for fire
					if (Array.isArray(fire)) { //this should remain "fire"
						var newfire = fire[Math.floor(Math.random() * fire.length)];
					} else {
						var newfire = fire;
					};
					createPixel(newfire,x,y); //add fire
					var firePixel = pixelMap[x][y];
					firePixel.temp = Math.max(elements[newfire].temp,firePixel.temp);
					firePixel.burning = true;
				};
			};
		};

		//explodeAtPlus moved to separate file

		if(typeof(settings.creeperSpawning) === "undefined") { //Default creeper setting
			setSetting("creeperSpawning",false);
		};

		function updateCreeperPreferences() { //Creeper setting handler
			if(settings.creeperSpawning) { //If the setting is on
				if(typeof(randomEvents.creeper) !== "function") { //add the event if it's missing
					randomEvents.creeper = function() {
						var amount = Math.floor((Math.random() * maximumCreeperTries)+minimumCreeperTries); //1-3
						//In worldgen worlds, you can expect about half of this because about half of the world is pixels in it.
						for(i = 0; i < amount; i++) { //dummy for to break
							if(settings.creeperSpawning) { //setting validation
								// random x between 1 and width-1
								var x = Math.floor(Math.random()*(width-1))+1;
								// random y between 1 and height
								var y = Math.floor(Math.random()*height-1)+1;
								if (isEmpty(x,y)) {
									// random element from the list of spawnable creepers
									var element = spawnCreepers[Math.floor(Math.random()*spawnCreepers.length)];
									// if element is an array, choose a random element from the array
									if (Array.isArray(element)) {
										element = element[Math.floor(Math.random()*element.length)];
									}
									createPixel(element,x,y);
								};
							} else { //if false (this function is never supposed to fire with the setting false)
								delete randomEvents.creeper; //self-disable
								//substitute event
								var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]];
								event();
								break;
							};
						};
					};
				};
			} else if(!settings.creeperSpawning) { //and if it's off
				if(randomEvents.creeper) { delete randomEvents.creeper }; //delete it if it exists.
			};
		};

		function toggleCreeperSpawning() { //Creeper toggle handler
			if(settings.creeperSpawning != true) { //If it's false
				setSetting("creeperSpawning",true); //make it true and update the status display CSS
				updateCreeperPreferences(); //apply
				document.getElementById("creeperStatusStylesheet").innerHTML = '.creeperStatus { color: #1E1; text-decoration: underline; }'; //Displayed info doen't update until it's pulled up again, so I'm using CSS to dynamically change the color of an element, like with find.js (RIP).
			} else { //and the inverse if it's true
				setSetting("creeperSpawning",false);
				updateCreeperPreferences();
				document.getElementById("creeperStatusStylesheet").innerHTML = '.creeperStatus { color: #E11; text-decoration: none; }';
			};
		};

		//Functions used by Nothing There

		function hasPixel(x,y,elementInput) {
			if(isEmpty(x,y,true)) { //if empty, it can't have a pixel
				return false;
			} else {
				if(elementInput.includes(",")) { //CSTA
					elementInput = elementInput.split(",");
				};
				if(Array.isArray(elementInput)) { //if element list
					return elementInput.includes(pixelMap[x][y].element);
				} else { //if single element
					return pixelMap[x][y].element === elementInput;
				};
			};		
		};

		function arrowAltTb(pixel,breakChanceMultiplier,changetemp=false,defaultBreakIntoDust=false) {
			var info = elements[pixel.element];
			var hardness = defaultHardness;
			if(typeof(info.hardness) === "number") {
				hardness = info.hardness;
			};
			hardness = 1 - hardness; //invert hardness, so a hardness of 0 becomes a 100% chance and a hardness of 1 becomes a 0% chance
			hardness *= breakChanceMultiplier;
			if(Math.random() < hardness) {
				return breakPixel(pixel,changetemp=false,defaultBreakIntoDust=false);
			} else {
				return false;
			};
		};

		function nothingThereBulletMovement(pixel,x,y) {
			if(!tryMove(pixel,x,y)) {
				if(!isEmpty(x,y,true)) {
					var thisDensity = elements[pixel.element].density;
					var newPixel = pixelMap[x][y];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					var newHardness = 0;
					if(nothingThereBulletExcludedElements.includes(newElement)) {
						return false;
					};
					if(typeof(newInfo.hardness) === "number") {
						newHardness = newInfo.hardness;
						//it's inverted later
					};
					if(typeof(newInfo.state) === "undefined" && newElement !== pixel.element) { //Copy-paste of "break" code
						if(Math.random() < ((1 - newHardness) ** 0.6)) {
							swapPixels(pixel,newPixel);
							//console.log(`nothingThereBulletMovement: Breaking pixel (${newPixel.element}, ${newPixel.x}, ${newPixel.y}))`)
							breakPixel(newPixel,false,0.3);
							return true;
						} else {
							deletePixel(pixel.x,pixel.y);
							return false;
						};
					} else {
						if(newElement == pixel.element) {
							swapPixels(pixel,newPixel);
							return true;
						} else if(newInfo.state == "gas") {
							swapPixels(pixel,newPixel);
							return true;
						} else if(newInfo.state == "liquid") {
							var newDensity = 1000;
							if(typeof(newInfo.density) === "number") {
								newDensity = newInfo.density;
								//console.log(`density ${newInfo.density} for ${newElement}`);   
							//} else {
								//console.log(`undef density for ${newElement}, 1000 default`);   
							};
							//console.log(`thisDensity: ${thisDensity}`);
							//console.log(`newDensity: ${newDensity}`);
							var chance = thisDensity/(thisDensity+newDensity);
							//console.log(`${newElement}, ${chance}`)
							if(Math.random() < chance) {
								swapPixels(pixel,newPixel);
							};
							return true;
						} else if(newInfo.state == "solid") {
							if(Math.random() < ((1 - newHardness) ** 0.6)) {
								swapPixels(pixel,newPixel);
								//console.log(`nothingThereBulletMovement: Breaking pixel (${newPixel.element}, ${newPixel.x}, ${newPixel.y}))`)
								breakPixel(newPixel,false,0.3);
								return true;
							} else {
								deletePixel(pixel.x,pixel.y);
								return false;
							};
						};
					};
				} else {
					return false;
				};
			} else {
				return true;
			};
		};

		//End NT functions

		nothingThereBulletExcludedElements = ["wall","nothing_there_phase_1","nothing_there_phase_2","nothing_there_phase_3_body","nothing_there_phase_3_head", "nothing_there_cleaver", "nothing_there_mace"];

		enemyHumanoidArray = ["head","body"] //just in case

		spawnCreepers = ["creeper","baby_creeper","angelic_creeper","bombing_creeper","hell_creeper"];

		if(settings.creeperSpawning) { //creeper spawning option
			randomEvents.creeper = function() {
				var amount = Math.floor((Math.random() * maximumCreeperTries)+minimumCreeperTries); //1-3
				for(i = 0; i < amount; i++) { //dummy for to break
					if(settings.creeperSpawning) { //setting validation
						// random x between 1 and width-1
						var x = Math.floor(Math.random()*(width-1))+1;
						// random y between 1 and height
						var y = Math.floor(Math.random()*height-1)+1;
						if (isEmpty(x,y)) {
							// random element from the list of spawnable creepers
							var element = spawnCreepers[Math.floor(Math.random()*spawnCreepers.length)];
							// if element is an array, choose a random element from the array
							if (Array.isArray(element)) {
								element = element[Math.floor(Math.random()*element.length)];
							}
							createPixel(element,x,y);
						};
					} else { //if false (this function is never supposed to fire with the setting false)
						delete randomEvents.creeper; //self-disable
						//substitute event
						var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]];
						event();
						break;
					};
				};
			};
		};

		standaloneSpawnCreeper = function(amount=1) {
			/*	The amount is the maximum amount of *attempts*. Often, less creepers will spawn due to things in the way.
				In a generated world, which uses half of the space, you can expect about half of this number to spawn.	*/
			for(i = 0; i < amount; i++) { //dummy for to break
				// random x between 1 and width-1
				var x = Math.floor(Math.random()*(width-1))+1;
				// random y between 1 and height
				var y = Math.floor(Math.random()*height-1)+1;
				if (isEmpty(x,y)) {
					// random element from the list of spawnable creepers
					var element = spawnCreepers[Math.floor(Math.random()*spawnCreepers.length)];
					// if element is an array, choose a random element from the array
					if (Array.isArray(element)) {
						element = element[Math.floor(Math.random()*element.length)];
					}
					createPixel(element,x,y);
				};
			};
		};

		//Prerequisite Functions and Variables

		function headHasBody(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			//console.log("Checking head for body");
			if(Object.keys(headBodyObject).includes(pixel.element)) {
				//console.log("The head has a corresponding body element.");
				if(typeof(pixelMap[pX][pY+1]) === "undefined") {
					//console.log("The body's place is empty.");
					return false;
				} else {
					var bodyPixel = pixelMap[pX][pY+1];
					//console.log("The body's place is not empty.");
					return (bodyPixel.element === headBodyObject[pixel.element]);
				};
			} else {
				//console.log("The head does not have corresponding body element.");
				return null;
			};
		};

		function bodyHasHead(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			//console.log("Checking body for head");
			if(Object.values(headBodyObject).includes(pixel.element)) {
				//console.log("The body has a corresponding head element.");
				if(typeof(pixelMap[pX][pY-1]) === "undefined") {
					//console.log("The head's place is empty.");
					return false;
				} else {
					var headPixel = pixelMap[pX][pY-1];
					//console.log("The head's place is not empty.");
					return (headPixel.element === getKeyByValue(headBodyObject,pixel.element));
				};
			} else {
				//console.log("The body does not have corresponding head element.");
				return null;
			};
		};

		function zombifyHuman(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			if(!["head","body"].includes(pixel.element)) {
				//console.log("Not part of a human");
				return false;
			} else {
				if(pixel.element === "head") {
					//console.log("Head");
					if(headHasBody(pixel)) {
						//console.log("There's also a body");
						var body = pixelMap[pX][pY+1];
						changePixel(body,"zombie_body",false);
						//console.log("Body turned (head path)");
					};
					changePixel(pixel,"zombie_head");
					//console.log("Head turned (head path)");
				} else {
					//console.log("Not head (i.e. body)");
					if(bodyHasHead(pixel)) {
						//console.log("There's also a head");
						var head = pixelMap[pX][pY-1];
						changePixel(head,"zombie_head",false);
						//console.log("Head turned (body path)");
					};
					changePixel(pixel,"zombie_body");
					//console.log("Body turned (body path)");
				};
			};
		};

		function dezombifyHuman(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			if(!["zombie_head","zombie_body"].includes(pixel.element)) {
				return false;
			} else {
				if(pixel.element === "zombie_head") {
					if(headHasBody(pixel)) {
						var body = pixelMap[pX][pY+1];
						changePixel(body,"body",false);
					};
					changePixel(pixel,"head");
				} else {
					if(bodyHasHead(pixel)) {
						var head = pixelMap[pX][pY-1];
						changePixel(head,"head",false);
					};
					changePixel(pixel,"body");
				};
			};
		};

		elements.spawner = {
			color: "#1c3038",
			breakInto: ["steel","steel","smoke","magic",null,null,null,null,null],
			properties: {
				spawnCounter: 0,
				spawnTime: 160,
				spawn: null,
				squadiusX: 4,
				squadiusY: 4,
				spawnTries: 4,
				//spawnRangeMax: null, //Disabled by default for performance and because I can't think of how to make it count MPL elements towards the limit
			},
			tick: function(pixel) {
				//validation
				if(Array.isArray(pixel.spawn)) {
					pixel.spawn = pixel.spawn.filter(function(e){ return elementExists(e) });
					if(pixel.spawn.length == 0) {
						pixel.spawn = null;
					};
				} else {
					if(!elementExists(pixel.spawn)) {
						pixel.spawn = null;
					};
				};
				if(pixel.spawn === null) {
					return false;
				};
				if(pixel.spawnCounter <= 0) {
					//var pixelsOfSpawnElement = 0;

					var newSpawn = pixel.spawn;
					if(Array.isArray(newSpawn)) {
						newSpawn = newSpawn[Math.floor(Math.random() * newSpawn.length)];
					};

					var xForMin = -1 * pixel.squadiusX;
					var xForMax = pixel.squadiusX + 1;
					var yForMin = -1 * pixel.squadiusY;
					var yForMax = pixel.squadiusY + 1;

					/*if(pixel.spawnRangeMax !== null) {
						for(xOffset = xForMin; xOffset < xForMax; xOffset++) {
							for(yOffset = yForMin; yOffset < yForMax; yOffset++) {
								var newX = pixel.x + xOffset;
								var newY = pixel.y + yOffset;
								if(isEmpty(newX,newY,true) || outOfBounds(newX,newY)) {
									continue;
								};
								newPixel = pixelMap[newX][newY];
								if(newPixel.element == pixel.spawn || pixel.spawn.includes(newPixel.element)) {
									pixelsOfSpawnElement++;
								};
							};
						};
						if(pixelsOfSpawnElement > pixel.spawnRangeMax) {
							return false;
						};
					};*/

					for(s = 0; s < pixel.spawnTries; s++) {
						var randomX = pixel.x + randomIntegerBetweenTwoValues(0 - pixel.squadiusX, pixel.squadiusX);
						var randomY = pixel.y + randomIntegerBetweenTwoValues(0	- pixel.squadiusY, pixel.squadiusY);
						if(isEmpty(randomX,randomY,false)) {
							createPixel(newSpawn,randomX,randomY);
						};
					};
					pixel.spawnCounter = pixel.spawnTime;
				} else {
					pixel.spawnCounter--;
				};
			},
			hardness: 0.7,
			state: "solid",
		}

		elements.frozen_rotten_meat = {
			color: ["#8FB588", "#8FA888"],
			behavior: [
				"XX|CR:plague,stench,stench%0.125 AND CH:meat>rotten_meat%1 AND CH:frozen_meat>frozen_rotten_meat%0.85|XX",
				"SP%99 AND CH:meat>rotten_meat%1 AND CH:frozen_meat>frozen_rotten_meat%0.85|XX|SP%99 AND CH:meat>rotten_meat%1 AND CH:frozen_meat>frozen_rotten_meat%0.85",
				"XX|M1 AND CH:meat>rotten_meat%1 AND CH:frozen_meat>frozen_rotten_meat%0.85|XX",
			],
			temp: -18,
			tempHigh: 0,
			stateHigh: "rotten_meat",
			category:"food",
			hidden:true,
			state: "solid",
			density: 1037.5,
		};

		elements.rotten_meat.tempLow = -18;
		elements.rotten_meat.stateLow = "frozen_rotten_meat";

		elements.zombie_blood = {
			color: ["#d18228", "#9a9e2f"],
			behavior: behaviors.LIQUID,
			reactions: {
				"vaccine": { "elem2":null, "chance": 0.01 },
				"plague": { "elem2":null, "chance": 0.01 },
				"virus": { "elem2":null, "chance": 0.01 },
				"cancer": { "elem1":"cancer", "chance": 0.02 },
				/*"rat": { "elem2":"infection", "chance":0.075 },
				"flea": { "elem1":"infection", "chance":0.03 },
				"dirt": { "elem1":null, "elem2":"mud" },
				"sand": { "elem1":null, "elem2":"wet_sand" },
				"mercury": { "elem1":"infection", "elem2":null, "chance":0.05 },
				"carbon_dioxide": { "elem2":null, "chance":0.05 },
				"alcohol": { "elem1":[null,"dna"], "chance":0.02 },*/
				"oxygen": { "elem2":null, "chance":0.04 },
				"blood": { "elem2":"zombie_blood", "chance":0.1 }
			},
			viscosity: 30,
			tempHigh: 127.55,
			stateHigh: ["steam","salt","oxygen","plague"],
			tempLow: 0,
			category:"liquids",
			state: "liquid",
			density: 1160,
			stain: 0.06,
			tick: function(pixel) {
				if(Math.random() < 0.2) {
					var pX = pixel.x;
					var pY = pixel.y;
					for(i = 0; i < adjacentCoords.length; i++) {
						var coord = adjacentCoords[i];
						var oX = coord[0];
						var oY = coord[1];
						var nX = pX+oX;
						var nY = pY+oY;
						if(isEmpty(nX,nY,true)) {
							continue;
						} else {
							var newPixel = pixelMap[nX][nY];
							var newElement = newPixel.element;
							if(enemyHumanoidArray.includes(newElement)) {
								if(Math.random() < 0.1) { zombifyHuman(newPixel) };
							};
						};
					};
				};
			},
		};

		var style = document.createElement('style'); //Initialize CSS for zombie spawning's status indicator
		style.type = 'text/css';
		style.id = 'zombieStatusStylesheet';
		//initial style conditional branch
		if(typeof(settings.zombieSpawning) === "undefined") { //undefined (falsy but it needs special handling)
			style.innerHTML = '.zombieStatus { color: #E11; text-decoration: none; }';
		} else {
			if(!settings.zombieSpawning) { //falsy: red
				style.innerHTML = '.zombieStatus { color: #E11; text-decoration: none; }';
			} else if(settings.zombieSpawning) { //truthy: green
				style.innerHTML = '.zombieStatus { color: #1E1; text-decoration: none; }';
			};
		};
		document.getElementsByTagName('head')[0].appendChild(style);

		if(typeof(settings.zombieSpawning) === "undefined") { //Default zombie setting
			setSetting("zombieSpawning",false);
		};

		function updateZombiePreferences() { //Zombie setting handler
			if(settings.zombieSpawning) { //If the setting is on
				if(typeof(randomEvents.zombie) !== "function") { //add the event if it's missing
					randomEvents.zombie = function() {
						var amount = Math.floor((Math.random() * maximumZombieTries)+minimumZombieTries); //1-3
						//In worldgen worlds, you can expect about half of this because about half of the world is pixels in it.
						for(i = 0; i < amount; i++) { //dummy for to break
							if(settings.zombieSpawning) { //setting validation
								// random x between 1 and width-1
								var x = Math.floor(Math.random()*(width-1))+1;
								// random y between 1 and height
								var y = Math.floor(Math.random()*height-1)+1;
								if (isEmpty(x,y)) {
									// random element from the list of spawnable zombies
									var element = spawnZombies[Math.floor(Math.random()*spawnZombies.length)];
									// if element is an array, choose a random element from the array
									if (Array.isArray(element)) {
										element = element[Math.floor(Math.random()*element.length)];
									}
									createPixel(element,x,y);
								};
							} else { //if false (this function is never supposed to fire with the setting false)
								delete randomEvents.zombie; //self-disable
								//substitute event
								var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]];
								event();
								break;
							};
						};
					};
				};
			} else if(!settings.zombieSpawning) { //and if it's off
				if(randomEvents.zombie) { delete randomEvents.zombie }; //delete it if it exists.
			};
		};

		function toggleZombieSpawning() { //Zombie toggle handler
			if(settings.zombieSpawning != true) { //If it's false
				setSetting("zombieSpawning",true); //make it true and update the status display CSS
				updateZombiePreferences(); //apply
				document.getElementById("zombieStatusStylesheet").innerHTML = '.zombieStatus { color: #1E1; text-decoration: underline; }'; //Displayed info doen't update until it's pulled up again, so I'm using CSS to dynamically change the color of an element, like with find.js (RIP).
			} else { //and the inverse if it's true
				setSetting("zombieSpawning",false);
				updateZombiePreferences();
				document.getElementById("zombieStatusStylesheet").innerHTML = '.zombieStatus { color: #E11; text-decoration: none; }';
			};
		};

		spawnZombies = ["zombie","baby_zombie"];

		if(settings.zombieSpawning) { //zombie spawning option
			randomEvents.zombie = function() {
				var amount = Math.floor((Math.random() * maximumZombieTries)+minimumZombieTries); //1-3
				for(i = 0; i < amount; i++) { //dummy for to break
					if(settings.zombieSpawning) { //setting validation
						// random x between 1 and width-1
						var x = Math.floor(Math.random()*(width-1))+1;
						// random y between 1 and height
						var y = Math.floor(Math.random()*height-1)+1;
						if (isEmpty(x,y)) {
							// random element from the list of spawnable zombies
							var element = spawnZombies[Math.floor(Math.random()*spawnZombies.length)];
							// if element is an array, choose a random element from the array
							if (Array.isArray(element)) {
								element = element[Math.floor(Math.random()*element.length)];
							}
							createPixel(element,x,y);
						};
					} else { //if false (this function is never supposed to fire with the setting false)
						delete randomEvents.zombie; //self-disable
						//substitute event
						var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]];
						event();
						break;
					};
				};
			};
		};

		standaloneSpawnZombie = function(amount=1) {
			/*	The amount is the maximum amount of *attempts*. Often, less zombies will spawn due to things in the way.
				In a generated world, which uses half of the space, you can expect about half of this number to spawn.	*/
			for(i = 0; i < amount; i++) { //dummy for to break
				// random x between 1 and width-1
				var x = Math.floor(Math.random()*(width-1))+1;
				// random y between 1 and height
				var y = Math.floor(Math.random()*height-1)+1;
				if (isEmpty(x,y)) {
					// random element from the list of spawnable zombies
					var element = spawnZombies[Math.floor(Math.random()*spawnZombies.length)];
					// if element is an array, choose a random element from the array
					if (Array.isArray(element)) {
						element = element[Math.floor(Math.random()*element.length)];
					}
					createPixel(element,x,y);
				};
			};
		};

		var style = document.createElement('style'); //Initialize CSS for skeleton spawning's status indicator
		style.type = 'text/css';
		style.id = 'skeletonStatusStylesheet';
		//initial style conditional branch
		if(typeof(settings.skeletonSpawning) === "undefined") { //undefined (falsy but it needs special handling)
			style.innerHTML = '.skeletonStatus { color: #E11; text-decoration: none; }';
		} else {
			if(!settings.skeletonSpawning) { //falsy: red
				style.innerHTML = '.skeletonStatus { color: #E11; text-decoration: none; }';
			} else if(settings.skeletonSpawning) { //truthy: green
				style.innerHTML = '.skeletonStatus { color: #1E1; text-decoration: none; }';
			};
		};
		document.getElementsByTagName('head')[0].appendChild(style);

		if(typeof(settings.skeletonSpawning) === "undefined") { //Default skeleton setting
			setSetting("skeletonSpawning",false);
		};

		function updateSkeletonPreferences() { //Skeleton setting handler
			if(settings.skeletonSpawning) { //If the setting is on
				if(typeof(randomEvents.skeleton) !== "function") { //add the event if it's missing
					randomEvents.skeleton = function() {
						var amount = Math.floor((Math.random() * maximumSkeletonTries)+minimumSkeletonTries); //1-3
						//In worldgen worlds, you can expect about half of this because about half of the world is pixels in it.
						for(i = 0; i < amount; i++) { //dummy for to break
							if(settings.skeletonSpawning) { //setting validation
								// random x between 1 and width-1
								var x = Math.floor(Math.random()*(width-1))+1;
								// random y between 1 and height
								var y = Math.floor(Math.random()*height-1)+1;
								if (isEmpty(x,y)) {
									// random element from the list of spawnable skeletons
									var element = spawnSkeletons[Math.floor(Math.random()*spawnSkeletons.length)];
									// if element is an array, choose a random element from the array
									if (Array.isArray(element)) {
										element = element[Math.floor(Math.random()*element.length)];
									}
									createPixel(element,x,y);
								};
							} else { //if false (this function is never supposed to fire with the setting false)
								delete randomEvents.skeleton; //self-disable
								//substitute event
								var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]];
								event();
								break;
							};
						};
					};
				};
			} else if(!settings.skeletonSpawning) { //and if it's off
				if(randomEvents.skeleton) { delete randomEvents.skeleton }; //delete it if it exists.
			};
		};

		function toggleSkeletonSpawning() { //Skeleton toggle handler
			if(settings.skeletonSpawning != true) { //If it's false
				setSetting("skeletonSpawning",true); //make it true and update the status display CSS
				updateSkeletonPreferences(); //apply
				document.getElementById("skeletonStatusStylesheet").innerHTML = '.skeletonStatus { color: #1E1; text-decoration: underline; }'; //Displayed info doen't update until it's pulled up again, so I'm using CSS to dynamically change the color of an element, like with find.js (RIP).
			} else { //and the inverse if it's true
				setSetting("skeletonSpawning",false);
				updateSkeletonPreferences();
				document.getElementById("skeletonStatusStylesheet").innerHTML = '.skeletonStatus { color: #E11; text-decoration: none; }';
			};
		};

		spawnSkeletons = ["skeleton"];

		if(settings.skeletonSpawning) { //skeleton spawning option
			randomEvents.skeleton = function() {
				var amount = Math.floor((Math.random() * maximumSkeletonTries)+minimumSkeletonTries); //1-3
				for(i = 0; i < amount; i++) { //dummy for to break
					if(settings.skeletonSpawning) { //setting validation
						// random x between 1 and width-1
						var x = Math.floor(Math.random()*(width-1))+1;
						// random y between 1 and height
						var y = Math.floor(Math.random()*height-1)+1;
						if (isEmpty(x,y)) {
							// random element from the list of spawnable skeletons
							var element = spawnSkeletons[Math.floor(Math.random()*spawnSkeletons.length)];
							// if element is an array, choose a random element from the array
							if (Array.isArray(element)) {
								element = element[Math.floor(Math.random()*element.length)];
							}
							createPixel(element,x,y);
						};
					} else { //if false (this function is never supposed to fire with the setting false)
						delete randomEvents.skeleton; //self-disable
						//substitute event
						var event = randomEvents[Object.keys(randomEvents)[Math.floor(Math.random()*Object.keys(randomEvents).length)]];
						event();
						break;
					};
				};
			};
		};

		standaloneSpawnSkeleton = function(amount=1) {
			/*	The amount is the maximum amount of *attempts*. Often, less skeletons will spawn due to things in the way.
				In a generated world, which uses half of the space, you can expect about half of this number to spawn.	*/
			for(i = 0; i < amount; i++) { //dummy for to break
				// random x between 1 and width-1
				var x = Math.floor(Math.random()*(width-1))+1;
				// random y between 1 and height
				var y = Math.floor(Math.random()*height-1)+1;
				if (isEmpty(x,y)) {
					// random element from the list of spawnable skeletons
					var element = spawnSkeletons[Math.floor(Math.random()*spawnSkeletons.length)];
					// if element is an array, choose a random element from the array
					if (Array.isArray(element)) {
						element = element[Math.floor(Math.random()*element.length)];
					}
					createPixel(element,x,y);
				};
			};
		};

		/*Start Main Zombie
			..................
			........%%%%......
			.......%%%%%%.....
			......%%%%%OOOOO..
			....%%%%%%OOOOOOO.
			...%%%%%%%%OOOOO..
			...%%%%%%%%%%%....
			...%%%%%%%%%%%....
			...%%%%%%%%%%%....
			....%%%%%%%%%%....
			......%%%%%%%%....
			.......%%...%%....
			.......%%...%%....
			..................
		*/

		elements.zombie = {
			color: ["#567C44","#199A9A","#41369B"],
			category: "life",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("zombie_body", pixel.x, pixel.y+1);
					pixel.element = "zombie_head";
					pixel.color = pixelColorPick(pixel)
				}
				else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("zombie_head", pixel.x, pixel.y-1);
					pixelMap[pixel.x][pixel.y-1].color = pixel.color;
					pixel.element = "zombie_body";
					pixel.color = pixelColorPick(pixel)
				}
				else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["zombie_body","zombie_head"],
			desc: "<em>I'd rather this be toggleable mid-game than require a reload.</em><br/><br/><span class=\"zombieStatus\">If this text is green or underlined, zombies (all types) can spawn.</span> <span onclick=toggleZombieSpawning() style=\"color: #ff00ff;\";>Click here</span> to toggle zombie spawning. If it's on, zombies can spawn through random events."
		};

		elements.zombie_body = {
			color: "#27719D",
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "rotten_meat",
			tempLow: -30,
			stateLow: "frozen_rotten_meat",
			burn: 10,
			burnTime: 250,
			burnInto: "rotten_meat",
			breakInto: ["zombie_blood","rotten_meat"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","rotten_meat","rotten_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.025 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0
			},
			movable: true,
			tick: function(pixel) {
				if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
					if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
						var headPixel = pixelMap[pixel.x][pixel.y-2];
						if (headPixel.element == "zombie_head") {
							if (isEmpty(pixel.x, pixel.y-1)) {
								movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
							}
							else {
								swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
							}
						}
					}
				}
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 100
					if (pixelTicks-pixel.dead > 100) {
						changePixel(pixel,"rotten_meat");
					};
					return;
				};

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true)) {
					if(pixelMap[pixel.x][pixel.y-1].element == "head") {
						changePixel(pixelMap[pixel.x][pixel.y-1],"zombie_head");
					} else if(pixelMap[pixel.x][pixel.y-1].element == "zombie_head") {
						var head = pixelMap[pixel.x][pixel.y-1];
						if (head.dead) { // If head is dead, kill body
							pixel.dead = head.dead;
						};
					} else {
						var head = null;
					};
				} else { var head = null };

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create zombie blood if decapitated 10% chance
					if (Math.random() < 0.1) {
						createPixel("zombie_blood", pixel.x, pixel.y-1);
						// set dead to true 10% chance
						if (Math.random() < 0.10) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return }
				else if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};
			},
		};

		elements.zombie_head = {
			color: "#567C44",
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "rotten_meat",
			tempLow: -30,
			stateLow: "frozen_rotten_meat",
			burn: 10,
			burnTime: 250,
			burnInto: "rotten_meat",
			breakInto: ["zombie_blood","rotten_meat"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","rotten_meat","rotten_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.025 }
			},
			properties: {
				dead: false,
				following: false,
				dir: 1,
				panic: 0
			},
			movable: true,
			tick: function(pixel) {
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 100
					if (pixelTicks-pixel.dead > 100) {
						changePixel(pixel,"rotten_meat");
					};
					return;
				};

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true)) {
					if(pixelMap[pixel.x][pixel.y+1].element == "body") {
						changePixel(pixelMap[pixel.x][pixel.y+1],"zombie_body");
					} else if(pixelMap[pixel.x][pixel.y+1].element == "zombie_body") {
						var body = pixelMap[pixel.x][pixel.y+1];
						if (body.dead) { // If body is dead, kill body
							pixel.dead = body.dead;
						};
					} else {
						var body = null;
					};
				} else { var body = null };

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create zombie blood if severed 10% chance
					if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
						createPixel("zombie_blood", pixel.x, pixel.y+1);
						// set dead to true 10% chance
						if (Math.random() < 0.10) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				if(pixelTicks % 2 == 0) { //reduce rate for performance
					/*var directionAdverb = "left";
					if(pixel.dir > 0) {
						directionAdverb = "right";
					};*/
					//console.log(`Looking ${directionAdverb}`)
					if(pixel.dir === -1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = (-1); j > (-35 - 1); j--) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/3) {	//One-third chance to mutilate (changePixel)
													if(Math.random() < 1/3) {	//One-third chance to change to blood
														changePixel(newPixel,"zombie_blood",false); //blood is turned in place
													} else {					//Remaining 2/3 chance to change to rotten flesh
														changePixel(newPixel,"rotten_meat",false);
													};
												} else {					//Remaining 2/3 chance to turn the human
													zombifyHuman(newPixel);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/3) {	//One-third chance to change to blood
												changePixel(newPixel,"zombie_blood",false); //blood is turned in place
											} else {					//Remaining 2/3 chance to change to rotten flesh
												changePixel(newPixel,"rotten_meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					} else if(pixel.dir === 1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = 1; j < 35 + 1; j++) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/3) {	//One-third chance to mutilate (changePixel)
													if(Math.random() < 1/3) {	//One-third chance to change to blood
														changePixel(newPixel,"zombie_blood",false); //blood is turned in place
													} else {					//Remaining 2/3 chance to change to rotten flesh
														changePixel(newPixel,"rotten_meat",false);
													};
												} else {					//Remaining 2/3 chance to turn the human
													zombifyHuman(newPixel);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/3) {	//One-third chance to change to blood
												changePixel(newPixel,"zombie_blood",false); //blood is turned in place
											} else {					//Remaining 2/3 chance to change to rotten flesh
												changePixel(newPixel,"rotten_meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

			//Baby Zombie

		elements.baby_zombie = {
			color: "#199A9A",
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "rotten_meat",
			tempLow: -30,
			stateLow: "frozen_rotten_meat",
			burn: 10,
			burnTime: 250,
			burnInto: "rotten_meat",
			breakInto: ["zombie_blood","rotten_meat"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","rotten_meat","rotten_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.025 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0
			},
			movable: true,
			tick: function(pixel) {
				tryMove(pixel, pixel.x, pixel.y+1); // Fall
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 100
					if (pixelTicks-pixel.dead > 100) {
						changePixel(pixel,"rotten_meat");
					};
					return;
				};

				if (Math.random() < 0.15) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],	//dash move
						[1*pixel.dir,-1],	//slash move
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if(tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!pixel.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				var pX = pixel.x;
				var pY = pixel.y;

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				if(pixelTicks % 2 == 0) { //reduce rate for performance
					/*var directionAdverb = "left";
					if(pixel.dir > 0) {
						directionAdverb = "right";
					};*/
					//console.log(`Looking ${directionAdverb}`)
					if(pixel.dir === -1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = (-1); j > (-35 - 1); j--) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/3) {	//One-third chance to mutilate (changePixel)
													if(Math.random() < 1/4) {	//One-fourth chance to change to blood
														changePixel(newPixel,"zombie_blood",false); //blood is turned in place
													} else {					//Remaining 3/4 chance to change to rotten flesh
														changePixel(newPixel,"rotten_meat",false);
													};
												} else {					//Remaining 2/3 chance to turn the human
													zombifyHuman(newPixel);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/4) {	//One-fourth chance to change to blood
												changePixel(newPixel,"zombie_blood",false); //blood is turned in place
											} else {					//Remaining 3/4 chance to change to rotten flesh
												changePixel(newPixel,"rotten_meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					} else if(pixel.dir === 1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = 1; j < 35 + 1; j++) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/3) {	//One-third chance to mutilate (changePixel)
													if(Math.random() < 1/4) {	//One-fourth chance to change to blood
														changePixel(newPixel,"zombie_blood",false); //blood is turned in place
													} else {					//Remaining 3/4 chance to change to rotten flesh
														changePixel(newPixel,"rotten_meat",false);
													};
												} else {					//Remaining 2/3 chance to turn the human
													zombifyHuman(newPixel);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/4) {	//One-fourth chance to change to blood
												changePixel(newPixel,"zombie_blood",false); //blood is turned in place
											} else {					//Remaining 3/4 chance to change to rotten flesh
												changePixel(newPixel,"rotten_meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
			related: ["zombie"],
			desc: "Baby zombies: smaller, faster, and more annoying.",
		};

		/*Start Main Creeper
			%%%%%%%%%%%%%%%%%%
			%%%%%%%%	%%%%%%
			%%%%%%%	  %%%%%
			%%%%%%	 OOOOO%%
			%%%%	  OOOOOOO%
			%%%		OOOOO%%
			%%%		   %%%%
			%%%		   %%%%
			%%%		   %%%%
			%%%%		  %%%%
			%%%%%%		%%%%
			%%%%%%%  %%%  %%%%
			%%%%%%%  %%%  %%%%
			%%%%%%%%%%%%%%%%%%
		*/

		elements.creeper = {
			color: ["#D2D2D2", "#BFDFB9", "#94CE89", "#78D965", "#5ED54C", "#58C546", "#50B143", "#479143", "#559552", "#3F8738", "#5B8B59"],
			category: "life",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("creeper_body", pixel.x, pixel.y+1);
					pixel.element = "creeper_head";
					pixel.color = pixelColorPick(pixel)
				}
				else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("creeper_head", pixel.x, pixel.y-1);
					pixelMap[pixel.x][pixel.y-1].color = pixel.color;
					pixel.element = "creeper_body";
					pixel.color = pixelColorPick(pixel)
				}
				else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["creeper_body","creeper_head"],
			desc: "<em>I'd rather this be toggleable mid-game than require a reload.</em><br/><br/><span class=\"creeperStatus\">If this text is green or underlined, creepers can spawn.</span> <span onclick=toggleCreeperSpawning() style=\"color: #ff00ff;\";>Click here</span> to toggle creeper spawning. If it's on, creepers (all types) can spawn through random events.<br/>To enable automatic creeper generation, set the generateCreepers query parameter."
		};

		elements.creeper_body = {
			color: ["#D2D2D2", "#BFDFB9", "#94CE89", "#78D965", "#5ED54C", "#58C546", "#50B143", "#479143", "#559552", "#3F8738", "#5B8B59"],
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
			breakInto: ["blood","gunpowder"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
					if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
						var headPixel = pixelMap[pixel.x][pixel.y-2];
						if (headPixel.element == "creeper_head") {
							if (isEmpty(pixel.x, pixel.y-1)) {
								movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
							}
							else {
								swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
							}
						}
					}
				}
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
					}
					return
				}

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "creeper_head") {
					var head = pixelMap[pixel.x][pixel.y-1];
					if (head.dead) { // If head is dead, kill body
						pixel.dead = head.dead;
					}
				}
				else { var head = null }

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create blood if decapitated 10% chance
					if (Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y-1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return }
				else if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(head) {
					if(typeof(head.charge) !== "undefined") {
						if(head.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(head.charged) !== "undefined") {
						if(head.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 7;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 5;
				};

				if(pixel.burning) {
					pixel.hissing = true;
					if(!pixel.hissStart) {
						pixel.hissStart = pixelTicks;
					};
					if(!pixel.burnStart) { //I don't like errors.
						pixel.burnStart = pixel.ticks;
					};
					if(pixelTicks - pixel.burnStart > 30) {
						//console.log("Kaboom?");
						explodeAt(pixel.x,pixel.y,explosionRadius);
						//console.log("Yes, Rico, kaboom.");
					};
				};

				//Head hissing color handler: keeps track of head's hissing for coloring purposes
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.dead || !head || head.dead) { //can't hiss without a head according to the classic creeper anatomy
						//console.log("ss-- oof");
						pixel.hissing = false;
						break;
					};
					if(head.hissing) {
						//console.log("Ssssssss");
						if(!head.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							head.hissStart = pixelTicks;
						};

						//Color code {
							var ticksHissing = pixelTicks - head.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								//console.log("the j");
								luminance = slBound(luminance + 1.176);
								//console.log(luminance);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}
					};
				};
			},
		};

		elements.creeper_head = {
			color: ["#5B8B59", "#3F8738", "#559552", "#479143", "#50B143", "#58C546"],
			category: "life",
			hidden: true,
			density: 1080,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
			breakInto: "blood",
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.5 }
			},
			properties: {
				dead: false,
				following: false,
				hissing: false,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
						return
					}
				}

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "creeper_body") {
					var body = pixelMap[pixel.x][pixel.y+1];
					if (body.dead) { // If body is dead, kill head
						pixel.dead = body.dead;
					}
				}
				else { var body = null }

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create blood if severed 10% chance
					if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y+1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(body) {
					if(typeof(body.charge) !== "undefined") {
						if(body.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(body.charged) !== "undefined") {
						if(body.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				var directionAdverb = "left";
				if(pixel.dir > 0) {
					directionAdverb = "right";
				};
				//console.log(`Looking ${directionAdverb}`)
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius"
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) {
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
										break;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break;
								};
							};
						};
					};
				};

				//Pre-explosion handler: keeps track of time before the kaboom
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.hissing) {
						//console.log("Ssssssss");
						if(pixel.dead || !body || body.dead) { //can't explode without a body according to the classic creeper anatomy
							//console.log("ss-- oof");
							pixel.hissing = false;
							break;
						};
						if(!pixel.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							pixel.hissStart = pixelTicks;
						};
						//Color code {
							var ticksHissing = pixelTicks - pixel.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								luminance = slBound(luminance + 1.176);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}

						if(pixelTicks - pixel.hissStart > 30) {
							//console.log("Kaboom?");
							//console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`);
							explodeAt(body.x,body.y,explosionRadius);
							//console.log("Yes, Rico, kaboom.");
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

		/*End Main Creeper
			&&&&&&&&&&&&&&&&&&
			&&&&&&&&&&&&&&&&&&
			&&&&&&&&&&&&&&&&&&
			&&&&&&&&&X&X&&&&&&
			&&&&&&&&&XXX&&&&&&
			&&&&&&&&&&X&&&&&&&
			&&&&&&&&&&X&&&&&&&
			&&& & & & X & &&&&
			&&&		   &&&&
			&&&&		  &&&&
			&&&&&&		&&&&
			&&&&&&&  &&&  &&&&
			&&&&&&&  &&&  &&&&
			&&&&&&&&&&&&&&&&&&
		*/

																//Baby Creeper

		elements.baby_creeper = {
			color: ["#D2D2D2", "#BFDFB9", "#94CE89", "#78D965", "#5ED54C", "#58C546", "#50B143", "#479143", "#559552", "#3F8738", "#5B8B59"],
			category: "life",
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
			breakInto: ["blood","gunpowder"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				tryMove(pixel, pixel.x, pixel.y+1); // Fall
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
					}
					return
				}

				if (Math.random() < 0.15) { // Move 15% chance (should be 12.5 but 15 looks better)
					var movesToTry = [
						[1*pixel.dir,0],	//dash move
						[1*pixel.dir,-1],	//slash move
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if(tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!pixel.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charge) {
					pixel.charged = true;
				};

				var pX = pixel.x;
				var pY = pixel.y;

				if(pixel.charged) {
					var explosionRadius = 6;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 4; //should be half of the original creeper's radius
				};

				if(pixel.burning) {
					pixel.hissing = true;
					if(!pixel.hissStart) {
						pixel.hissStart = pixelTicks;
					};
					if(!pixel.burnStart) { //I don't like errors.
						pixel.burnStart = pixel.ticks;
					};
					if(pixelTicks - pixel.burnStart > 15) {
						//console.log("Kaboom?");
						explodeAt(pixel.x,pixel.y,explosionRadius);
						//console.log("Yes, Rico, kaboom.");
					};
				};

				//Pre-explosion handler: keeps track of time before the kaboom
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.hissing) {
						//console.log("Ssssssss");
						if(pixel.dead) {
							//console.log("ss-- oof");
							pixel.hissing = false;
							break;
						};
						if(!pixel.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							pixel.hissStart = pixelTicks;
						};
						//Color code {
							var ticksHissing = pixelTicks - pixel.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + (2 * ticksHissing));
								green = rgbColorBound(green + (2 * ticksHissing));
								blue = rgbColorBound(blue + (2 * ticksHissing));
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								luminance = slBound(luminance + (2 * 1.176));
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}

						if(pixelTicks - pixel.hissStart > 15) {
							//console.log("Kaboom?");
							//console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`);
							explodeAt(pixel.x,pixel.y,explosionRadius);
							//console.log("Yes, Rico, kaboom.");
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				var directionAdverb = "left";
				if(pixel.dir > 0) {
					directionAdverb = "right";
				};
				//console.log(`Looking ${directionAdverb}`)
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius"
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) {
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
										break;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break;
								};
							};
						};
					};
				};
			},
			related: ["creeper"],
		};

																//Angelic Creeper

		elements.angelic_creeper = { //let's get this one out of the way first
			color: ["#f5ef56", "#fcbddf", "#de8aa8", "#e35d95", "#eb4974", "#ed3ea7", "#d645a3", "#a84556", "#9e4f6c", "#91315b", "#8c4963"],
			category: "life",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("angelic_creeper_body", pixel.x, pixel.y+1);
					pixel.element = "angelic_creeper_head";
					pixel.color = pixelColorPick(pixel)
				}
				else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("angelic_creeper_head", pixel.x, pixel.y-1);
					pixelMap[pixel.x][pixel.y-1].color = pixel.color;
					pixel.element = "angelic_creeper_body";
					pixel.color = pixelColorPick(pixel)
				}
				else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["angelic_creeper_body","angelic_creeper_head"],
			desc: 'A creeper type from <em>Extra Creeper Types</em> <a href="https://www.curseforge.com/minecraft/mc-mods/extra-creeper-types">(CF)</a>. It sends things upward.'
		};

		elements.angelic_creeper_body = {
			color: ["#d2d2d2", "#fcbddf", "#de8aa8", "#e35d95", "#eb4974", "#ed3ea7", "#d645a3", "#a84556", "#9e4f6c", "#91315b", "#8c4963"],
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
			breakInto: ["blood","blood","gunpowder","gunpowder","feather"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				if(!pixel.hissing) { //If not hissing (it floats when hissing)
					if(Math.random() < 0.2) { //20% chance to fall
						if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
							if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
								var headPixel = pixelMap[pixel.x][pixel.y-2];
								if (headPixel.element == "angelic_creeper_head") {
									if (isEmpty(pixel.x, pixel.y-1)) {
										movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
									} else {
										swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
									};
								};
							};
						};
					};
				} else {
					if((pixelTicks - pixel.start) % 3 == 0) {
						if (!isEmpty(pixel.x, pixel.y-1, true)) { // Find head
							var headPixel = pixelMap[pixel.x][pixel.y-1];
							if (headPixel.element == "angelic_creeper_head") { //Validate head
								if (tryMove(headPixel, pixel.x, pixel.y-2)) { // Float
									if (isEmpty(pixel.x, pixel.y-1)) { //If the head didn't swap with something
										movePixel(pixel, pixel.x, pixel.y-1); //Pull body up
									} else { //If it did swap
										swapPixels(pixel, pixelMap[pixel.x][pixel.y-1]); //Pull body up through other pixel
									};
								};
							};
						};
					};
				};
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
					}
					return
				}

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "angelic_creeper_head") {
					var head = pixelMap[pixel.x][pixel.y-1];
					if (head.dead) { // If head is dead, kill body
						pixel.dead = head.dead;
					}
				}
				else { var head = null }

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create blood if decapitated 10% chance
					if (Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y-1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return }
				else if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(head) {
					if(typeof(head.charge) !== "undefined") {
						if(head.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(head.charged) !== "undefined") {
						if(head.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				if(pixel.burning) {
					pixel.hissing = true;
					if(!pixel.hissStart) {
						pixel.hissStart = pixelTicks;
					};
					if(!pixel.burnStart) { //I don't like errors.
						pixel.burnStart = pixel.ticks;
					};
					if(pixelTicks - pixel.burnStart > 30) {
						//console.log("GOTTA YEET YEET YEET!");
						explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","smoke",null,angelicUpwardVelocity);	//Special effect: Flings you upwards (extended to all movable tiles because it's easier).
																										//It also floats when hissing, but that will come soon.
						//console.log("Yes, Rico, kaboom.");
					};
				};

				//Head hissing color handler: keeps track of head's hissing for coloring purposes
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.dead || !head || head.dead) { //can't hiss without a head according to the classic creeper anatomy
						//console.log("ss-- oof");
						pixel.hissing = false;
						break;
					};
					if(head.hissing) {
						//console.log("Ssssssss");
						if(!head.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							head.hissStart = pixelTicks;
						};

						//Color code {
							var ticksHissing = pixelTicks - head.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								//console.log("the j");
								luminance = slBound(luminance + 1.176);
								//console.log(luminance);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}
					};
				};
			},
		},

		elements.angelic_creeper_head = {
			color: ["#f5ef56", "#f0ea4f", "#f0ea60"],
			category: "life",
			hidden: true,
			density: 1080,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
			breakInto: ["blood","blood","blood","blood","feather"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.5 }
			},
			properties: {
				dead: false,
				following: false,
				hissing: false,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
						return
					}
				}

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "angelic_creeper_body") {
					var body = pixelMap[pixel.x][pixel.y+1];
					if (body.dead) { // If body is dead, kill head
						pixel.dead = body.dead;
					}
				}
				else { var body = null }

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create blood if severed 10% chance
					if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y+1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(body) {
					if(typeof(body.charge) !== "undefined") {
						if(body.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(body.charged) !== "undefined") {
						if(body.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				var directionAdverb = "left";
				if(pixel.dir > 0) {
					directionAdverb = "right";
				};
				//console.log(`Looking ${directionAdverb}`)
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius"
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) {
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
										break;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break;
								};
							};
						};
					};
				};

				//Pre-explosion handler: keeps track of time before the kaboom
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.hissing) {
						//console.log("Ssssssss");
						if(pixel.dead || !body || body.dead) { //can't explode without a body according to the classic creeper anatomy
							//console.log("ss-- oof");
							pixel.hissing = false;
							break;
						};
						if(!pixel.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							pixel.hissStart = pixelTicks;
						};
						//Color code {
							var ticksHissing = pixelTicks - pixel.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								luminance = slBound(luminance + 1.176);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}

						if(pixelTicks - pixel.hissStart > 30) {
							//console.log("GOTTA YEET YEET YEET!");
							//console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`);
							explodeAtPlus(body.x,body.y,explosionRadius,"fire","smoke",null,angelicUpwardVelocity);
							//console.log("Yes, Rico, kaboom.");
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

																	//Bombing Creeper

		elements.bombing_creeper = {
			color: ["#5b8b59", "#3f8738", "#559552", "#479143", "#50b143", "#58c546", "#e83c3c", "#c92a2a", "#f53d3d", "#ad3131"],
			category: "life",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("bombing_creeper_body", pixel.x, pixel.y+1);
					pixel.element = "bombing_creeper_head";
					pixel.color = pixelColorPick(pixel)
				}
				else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("bombing_creeper_head", pixel.x, pixel.y-1);
					pixelMap[pixel.x][pixel.y-1].color = pixel.color;
					pixel.element = "bombing_creeper_body";
					pixel.color = pixelColorPick(pixel)
				}
				else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["bombing_creeper_body","bombing_creeper_head"],
			desc: 'A creeper type from <em>Extra Creeper Types</em> <a href="https://www.curseforge.com/minecraft/mc-mods/extra-creeper-types">(CF)</a>. It spawns more explosives when it explodes.'
		};

		elements.bombing_creeper_body = {
			color: ["#e83c3c", "#c92a2a", "#f53d3d", "#ad3131"],
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","dynamite","gunpowder"],
			breakInto: ["blood","dynamite","dynamite"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
					if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
						var headPixel = pixelMap[pixel.x][pixel.y-2];
						if (headPixel.element == "bombing_creeper_head") {
							if (isEmpty(pixel.x, pixel.y-1)) {
								movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
							}
							else {
								swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
							}
						}
					}
				}
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
					}
					return
				}

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "bombing_creeper_head") {
					var head = pixelMap[pixel.x][pixel.y-1];
					if (head.dead) { // If head is dead, kill body
						pixel.dead = head.dead;
					}
				}
				else { var head = null }

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create blood if decapitated 10% chance
					if (Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y-1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return }
				else if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(head) {
					if(typeof(head.charge) !== "undefined") {
						if(head.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(head.charged) !== "undefined") {
						if(head.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				if(pixel.burning) {
					pixel.hissing = true;
					if(!pixel.hissStart) {
						pixel.hissStart = pixelTicks;
					};
					if(!pixel.burnStart) { //I don't like errors.
						pixel.burnStart = pixel.ticks;
					};
					if(pixelTicks - pixel.burnStart > 30) {
						//console.log("Kaboom?");
						explodeAt(pixel.x,pixel.y,explosionRadius,"fire,dynamite");	//Effect: Places (originally 5) primed TNT when it explodes (i.e. cluster bomb creeper)
																					//Dynamite is the closest thing we have to powder TNT (i.e. good enough)
						//console.log("Yes, Rico, kaboom.");
					};
				};

				//Head hissing color handler: keeps track of head's hissing for coloring purposes
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.dead || !head || head.dead) { //can't hiss without a head according to the classic creeper anatomy
						//console.log("ss-- oof");
						pixel.hissing = false;
						break;
					};
					if(head.hissing) {
						//console.log("Ssssssss");
						if(!head.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							head.hissStart = pixelTicks;
						};

						//Color code {
							var ticksHissing = pixelTicks - head.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								//console.log("the j");
								luminance = slBound(luminance + 1.176);
								//console.log(luminance);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}
					};
				};
			},
		};

		elements.bombing_creeper_head = {
			color: ["#5B8B59", "#3F8738", "#559552", "#479143", "#50B143", "#58C546"],
			category: "life",
			hidden: true,
			density: 1080,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "cooked_meat",
			tempLow: -30,
			stateLow: "frozen_meat",
			burn: 10,
			burnTime: 250,
			burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","dynamite","gunpowder"],
			breakInto: ["blood","dynamite"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.5 }
			},
			properties: {
				dead: false,
				following: false,
				hissing: false,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
						return
					}
				}

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "bombing_creeper_body") {
					var body = pixelMap[pixel.x][pixel.y+1];
					if (body.dead) { // If body is dead, kill head
						pixel.dead = body.dead;
					}
				}
				else { var body = null }

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create blood if severed 10% chance
					if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y+1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(body) {
					if(typeof(body.charge) !== "undefined") {
						if(body.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(body.charged) !== "undefined") {
						if(body.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				var directionAdverb = "left";
				if(pixel.dir > 0) {
					directionAdverb = "right";
				};
				//console.log(`Looking ${directionAdverb}`)
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius"
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) {
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
										break;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break;
								};
							};
						};
					};
				};

				//Pre-explosion handler: keeps track of time before the kaboom
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.hissing) {
						//console.log("Ssssssss");
						if(pixel.dead || !body || body.dead) { //can't explode without a body according to the classic creeper anatomy
							//console.log("ss-- oof");
							pixel.hissing = false;
							break;
						};
						if(!pixel.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							pixel.hissStart = pixelTicks;
						};
						//Color code {
							var ticksHissing = pixelTicks - pixel.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								luminance = slBound(luminance + 1.176);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}

						if(pixelTicks - pixel.hissStart > 30) {
							//console.log("Kaboom?");
							//console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`);
							explodeAt(body.x,body.y,explosionRadius,"fire,dynamite");
							//console.log("Yes, Rico, kaboom.");
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

																	//Hell Creeper

		elements.hell_creeper = {
			color: ["#D2D2D2", "#ff141e", "#fc3232", "#DFAFAF", "#e84a4a", "#ce7979", "#d95555", "#d53c3c", "#c53636", "#b13333", "#913535", "#954242", "#872828", "#8b4949", "#2b0304"],
			category: "life",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("hell_creeper_body", pixel.x, pixel.y+1);
					pixel.element = "hell_creeper_head";
					pixel.color = pixelColorPick(pixel)
				}
				else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("hell_creeper_head", pixel.x, pixel.y-1);
					pixelMap[pixel.x][pixel.y-1].color = pixel.color;
					pixel.element = "hell_creeper_body";
					pixel.color = pixelColorPick(pixel)
				}
				else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["hell_creeper_body","hell_creeper_head"],
			desc: 'A creeper type from <em>Extra Creeper Types</em> <a href="https://www.curseforge.com/minecraft/mc-mods/extra-creeper-types">(CF)</a>. It has a small explosion radius, but spawns a lot of fire around its explosion.'
		};

		elements.hell_creeper_body = {
			color: ["#D2D2D2", "#ff141e", "#fc3232", "#DFAFAF", "#e84a4a", "#ce7979", "#d95555", "#d53c3c", "#c53636", "#b13333", "#913535", "#954242", "#872828", "#8b4949", "#2b0304"],
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 2000, //they are immune to lava, and minecraft's lava is presumably mafic, so at least 1200*C
			stateHigh: "ash",
			breakInto: ["blood","gunpowder","fire"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
					if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
						var headPixel = pixelMap[pixel.x][pixel.y-2];
						if (headPixel.element == "hell_creeper_head") {
							if (isEmpty(pixel.x, pixel.y-1)) {
								movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
							}
							else {
								swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
							}
						}
					}
				}
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
					}
					return
				}

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "hell_creeper_head") {
					var head = pixelMap[pixel.x][pixel.y-1];
					if (head.dead) { // If head is dead, kill body
						pixel.dead = head.dead;
					}
				}
				else { var head = null }

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create blood if decapitated 10% chance
					if (Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y-1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return }
				else if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(head) {
					if(typeof(head.charge) !== "undefined") {
						if(head.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(head.charged) !== "undefined") {
						if(head.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				if(pixel.burning) {
					pixel.hissing = true;
					if(!pixel.hissStart) {
						pixel.hissStart = pixelTicks;
					};
					if(!pixel.burnStart) { //I don't like errors.
						pixel.burnStart = pixel.ticks;
					};
					if(pixelTicks - pixel.burnStart > 30) {
						//console.log("Kaboom?");
						explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","fire",null,hellExplosionFire);
						//console.log("Yes, Rico, kaboom.");
					};
				};

				//Head hissing color handler: keeps track of head's hissing for coloring purposes
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.dead || !head || head.dead) { //can't hiss without a head according to the classic creeper anatomy
						//console.log("ss-- oof");
						pixel.hissing = false;
						break;
					};
					if(head.hissing) {
						//console.log("Ssssssss");
						if(!head.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							head.hissStart = pixelTicks;
						};

						//Color code {
							var ticksHissing = pixelTicks - head.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								//console.log("the j");
								luminance = slBound(luminance + 1.176);
								//console.log(luminance);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}
					};
				};
			},
		};

		elements.hell_creeper_head = {
			color: ["#D2D2D2", "#ff141e", "#fc3232", "#e84a4a", "#b13333", "#913535", "#954242", "#872828", "#8b4949", "#2b0304", "#111111", "#faae3c", "#f5e131"],
			category: "life",
			hidden: true,
			density: 1080,
			state: "solid",
			conduct: 25,
			tempHigh: 2000,
			stateHigh: "ash",
			tempLow: -30,
			stateLow: "frozen_meat",
			breakInto: ["blood","fire"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 },
				"oxygen": { "elem2":"carbon_dioxide", "chance":0.5 }
			},
			properties: {
				dead: false,
				following: false,
				hissing: false,
				charged: false,
				didChargeBlueTinted: false
			},
			movable: true,
			tick: function(pixel) {
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into rotten_meat if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
						return
					}
				}

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "hell_creeper_body") {
					var body = pixelMap[pixel.x][pixel.y+1];
					if (body.dead) { // If body is dead, kill head
						pixel.dead = body.dead;
					}
				}
				else { var body = null }

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create blood if severed 10% chance
					if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y+1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				if(pixel.charge) {
					pixel.charged = true;
				};

				if(body) {
					if(typeof(body.charge) !== "undefined") {
						if(body.charge) {
							pixel.charged = true;
						};
					};
					if(typeof(body.charged) !== "undefined") {
						if(body.charged) {
							pixel.charged = true;
						};
					};
				};

				if(typeof(pixel.charged) === "undefined") {
					pixel.charged = false;
				};

				if(pixel.charged) {
					var explosionRadius = 10;
					if(!pixel.didChargeBlueTinted) { //do once, on initial charge
						//console.log("something something halsey lyric");
						var color = pixel.color;
						if(color.startsWith("rgb")) {
							//console.log("rgb detected");
							color = color.split(","); //split color for addition
							var red = parseFloat(color[0].substring(4));
							var green = parseFloat(color[1]);
							var blue = parseFloat(color[2].slice(0,-1));
							red = rgbColorBound(red + 51);
							green = rgbColorBound(green + 51);
							blue = rgbColorBound(blue + 102);
							color = `rgb(${red},${green},${blue})`;
							pixel.color = color;
							//console.log("color set");
						} else if(color.startsWith("hsl")) {
							//console.log("hsl detected");
							color = color.split(","); //split color for addition
							var hue = parseFloat(color[0].substring(4));
							var saturation = parseFloat(color[1].slice(0,-1));
							var luminance = parseFloat(color[2].slice(0,-2));
							hue = hue % 360; //piecewise hue shift
							if(hue <= 235 && hue >= 135) {
								hue = 185;
							} else if(hue < 135) {
								hue += 50;
							} else if(hue > 235 && hue < 360) {
								hue -= 50;
							};
							saturation = slBound (saturation + 10);
							luminance = slBound(luminance + 20);
							color = `hsl(${hue},${saturation}%,${luminance}%)`;
							pixel.color = color;
							//console.log("color set");
						};
						pixel.didChargeBlueTinted = true;
					};
				} else {
					var explosionRadius = 7;
				};

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				var directionAdverb = "left";
				if(pixel.dir > 0) {
					directionAdverb = "right";
				};
				//console.log(`Looking ${directionAdverb}`)
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius"
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										//console.log(`Human detected at (${nX},${nY})`)
										//Start "hissing" if a human is close enough
										if(coordPyth(pX,pY,nX,nY) <= 3.15) {
											pixel.hissing = true;
											if(!pixel.hissStart) {
												pixel.hissStart = pixelTicks;
											};
										};
										break;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break;
								};
							};
						};
					};
				};

				//Pre-explosion handler: keeps track of time before the kaboom
				for(i = 0; i < 1; i++) { //dummy for loop
					if(pixel.hissing) {
						//console.log("Ssssssss");
						if(pixel.dead || !body || body.dead) { //can't explode without a body according to the classic creeper anatomy
							//console.log("ss-- oof");
							pixel.hissing = false;
							break;
						};
						if(!pixel.hissStart) {
							//console.log("t-30 ticks or whatever it was");
							pixel.hissStart = pixelTicks;
						};
						//Color code {
							var ticksHissing = pixelTicks - pixel.hissStart;
							var color = pixel.color; //do on each hissing tick
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + ticksHissing);
								green = rgbColorBound(green + ticksHissing);
								blue = rgbColorBound(blue + ticksHissing);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								luminance = slBound(luminance + 1.176);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
						//}

						if(pixelTicks - pixel.hissStart > 30) {
							//console.log("Kaboom?");
							//console.log(`Exploding with radius ${explosionRadius} (charged: ${pixel.charged})`);
							explodeAtPlus(pixel.x,pixel.y,explosionRadius,"fire","fire",null,hellExplosionFire);
							//console.log("Yes, Rico, kaboom.");
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

		/* +-----------------------------------+
		   | Nothing There					 |
		   |								   |
		   | amogus							|
		   |								   |
		   | red imposter					  |
		   |								   |
		   |								   |
		   |								   |
		   |								   |
		   |								   |
		   |								   |
		   |								   |
		   +-----------------------------------+ */

		elements.nothing_there_bullet = {
			flippableX: true,
			movable: true,
			density: 10000,
			desc: "A hypersonic bullet made of Nothing There's flesh. I don't remember if it can turn humans into red clouds.",
			color: "#a3281a",
			related: ["nothing_there_phase_3_body","nothing_there_phase_3_head"],
			movable: true,
			tick: function(pixel) {
				if(typeof(pixel.flipX) == undefined) {
					pixel.flipX = !!Math.floor(Math.random() * 2);
				};
				var dir = pixel.flipX ? -1 : 1;
				for(i = 0; i < 6; i++) {
					if(outOfBounds(pixel.x+dir,pixel.y)) {
						deletePixel(pixel.x,pixel.y);
						break;
					};
					if(!nothingThereBulletMovement(pixel,pixel.x+dir,pixel.y)) {
						return true;
					};
				};
			},
		};

		elements.nothing_there_mace = {
			movable: true,
			density: 10000,
			desc: "A spiky mace attached to Nothing There, which can turn humans into red clouds.",
			color: "#fa4632",
			properties: {
				counter: 2
			},
			related: ["nothing_there_phase_3_body","nothing_there_phase_3_head"],
			movable: true,
			tick: function(pixel) {
				if(outOfBounds(pixel.x,pixel.y + 1)) {
					deletePixel(pixel.x,pixel.y);
					return false;
				};
				if(!tryMove(pixel,pixel.x,pixel.y + 1)) {
					var newPixel = pixelMap[pixel.x][pixel.y + 1];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					if(newElement !== pixel.element) {
						if(newInfo.state === "gas") {
							swapPixels(pixel,newPixel);
						} else {
							if(pixel.counter > 0) {
								explodeAtPlus(pixel.x,pixel.y + 1,5,null,null);
								pixel.counter--;
							} else {
								deletePixel(pixel.x,pixel.y);
								return true;
							};
						};
					};
				};
			},
		};

		elements.nothing_there_cleaver = {
			movable: true,
			density: 10000,
			desc: "A very sharp blade attached to Nothing There, which can turn humans into red clouds.",
			color: "#a33c3c",
			properties: {
				counter: 4
			},
			related: ["nothing_there_phase_3_body","nothing_there_phase_3_head"],
			movable: true,
			tick: function(pixel) {
				if(outOfBounds(pixel.x,pixel.y + 1)) {
					deletePixel(pixel.x,pixel.y);
					return false;
				};
				if(!tryMove(pixel,pixel.x,pixel.y + 1)) {
					var newPixel = pixelMap[pixel.x][pixel.y + 1];
					var newElement = newPixel.element;
					var newInfo = elements[newElement];
					if(!nothingThereBulletExcludedElements.includes(newElement)) {
						if(pixel.counter > 0) {
							swapPixels(pixel,newPixel);
							breakPixel(newPixel,false,false);
							pixel.counter--;
						} else {
							deletePixel(pixel.x,pixel.y);
							return true;
						};
					} else {
						deletePixel(pixel.x,pixel.y);
						return false;
					};
				};
			},
		};

		testSwapArray = ["meat","cooked_meat","rotten_meat","blood","infection","antibody","plague","zombie_blood","frozen_meat","frozen_rotten_meat"];

		elements.nothing_there_phase_1 = {
			color: "#faacac",
			category: "life",
			density: 2000,
			desc: "O-06-20 <span style='color: red;'>(ALEPH)</span><br/>In this phase, it looks like a dog made of misshapen human parts. It can easily turn humans into unrecognizable messes.",
			state: "solid",
			tempHigh: 3000,
			hardness: 0.995,
			stateHigh: "cooked_meat",
			burn: 1,
			burnTime: 250000,
			burnInto: "cooked_meat",
			breakInto: ["blood","meat","magic"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.00002 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.00004 },
				"plague": { "elem1":"plague", "chance":0.000003 }
			},
			related: ["nothing_there_phase_2", "nothing_there_phase_3_body", "nothing_there_phase_3_head"],
			properties: {
				dead: false,
				dir: 1,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				var pixelBreakInto = elements[pixel.element].breakInto;
				tryMove(pixel, pixel.x, pixel.y+1); // Fall
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Break if pixelTicks-dead > 5
					if (pixelTicks-pixel.dead > 5) {
						changePixel(pixel,pixelBreakInto[Math.floor(Math.random() * pixelBreakInto.length)],false);
					};
					return;
				};

				if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],	//dash move
						[1*pixel.dir,-1],	//cleave move
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if(tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
							break;
						} else { //move through given pixels
							if(!isEmpty(pixel.x+move[0], pixel.y+move[1], true)) {
								var blockingPixel = pixelMap[pixel.x+move[0]][pixel.y+move[1]];
								//console.log(blockingPixel);
								var blockingElement = blockingPixel.element;
								if(testSwapArray.includes(blockingElement)) {
									swapPixels(pixel,blockingPixel);
									break;
								};
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!pixel.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				var pX = pixel.x;
				var pY = pixel.y;

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				if(pixelTicks % 2 == 0 && !pixel.dead) { //reduce rate for performance
					/*var directionAdverb = "left";
					if(pixel.dir > 0) {
						directionAdverb = "right";
					};*/
					//console.log(`Looking ${directionAdverb}`)
					if(pixel.dir === -1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = (-1); j > (-35 - 1); j--) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/4) {	//One-fourth chance to change to blood
													changePixel(newPixel,"blood",false);
												} else {					//Remaining 3/4 chance to change to meat
													changePixel(newPixel,"meat",false);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/4) {	//One-fourth chance to change to blood
												changePixel(newPixel,"blood",false);
											} else {					//Remaining 3/4 chance to change to meat
												changePixel(newPixel,"meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					} else if(pixel.dir === 1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = 1; j < 35 + 1; j++) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/4) {	//One-fourth chance to change to blood
													changePixel(newPixel,"blood",false);
												} else {					//Remaining 3/4 chance to change to meat
													changePixel(newPixel,"meat",false);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/4) {	//One-fourth chance to change to blood
												changePixel(newPixel,"blood",false);
											} else {					//Remaining 3/4 chance to change to meat
												changePixel(newPixel,"meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					};
				};

				if(pixelTicks - pixel.start > 300 && (Math.random() < 0.003)) {
					var dir = pixel.dir;
					changePixel(pixel,"nothing_there_phase_2",false);
					pixel.dir = dir;
				};

				//End
			},
		};

		elements.nothing_there_phase_2 = {
			behavior: behaviors.POWDER_OLD,
			color: "#d90b0b",
			category: "life",
			density: 4000,
			desc: "O-06-20 <span style='color: red;'>(ALEPH)</span><br/>In this phase, it looks like a red, fibrous cocoon. It will soon hatch into its third phase.",
			state: "solid",
			tempHigh: 3500,
			hardness: 0.999,
			stateHigh: "cooked_meat",
			burn: 1,
			burnTime: 350000,
			burnInto: "cooked_meat",
			breakInto: ["blood","meat","magic"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.000001 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.000001 },
				"plague": { "elem1":"plague", "chance":0.000001 }
			},
			related: ["nothing_there_phase_1", "nothing_there_phase_3_body", "nothing_there_phase_3_head"],
			properties: {
				dead: false,
				dir: 1,
				timer: 0
			},
			movable: true,
			tick: function(pixel) {
				var pixelBreakInto = elements[pixel.element].breakInto;
						doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Break if pixelTicks-dead > 5
					if (pixelTicks-pixel.dead > 5) {
						changePixel(pixel,pixelBreakInto[Math.floor(Math.random() * pixelBreakInto.length)],false);
					};
					return;
				};

				if(pixelTicks - pixel.start > 300) {
					var dir = pixel.dir;
					if (isEmpty(pixel.x, pixel.y+1)) {
						createPixel("nothing_there_phase_3_body", pixel.x, pixel.y+1);
						pixel.element = "nothing_there_phase_3_head";
						pixel.color = pixelColorPick(pixel)
						pixelMap[pixel.x][pixel.y+1].dir = dir;
					}
					else if (isEmpty(pixel.x, pixel.y-1)) {
						createPixel("nothing_there_phase_3_head", pixel.x, pixel.y-1);
						pixelMap[pixel.x][pixel.y-1].color = pixel.color;
						pixel.element = "nothing_there_phase_3_body";
						pixel.color = pixelColorPick(pixel)
						pixel.dir = dir;
					};
				};

				//End
			},
		};

		elements.nothing_there_phase_3 = {
			color: "#fc1e35",
			category: "life",
			desc: "Spawns Nothing There in its humanoid third phase, for when you don't want to wait for it to go through the other phases.",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("nothing_there_phase_3_body", pixel.x, pixel.y+1);
					pixel.element = "nothing_there_phase_3_head";
					pixel.color = pixelColorPick(pixel)
				} else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("nothing_there_phase_3_head", pixel.x, pixel.y-1);
					pixel.element = "nothing_there_phase_3_body";
					pixel.color = pixelColorPick(pixel)
				} else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["nothing_there_phase_3_body","nothing_there_phase_3_head"],
		};

		elements.nothing_there_phase_3_body = {
			color: "#fc1e35",
			category: "life",
			density: 3000,
			desc: "O-06-20 <span style='color: red;'>(ALEPH)</span><br/>In this phase, it looks like a humanoid made of misarranged flesh. It is almost indestructible and has a variety of ways to destroy your canvas and annihilate any humans inside of it.<br/>Let's hope it doesn't learn to blend in and walk among us.",
			state: "solid",
			tempHigh: 3000,
			hardness: 0.9975,
			hidden: true,
			stateHigh: "cooked_meat",
			burn: 1,
			burnTime: 300000,
			burnInto: "cooked_meat",
			breakInto: ["blood","meat","magic"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.00001 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.00002 },
				"plague": { "elem1":"plague", "chance":0.0000015 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			related: ["nothing_there_phase_1", "nothing_there_phase_2", "nothing_there_mace", "nothing_there_cleaver", "nothing_there_bullet"],
			tick: function(pixel) {
				var pixelBreakInto = elements[pixel.element].breakInto;

				if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
					if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
						var headPixel = pixelMap[pixel.x][pixel.y-2];
						if (headPixel.element == "nothing_there_phase_3_head") {
							if (isEmpty(pixel.x, pixel.y-1)) {
								movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
							}
							else {
								swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
							}
						}
					}
				}
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Break if pixelTicks-dead > 5
					if (pixelTicks-pixel.dead > 5) {
						changePixel(pixel,pixelBreakInto[Math.floor(Math.random() * pixelBreakInto.length)],false);
					};
					return;
				};

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true)) {
					if(pixelMap[pixel.x][pixel.y-1].element == "nothing_there_phase_3_head") {
						var head = pixelMap[pixel.x][pixel.y-1];
						if (head.dead) { // If head is dead, kill body
							pixel.dead = head.dead;
						};
					} else {
						var head = null;
					};
				} else { var head = null };

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create blood if decapitated 30% chance
					if (Math.random() < 0.3) {
						createPixel("blood", pixel.x, pixel.y-1);
						// set dead to true 10% chance
						if (Math.random() < 0.1) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return } //do not proceed if headless
				else if (Math.random() < 0.08) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};
			},
		};

		elements.nothing_there_phase_3_head = {
			color: "#ff3046",
			category: "life",
			density: 3000,
			desc: "O-06-20 <span style='color: red;'>(ALEPH)</span><br/>In this phase, it looks like a humanoid made of misarranged flesh. It is almost indestructible and has a variety of ways to destroy your canvas and annihilate any humans inside of it.<br/>Let's hope it doesn't learn to blend in and walk among us.",
			state: "solid",
			tempHigh: 3000,
			hardness: 0.9975,
			hidden: true,
			stateHigh: "cooked_meat",
			burn: 1,
			burnTime: 300000,
			burnInto: "cooked_meat",
			breakInto: ["blood","meat","magic"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.00001 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.00002 },
				"plague": { "elem1":"plague", "chance":0.0000015 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			related: ["nothing_there_phase_1", "nothing_there_phase_2", "nothing_there_mace", "nothing_there_cleaver", , "nothing_there_bullet"],
			tick: function(pixel) {
				var pixelBreakInto = elements[pixel.element].breakInto;
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Break if pixelTicks-dead > 5
					if (pixelTicks-pixel.dead > 5) {
						changePixel(pixel,pixelBreakInto[Math.floor(Math.random() * pixelBreakInto.length)],false);
					};
					return;
				};

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true)) {
					if(pixelMap[pixel.x][pixel.y+1].element == "nothing_there_phase_3_body") {
						var body = pixelMap[pixel.x][pixel.y+1];
						if (body.dead) { // If body is dead, kill body
							pixel.dead = body.dead;
						};
					} else {
						var body = null;
					};
				} else { var body = null };

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create blood if severed 30% chance
					if (isEmpty(pixel.x, pixel.y+1) && Math.random() < 0.3) {
						createPixel("blood", pixel.x, pixel.y+1);
						// set dead to true 10% chance
						if (Math.random() < 0.10) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				//Human detection loop
				if(pixelTicks % 2 == 0 && !pixel.dead) { //reduce rate for performance
					/*var directionAdverb = "left";
					if(pixel.dir > 0) {
						directionAdverb = "right";
					};*/
					//console.log(`Looking ${directionAdverb}`)
					if(pixel.dir === -1) {
						//do action every 40 ticks
						var bulletPositions = [[-1, -1], [-1, 0]];
						var bulletPosition = bulletPositions[Math.floor(Math.random() * 2)];

						var smashPosition = [-1, -1];

						var cleavePositions = [[-1, -1], [-2, -1], [-3, -1]];

						var start = 2 * Math.floor(pixel.start/2);
						if((pixelTicks - start) % 40 == 0) {
							var action = Math.floor(Math.random() * 3);
							if(action == 0) { //bullet
								var bX = pX + bulletPosition[0];
								var bY = pY + bulletPosition[1];

								if(!outOfBounds(bX,bY)) {
									if(isEmpty(bX,bY)) {
										createPixel("nothing_there_bullet",bX,bY);
										pixelMap[bX][bY].flipX = true;
									} else {
										if(!nothingThereBulletExcludedElements.includes(pixelMap[bX][bY].element)) {
											deletePixel(bX,bY);
											createPixel("nothing_there_bullet",bX,bY);
											pixelMap[bX][bY].flipX = true;
										};
									};
								};
							} else if(action == 1) { //smash
								var sX = pX + smashPosition[0];
								var sY = pY + smashPosition[1];

								if(!outOfBounds(sX,sY)) {
									if(isEmpty(sX,sY)) {
										createPixel("nothing_there_mace",sX,sY);
									} else {
										if(!nothingThereBulletExcludedElements.includes(pixelMap[sX][sY].element)) {
											deletePixel(sX,sY);
											createPixel("nothing_there_mace",sX,sY);
										};
									};
								};
							} else if(action == 2) { //cleave
								for(cleaverIndex = 0; cleaverIndex < cleavePositions.length; cleaverIndex++) {
									var cX = pX + cleavePositions[cleaverIndex][0];
									var cY = pY + cleavePositions[cleaverIndex][1];

									if(!outOfBounds(cX,cY)) {
										if(isEmpty(cX,cY)) {
											createPixel("nothing_there_cleaver",cX,cY);
										} else {
											if(!nothingThereBulletExcludedElements.includes(pixelMap[cX][cY].element)) {
												deletePixel(cX,cY);
												createPixel("nothing_there_cleaver",cX,cY);
											};
										};
									};
								};
							};
						};

						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = (-1); j > (-35 - 1); j--) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/4) {	//One-fourth chance to change to blood
													changePixel(newPixel,"blood",false);
												} else {					//Remaining 3/4 chance to change to meat
													changePixel(newPixel,"meat",false);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/4) {	//One-fourth chance to change to blood
												changePixel(newPixel,"blood",false);
											} else {					//Remaining 3/4 chance to change to meat
												changePixel(newPixel,"meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					} else if(pixel.dir === 1) {
						//do action every 40 ticks
						var bulletPositions = [[1, -1], [1, 0]];
						var bulletPosition = bulletPositions[Math.floor(Math.random() * 2)];

						var smashPosition = [1, -1];

						var cleavePositions = [[1, -1], [2, -1], [3, -1]];

						var start = 2 * Math.floor(pixel.start/2);
						if((pixelTicks - start) % 40 == 0) {
							var action = Math.floor(Math.random() * 3);
							if(action == 0) { //bullet
								var bX = pX + bulletPosition[0];
								var bY = pY + bulletPosition[1];

								if(!outOfBounds(bX,bY)) {
									if(isEmpty(bX,bY)) {
										createPixel("nothing_there_bullet",bX,bY);
										pixelMap[bX][bY].flipX = false;
									} else {
										if(!nothingThereBulletExcludedElements.includes(pixelMap[bX][bY].element)) {
											deletePixel(bX,bY);
											createPixel("nothing_there_bullet",bX,bY);
											pixelMap[bX][bY].flipX = false;
										};
									};
								};
							} else if(action == 1) { //smash
								var sX = pX + smashPosition[0];
								var sY = pY + smashPosition[1];

								if(!outOfBounds(sX,sY)) {
									if(isEmpty(sX,sY)) {
										createPixel("nothing_there_mace",sX,sY);
									} else {
										if(!nothingThereBulletExcludedElements.includes(pixelMap[sX][sY].element)) {
											deletePixel(sX,sY);
											createPixel("nothing_there_mace",sX,sY);
										};
									};
								};
							} else if(action == 2) { //cleave
								for(cleaverIndex = 0; cleaverIndex < cleavePositions.length; cleaverIndex++) {
									var cX = pX + cleavePositions[cleaverIndex][0];
									var cY = pY + cleavePositions[cleaverIndex][1];

									if(!outOfBounds(cX,cY)) {
										if(isEmpty(cX,cY)) {
											createPixel("nothing_there_cleaver",cX,cY);
										} else {
											if(!nothingThereBulletExcludedElements.includes(pixelMap[cX][cY].element)) {
												deletePixel(cX,cY);
												createPixel("nothing_there_cleaver",cX,cY);
											};
										};
									};
								};
							};
						};

						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = 1; j < 35 + 1; j++) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) { //If not dead
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Infect/kill if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 1.5) { //approx. sqrt(2)
												if(Math.random() < 1/4) {	//One-fourth chance to change to blood
													changePixel(newPixel,"blood",false);
												} else {					//Remaining 3/4 chance to change to meat
													changePixel(newPixel,"meat",false);
												};
											};
										} else { //Mutilate if dead
											if(Math.random() < 1/4) {	//One-fourth chance to change to blood
												changePixel(newPixel,"blood",false);
											} else {					//Remaining 3/4 chance to change to meat
												changePixel(newPixel,"meat",false);
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

		runAfterLoad(function() {
			if(typeof(badPixels) === "object") {
				badPixels.nothing_there_phase_1 = { panicIncrease: 1, panicIncreaseChance: 1 } //insta-panic for "aleph" thing and "level 1" humans
				badPixels.nothing_there_phase_2 = { panicIncrease: 1, panicIncreaseChance: 1 }
				badPixels.nothing_there_phase_3_body = { panicIncrease: 1, panicIncreaseChance: 1 }
				badPixels.nothing_there_phase_3_head = { panicIncrease: 1, panicIncreaseChance: 1 }
			}
		});

		/* +-----------------------------------+
		   | End Nothing There elements		|
		   |								   |
		   |								   |
		   |								   |
		   |								   |
		   |								   |
		   +-----------------------------------+ */

		/* +++++++++++++++++++++++++++
		   + Start skeleton elements +
		   +++++++++++++++++++++++++++ */

		arrowExcludedElements = ["wall"];

		arrowPlacementExcludedElements = ["skeleton_head", "skeleton_body", "arrow", "wall"];

		elements.rock.hardness = 0.55;

		elements.arrow = {
			cooldown: 2,
			flippableX: true,
			movable: true,
			properties: {
				flipY: false,
				speed: 5,
				fall: 0,
				attached: false,
				attachOffsets: [null, null],
				penetrateCounter: 7
			},
			density: 2471,
			color: "#cacdcf",
			related: ["skeleton_body","skeleton_head"],
			movable: true,
			burn: 20,
			//burnInto: "flint",
			burnInto: "rock",
			burnTime: 250,
			breakInto: ["gravel","gravel","sawdust","feather"],
			tick: function(pixel) {
				if(pixel.attachOffsets.includes(null)) {
					pixel.attached = false;
				};
				if(pixel.attached) {
					var attachCoords = [pixel.x+pixel.attachOffsets[0], pixel.y+pixel.attachOffsets[1]];
					var attachX = pixel.x + pixel.attachOffsets[0];
					var attachY = pixel.y + pixel.attachOffsets[1];
					if(isEmpty(attachX,attachY,true)) {
						pixel.attached = false;
					} else {
						var attachPixel = pixelMap[attachX][attachY];
						var attachInfo = elements[attachPixel.element];
						var attachState = "solid";
						if(typeof(attachInfo.state) === "string") {
							attachState = attachInfo.state;
						};
						var attachBlacklistStates = ["liquid","gas"];
						if(attachBlacklistStates.includes(attachState)) {
							pixel.attached = false;
						};
					};
				} else { //Move if not attached
					var speedForBreakMult = pythSpeed(pixel.speed,pixel.y);
					var breakMult = speedForBreakMult/5;

					if(typeof(pixel.flipX) == undefined) {
						pixel.flipX = !!Math.floor(Math.random() * 2);
					};
					var dir = pixel.flipX ? -1 : 1;
					if(Math.random() < (1/(pixel.speed**1.585))) { //1/0 is Infinity in JavaScript, so this should always be true at 0 speed)
						pixel.fall++;
					};
					//Horizontal movement
					for(i = 0; i < pixel.speed; i++) {
						if(outOfBounds(pixel.x+dir,pixel.y)) {
							deletePixel(pixel.x,pixel.y);
							break;
						};
						if(!isEmpty(pixel.x+dir,pixel.y,true)) {
							var otherPixel = pixelMap[pixel.x+dir][pixel.y];
							var otherElement = otherPixel.element;
							var otherInfo = elements[otherElement];
							if(arrowExcludedElements.includes(otherElement)) {
								pixel.attached = true; //attach
								pixel.speed = 0;
								pixel.fall = 0;
								pixel.attachOffsets = [dir, 0];
								break;
							};
							var otherDensity = (typeof(otherInfo.density) === "undefined" ? 1000 : otherInfo.density);
							var swapChance = 1 - Math.max(0,(otherDensity / 2471));
							if(Math.random() < swapChance && pixel.penetrateCounter > 0) {
								swapPixels(pixel,otherPixel);
								arrowAltTb(otherPixel,breakMult);
								pixel.speed = Math.max(0,--pixel.speed);
								pixel.penetrateCounter--;
							} else {
								if(!arrowAltTb(otherPixel,breakMult)) { //if this didn't break it
									pixel.attached = true; //attach
									pixel.speed = 0;
									pixel.fall = 0;
									pixel.attachOffsets = [dir, 0];
								};
							};
							break;
						} else {
							tryMove(pixel,pixel.x+dir,pixel.y);
						};
					};
					if(Math.random() < 0.1) {
						pixel.speed = Math.max(0,--pixel.speed);
					};

					var dirY = 1;
					//Vertical movement
					if(typeof(pixel.flipY) !== "undefined") {
						if(pixel.flipY) {
							pixel.dirY = -1;
						};
					};

					for(j = 0; j < pixel.fall; j++) {
						if(outOfBounds(pixel.x,pixel.y+dirY)) {
							deletePixel(pixel.x,pixel.y);
							break;
						};
						if(!isEmpty(pixel.x,pixel.y+dirY,true)) {
							var otherPixel = pixelMap[pixel.x][pixel.y+dirY];
							var otherElement = otherPixel.element;
							var otherInfo = elements[otherElement];
							if(arrowExcludedElements.includes(otherElement)) {
								pixel.attached = true; //attach
								pixel.speed = 0;
								pixel.fall = 0;
								pixel.attachOffsets = [0, dirY];
								break;
							};
							var otherDensity = (typeof(otherInfo.density) === "undefined" ? 1000 : otherInfo.density);
							var swapChance = 1 - Math.max(0,(otherDensity / 2471));
							if(Math.random() < swapChance && pixel.penetrateCounter > 0) {
								swapPixels(pixel,otherPixel);
								arrowAltTb(otherPixel,breakMult);
								pixel.speed = Math.max(0,--pixel.speed);
								pixel.penetrateCounter--;
							} else {
								if(!arrowAltTb(otherPixel,breakMult)) { //if this didn't break it
									pixel.attached = true; //attach
									pixel.speed = 0;
									pixel.fall = 0;
									pixel.attachOffsets = [0, dirY];
								};
							};
							break;
						} else {
							tryMove(pixel,pixel.x,pixel.y+dirY);
						};
					};

					//End
				};
			},
		};

		elements.skeleton = {
			color: ["#ebebe6", "#cfcfc8"],
			category: "life",
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				following: false
			},
			movable: true,
			tick: function(pixel) {
				if (isEmpty(pixel.x, pixel.y+1)) {
					createPixel("skeleton_body", pixel.x, pixel.y+1);
					pixel.element = "skeleton_head";
					pixel.color = pixelColorPick(pixel)
				}
				else if (isEmpty(pixel.x, pixel.y-1)) {
					createPixel("skeleton_head", pixel.x, pixel.y-1);
					pixelMap[pixel.x][pixel.y-1].color = pixel.color;
					pixel.element = "skeleton_body";
					pixel.color = pixelColorPick(pixel)
				}
				else {
					deletePixel(pixel.x, pixel.y);
				}
			},
			related: ["skeleton_body","skeleton_head"],
			desc: "<span class=\"skeletonStatus\">If this text is green or underlined, skeletons can spawn.</span> <span onclick=toggleSkeletonSpawning() style=\"color: #ff00ff;\";>Click here</span> to toggle skeleton spawning. If it's on, skeletons (all types) can spawn through random events."
		};

		elements.skeleton_body = {
			color: "#ebebe6",
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "bone",
			burn: 10,
			burnTime: 250,
			burnInto: ["bone","ash","arrow"],
			hardness: 0.55,
			breakInto: ["bone","bone","bone","bone_marrow"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0,
				chargeCounter: 20,
				shooting: false
			},
			movable: true,
			tick: function(pixel) {
				if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
					if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
						var headPixel = pixelMap[pixel.x][pixel.y-2];
						if (headPixel.element == "skeleton_head") {
							if (isEmpty(pixel.x, pixel.y-1)) {
								movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
							}
							else {
								swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
							}
						}
					}
				}
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into bone if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						changePixel(pixel,"bone");
					}
					return
				}

				// Find the head
				if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "skeleton_head") {
					var head = pixelMap[pixel.x][pixel.y-1];
					if (head.dead) { // If head is dead, kill body
						pixel.dead = head.dead;
					}
				}
				else { var head = null }

				if (isEmpty(pixel.x, pixel.y-1)) {
					// create blood if decapitated 10% chance (bone marrow)
					if (Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y-1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}
				else if (head == null) { return }
				else if (Math.random() < 0.1) { // Move 10% chance
					var movesToTry = [
						[1*pixel.dir,0],
						[1*pixel.dir,-1],
					];
					// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
					while (movesToTry.length > 0) {
						var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
						if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
							if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
								movePixel(head, head.x+move[0], head.y+move[1]);
								break;
							};
						};
					};
					// 15% chance to change direction while not chasing a human
					if(!head.following) {
						if (Math.random() < 0.15) {
							pixel.dir *= -1;
							//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
						};
					}/* else {
						//console.log("*chases cutely*");
					};*/
				};

				if(pixel.shooting) {
					if(pixel.chargeCounter <= 0) {
						var bX = pixel.x + pixel.dir;
						var bY = pixel.y - 1;
						var arrowFlipX = null;
						if(pixel.dir < 0) {
							arrowFlipX = true;
						} else if(pixel.dir > 0) {
							arrowFlipX = false;
						};
						if(!outOfBounds(bX,bY)) {
							if(isEmpty(bX,bY)) {
								createPixel("arrow",bX,bY);
								pixelMap[bX][bY].flipX = arrowFlipX;
							} else {
								if(!arrowExcludedElements.includes(pixelMap[bX][bY].element) && !arrowPlacementExcludedElements.includes(pixelMap[bX][bY].element)) {
									deletePixel(bX,bY);
									createPixel("arrow",bX,bY);
									pixelMap[bX][bY].flipX = arrowFlipX;
								};
							};
						};
						pixel.chargeCounter = 20;
					};
					if(pixel.chargeCounter > 0) {
						pixel.chargeCounter--;
					};
				};
			},
		};

		elements.skeleton_head = {
			color: ["#ebebe6", "#cfcfc8"],
			category: "life",
			hidden: true,
			density: 1500,
			state: "solid",
			conduct: 25,
			tempHigh: 250,
			stateHigh: "bone",
			burn: 10,
			burnTime: 250,
			burnInto: ["bone","ash","arrow"],
			hardness: 0.55,
			breakInto: ["bone","bone","bone","bone_marrow"],
			reactions: {
				"cancer": { "elem1":"cancer", "chance":0.005 },
				"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
				"plague": { "elem1":"plague", "chance":0.05 }
			},
			properties: {
				dead: false,
				dir: 1,
				panic: 0
			},
			movable: true,
			tick: function(pixel) {
				doHeat(pixel);
				doBurning(pixel);
				doElectricity(pixel);
				if (pixel.dead) {
					// Turn into bone if pixelTicks-dead > 500
					if (pixelTicks-pixel.dead > 200) {
						changePixel(pixel,"bone");
					}
					return
				}

				// Find the body
				if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "skeleton_body") {
					var body = pixelMap[pixel.x][pixel.y+1];
					if (body.dead) { // If body is dead, kill head
						pixel.dead = body.dead;
					}
				}
				else { var body = null }

				if(body) {
					if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
						pixel.dir = body.dir;
					};
				};

				if (isEmpty(pixel.x, pixel.y+1)) {
					tryMove(pixel, pixel.x, pixel.y+1);
					// create blood if severed 10% chance
					if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
						createPixel("blood", pixel.x, pixel.y+1);
						// set dead to true 15% chance
						if (Math.random() < 0.15) {
							pixel.dead = pixelTicks;
						}
					}
				}

				//start of most new code
				var pX = pixel.x;
				var pY = pixel.y;

				//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
				var directionAdverb = "left";
				if(pixel.dir > 0) {
					directionAdverb = "right";
				};
				//console.log(`Looking ${directionAdverb}`)
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										if(body) body.shooting = true;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(enemyHumanoidArray.includes(newElement)) {
									//console.log(`Human part found at (${nX},${nY})`)
									if(!newPixel.dead) {
										pixel.following = true;
										if(body) body.shooting = true;
									};
								} else {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
									break;
								};
							};
						};
					};
				};

				if(Math.random() < 0.01) { //1% chance each tick to lose interest
					pixel.following = false;
					//console.log("Meh.");
				};
			},
		};

		/* -------------------------
		   - End skeleton elements -
		   ------------------------- */
		mobsLoaded = true;

	//CHANGES TO HUMANS ##

		function hasPixel(x,y,elementInput) {
			if(isEmpty(x,y,true)) { //if empty, it can't have a pixel
				return false;
			} else {
				if(elementInput.includes(",")) { //CSTA
					elementInput = elementInput.split(",");
				};
				if(Array.isArray(elementInput)) { //if element list
					return elementInput.includes(pixelMap[x][y].element);
				} else { //if single element
					return pixelMap[x][y].element === elementInput;
				};
			};		
		};

		elements.brain = {
			color: ["#fce3e3","#deb6c5","#f5ced5","#e87b8f"],
			behavior: [
				"XX|XX|XX",
				"XX|CH:rotten_meat%1|XX",
				"M2|M1|M2",
			],
			reactions: {
				"dirty_water": { "elem1":"rotten_meat", "chance":0.1 },
				"fly": { "elem1":"rotten_meat", "chance":0.2 },
				"dioxin": { "elem1":"rotten_meat", "elem2":null, "chance":0.1 },
				"uranium": { "elem1":"rotten_meat", "chance":0.1 },
				"cancer": { "elem1":"rotten_meat", "chance":0.1 },
				"plague": { "elem1":"rotten_meat", "elem2":null, "chance":0.3 },
				"ant": { "elem1":"rotten_meat", "chance":0.1 },
				"worm": { "elem1":"rotten_meat", "chance":0.1 },
				"rat": { "elem1":"rotten_meat", "chance":0.3 },
				"mushroom_spore": { "elem1":"rotten_meat", "chance":0.1 },
				"mushroom_stalk": { "elem1":"rotten_meat", "chance":0.1 },
				"mercury": { "elem1":"rotten_meat", "elem2":null, "chance":0.2 },
				"mercury_gas": { "elem1":"rotten_meat", "elem2":null, "chance":0.1 },
				"virus": { "elem1":"rotten_meat", "chance":0.1 },
				"poison": { "elem1":"rotten_meat", "elem2":null, "chance":0.5 },
				"infection": { "elem1":"rotten_meat", "elem2":null, "chance":0.1 },
				"ink": { "elem1":"rotten_meat", "elem2":null, "chance":0.1 },
				"acid": { "elem1":"rotten_meat", "elem2":null, "chance":0.5 },
				"acid_gas": { "elem1":"rotten_meat", "chance":0.4 },
				"cyanide": { "elem1":"rotten_meat", "elem2":null, "chance":0.5 }
			},
			tempHigh: 100,
			stateHigh: "cooked_meat",
			tempLow: -18,
			stateLow: "frozen_meat",
			category:"life",
			hidden: true,
			breakInto: ["meat", "blood"],
			burn:10,
			burnTime:200,
			burnInto:["cooked_meat","steam","steam","salt"],
			state: "solid",
			density: 1081,
			conduct: 1,
		};

		elements.cerebrospinal_fluid = {
			color: "#ced7db",
			behavior: behaviors.LIQUID,
			state: "liquid",
			tempHigh: 100,
			stateHigh: "steam",
			breakInto: "steam",
			reactions: JSON.parse(JSON.stringify(elements.water.reactions)),
		};

		function validatePanic(pixel) {
			//console.log(`validatePanic: validatePanic called on pixel ${pixel.element} at (${pixel.x},${pixel.y}) with panic level ${pixel.panic || 0}`);
			if(pixel.element.endsWith("body")) {
				//console.log("validatePanic called on body pixel (panic is stored in the head)");
			};
			if(Number.isNaN(pixel.panic)) {
				//console.log("NaN case: panic set to 0");
				pixel.panic = 0;
			};
			//console.log(`Bounding code running from value of ${pixel.panic}`);
			pixel.panic = Math.max(0,Math.min(1,pixel.panic));
			//console.log(`Validation result: Panic set to ${pixel.panic}`);

			if(Number.isNaN(pixel.mood)) {
				//console.log("NaN case: panic set to 0");
				pixel.mood = 0;
			};
			//console.log(`Bounding code running from value of ${pixel.panic}`);
			pixel.mood = Math.max(-3,Math.min(3,pixel.mood));
			//console.log(`Validation result: Panic set to ${pixel.panic}`);
		};

		goodPixels = {
			silver: { panicChange: 0.01, panicChangeChance: 0.1, moodChange: 0.004 },
			gold: { panicChange: 0.02, panicChangeChance: 0.15, moodChange: 0.01 },
			diamond: { panicChange: 0.03, panicChangeChance: 0.2, moodChange: 0.02 },
		}; //effectively, the difference is that good pixels don't make the human flip direction (run away);
		badPixels = {
			rotten_meat: { panicChange: 0.02, panicChangeChance: 0.15, moodChange: -0.015 },
			blood: { panicChange: 0.06, panicChangeChance: 0.2, moodChange: -0.006 },
			brain: { panicChange: 0.1, panicChangeChance: 0.3, moodChange: -0.005 },
			fire: { panicChange: 0.1, panicChangeChance: 0.1, moodChange: 0 },
			poison: { panicChange: 0.2, panicChangeChance: 0.05, moodChange: -0.01 },
			grenade: { panicChange: 0.2, panicChangeChance: 0.4, moodChange: -0.3 },
			bomb: { panicChange: 0.2, panicChangeChance: 0.4, moodChange: -0.3 },
			tnt: { panicChange: 0.2, panicChangeChance: 0.4, moodChange: 0 },
			dynamite: { panicChange: 0.2, panicChangeChance: 0.4, moodChange: -0.3 },
			upward_bomb: { panicChange: 0.2, panicChangeChance: 0.4, moodChange: -0.3 },
			cluster_bomb: { panicChange: 0.2, panicChangeChance: 0.4, moodChange: -0.4 },
			landmine: { panicChange: 0.25, panicChangeChance: 0.1, moodChange: -0.3 },
			fireball: { panicChange: 0.25, panicChangeChance: 0.45, moodChange: -0.35 },
			magma: { panicChange: 0.3, panicChangeChance: 0.2, moodChange: 0 },
			plasma: { panicChange: 0.3, panicChangeChance: 0.2, moodChange: 0 },
			nuke: { panicChange: 1, panicChangeChance: 1, moodChange: -1 }, //insta-panic
			cluster_nuke: { panicChange: 1, panicChangeChance: 1, moodChange: -1 }, //insta-panic
		}; //testing
		otherPixels = ["head","body"]; //do custom code here

		var initialTransparencyArray = ["glass","water","salt_water","sugar_water","steam","oxygen","nitrogen","neon","methane","propane","anesthesia","ammonia","carbon_dioxide","helium","hydrogen","ozone","radiation","pool_water"];
		for(transparentElementIndex = 0; transparentElementIndex < initialTransparencyArray.length; transparentElementIndex++) {
			var transparentElement = initialTransparencyArray[i];
			if(typeof(elements[transparentElement]) !== "undefined") {
				elements[transparentElement].transparent = true;
			};
		};

		elements.body.properties = {
			dead: false,
			dir: 1,
			extremePanicStart: null,
		};
		elements.body.tick = function(pixel) {
			if(typeof(pixel.extremePanicStart) == "undefined") {
				//console.log("oops");
				pixel.extremePanicStart = null
			};
			if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
				if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
					var headpixel = pixelMap[pixel.x][pixel.y-2];
					if (headpixel.element == "head") {
						if (isEmpty(pixel.x, pixel.y-1)) {
							movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
						}
						else {
							swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
						}
					}
				}
			}
			doHeat(pixel);
			doBurning(pixel);
			doElectricity(pixel);
			if (pixel.dead) {
				// Turn into rotten_meat if pixelTicks-dead > 500
				if (pixelTicks-pixel.dead > 200) {
					changePixel(pixel,"rotten_meat");
				}
				return
			}

			// Find the head
			if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == "head") {
				var head = pixelMap[pixel.x][pixel.y-1];
				if (head.dead) { // If head is dead, kill body
					pixel.dead = head.dead;
				}
			}
			else { var head = null }

			if (isEmpty(pixel.x, pixel.y-1)) {
				// create blood if decapitated 10% chance
				if (Math.random() < 0.1) {
					createPixel("blood", pixel.x, pixel.y-1);
					// set dead to true 15% chance
					if (Math.random() < 0.15) {
						pixel.dead = pixelTicks;
					}
				}
			}
			if (head == null) { return };

			if (Math.random() < (0.1 + head.panic)) { // Move 10% chance, varying depending on panic value
				var movesToTry = [
					[1*pixel.dir,0],
					[1*pixel.dir,-1],
				];
				// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
				while (movesToTry.length > 0) {
					var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
					/*
					console.log(move);
					console.log("Body X:", pixel.x, "to", pixel.x+move[0]);
					console.log("Body Y:", pixel.y, "to", pixel.y+move[1]);
					console.log("Head X:",head.x, "to", head.x+move[0]);
					console.log("Head Y:", head.y, "to", head.y+move[1]);
					*/
					//If head coords are empty
					if (isEmpty(pixel.x+move[0], pixel.y+move[1]) && isEmpty(head.x+move[0], head.y+move[1])) {
						//console.log("Head target coords are empty");
						if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
							movePixel(head, head.x+move[0], head.y+move[1]);
							//console.log(`Moved body to (${pixel.x},${pixel.y}) and head to (${head.x},${head.y})`);
							//console.log(`Head-body offset (should always be [0,-1]): [${head.x-pixel.x},${head.y-pixel.y}]`)
							break;
						}
					}
				}
				// 15% chance to change direction
				if(!head.dirLocked) {
					if (Math.random() < 0.15) {
						pixel.dir *= -1;
						//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
					};
				};
			};	

			//if not flagged for extreme panic
			//extreme panic will not be flagged in good moods, just to be nice
			if(pixel.extremePanicStart == null && head.panic > 0.8 && head.mood <= 0) {
				//flag extreme panic
				pixel.extremePanicStart = pixelTicks;
			}
			//if flagged for extreme panic and panic is still extreme
			else if(pixel.extremePanicStart != null && (head.panic > 0.8 && head.mood <= 0)) {
				//if extreme panic lasts too long
				if(pixelTicks - pixel.extremePanicStart > 350) {
					//random chance to die from exhaustion/a heart attack/whatever
					if(Math.random() < 0.01) {
						pixel.dead = true;
					};
				};
			}
			//if flagged for extreme panic and extreme panic is no longer extreme
			else if(pixel.extremePanicStart != null && (head.panic <= 0.8 || head.mood > 0)) {
				//unflag
				pixel.extremePanicStart = null;
			};

		};

		elements.body.onTryMoveInto = function(pixel,otherPixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			if(!pixel.dead && hasPixel(pX,pY-1,"head")) { //if this body pixel is alive and has a head
				var head = pixelMap[pX][pY-1];
				var otherElement = otherPixel.element;
				var oX = otherPixel.x;
				var oY = otherPixel.y;
				if(oY !== (pY - 1)) { //exclude the head above this body
					if(otherElement === "head") { //if the pixel hitting this body is a head
						if(hasPixel(oX,oY+1,"body")) { //if the pixel hitting this pixel has a body under it
							var otherBody = pixelMap[oX][oY+1];
							if(otherPixel.dead || otherBody.dead) { //if either part of that human is dead
								head.panic += 0.08; //being hit by a dead ******* body is terrifying
							} else {
								if(otherPixel.panic > 0.04 && otherPixel.mood <= 0) { head.panic += 0.04 }; //living, normal, bodied heads scare only if that incoming human is already scared
							};
						} else { //if it's a severed head
							if(otherPixel.dead) { //if the head is dead
								head.panic += 0.08; //being hit by a /severed ******* head/ is terrifying
							} else {
								head.panic += 0.1; //being hit by a //******* severed head that's still alive// is even worse
							};					
						};
					} else if(otherElement === "body") { //if the pixel hitting this body is a body
						if(hasPixel(oX,oY-1,"head")) { //if the pixel hitting this pixel has a head on it
							var otherHead = pixelMap[oX][oY-1];
							if(otherPixel.dead || otherHead.dead) { //if either part of that human is dead
								head.panic += 0.06; //dead whole body case
							} else {
								if(otherHead.panic > 0.04) { head.panic += 0.04 }; //living, normal, bodied heads scare only if that incoming human is already scared
							};
						} else { //severed body case
							if(otherPixel.dead) { //if the body is dead
								head.panic += 0.08; //imagine being hit by a severed human without the head
							} else {
								head.panic += 0.1; //imagine the above but the heart is still beating
							};
						};
					};
				};
			};
		};

		elements.head.properties = {
			dead: false,
			dirLocked: false,
			panic: 0,
		};

		elements.head.tick = function(pixel) {
			doHeat(pixel);
			doBurning(pixel);
			doElectricity(pixel);
			if (pixel.dead) {
				// Turn into rotten_meat if pixelTicks-dead > 500
				if (pixelTicks-pixel.dead > 200) {
					changePixel(pixel,"rotten_meat");
					return
				}
			}

			// Find the body
			if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == "body") {
				var body = pixelMap[pixel.x][pixel.y+1];
				if (body.dead) { // If body is dead, kill head
					pixel.dead = body.dead;
				}
			}
			else { var body = null }

			if (isEmpty(pixel.x, pixel.y+1)) {
				tryMove(pixel, pixel.x, pixel.y+1);
				// create blood if severed 10% chance
				if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
					createPixel("blood", pixel.x, pixel.y+1);
					// set dead to true 15% chance
					if (Math.random() < 0.15) {
						pixel.dead = pixelTicks;
					}
				}
			}

			pixel.mood ??= 0;

			if((pixelTicks-pixel.start) % 5 === 0) {
				//Vision loop
				var pX = pixel.x;
				var pY = pixel.y;
				if(pixel.dir === -1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = (-1); j > (-16 - 1); j--) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(Object.keys(goodPixels).includes(newElement)) {
									//no dir flip
									if(Math.random() > goodPixels[newElement].panicChangeChance) {
										pixel.panic += goodPixels[newElement].panicChange;
										pixel.mood += goodPixels[newElement].moodChange; //like if there was a pretty painting item, it would make you feel better but you wouldn't necessarily feel the need to run towards it
									};
									pixel.dirLocked = true;
								} else if(Object.keys(badPixels).includes(newElement)) {
									body.dir = 1; //flip dir
									if(Math.random() > badPixels[newElement].panicChangeChance) {
										pixel.panic += badPixels[newElement].panicChange;
										pixel.mood += badPixels[newElement].moodChange;
									};
									pixel.dirLocked = true;
								}; //good and bad should be mutually exclusive; good will be evaulated first because one inevitably has to be considered first
								if(otherPixels.includes(newElement)) {
									//specific custom code
									if(newElement === "head") {
										if(hasPixel(nX,nY+1,"body")) {
											var newBody = pixelMap[nX][nY+1];
											if(newPixel.dead || newBody.dead) {
												pixel.panic += 0.02;	//if it's seeing a whole human, it's likely to see the dead head and the dead body, thus double-executing
																		//it would be nice if there was a way to avoid double/multiple detection of the same human
												if(hasPixel(pX,pY+1,"body")) { //mix error-proofing
													var body = pixelMap[pX][pY+1];
													body.dir = 1; //run away
												};
											} else {
												if(newPixel.panic > 0.04) {
													if(newPixel.panic > 0.8) {
														pixel.panic += 0.015; //it will add up
													} else if(newPixel.panic > 0.6) {
														pixel.panic += 0.012;
													} else if(newPixel.panic > 0.4) {
														pixel.panic += 0.009;
													} else if(newPixel.panic > 0.2) {
														pixel.panic += 0.006;
													} else {
														pixel.panic += 0.003;
													};

													//the vision loop is in the head, and this is in the "seeing head" case, then this will happen when the head sees another head, and heads store panic; this is in the "other head" is panicking case so this will ultimately be the code that runs when its human sees another human panicking
													if(Math.random() < 0.5) {
														//run in same direction as panicking person
														pixel.dir = newPixel.dir
													};
												};
											};
										} else { //severed head
											newPixel.dead ? pixel.panic += 0.03 : pixel.panic += 0.04;
											if(hasPixel(pX,pY+1,"body")) {
												var body = pixelMap[pX][pY+1];
												body.dir = 1; //run away
											};
										};
									} else if(newElement === "body") {
										if(hasPixel(nX,nY-1,"head")) {
											var newHead = pixelMap[nX][nY-1];
											if(newPixel.dead || newHead.dead) {
												pixel.panic += 0.02;
												if(hasPixel(pX,pY+1,"body")) {
													var body = pixelMap[pX][pY+1];
													body.dir = 1; //run away
												};
											} else {
												if(newHead.panic > 0.04) {
													if(newHead.panic > 0.8) {
														pixel.panic += 0.014; //it will add up
													} else if(newHead.panic > 0.6) {
														pixel.panic += 0.011;
													} else if(newHead.panic > 0.4) {
														pixel.panic += 0.008;
													} else if(newHead.panic > 0.2) {
														pixel.panic += 0.005;
													} else {
														pixel.panic += 0.002;
													};
												};
											};
										} else { //severed body
											newPixel.dead ? pixel.panic += 0.025 : pixel.panic += 0.035;
											if(hasPixel(pX,pY+1,"body")) { //mix error-proofing
												var body = pixelMap[pX][pY+1];
												body.dir = 1; //run away
											};
										};
									};
								};
								//code outside of those three if blocks will be applied to pixels of all elements
								if(!elements[newElement].transparent) {
									break; //can't see through humans
								};
							};
						};
					};
				} else if(pixel.dir === 1) {
					for(i = -4; i < 4+1; i++) {
						var oY = i;
						//console.log(`Starting row look at row ${pY+oY}`)
						for(j = 1; j < 16 + 1; j++) {
							var oX = j;
							var nX = pX+oX;
							var nY = pY+oY;
							if(outOfBounds(nX,nY)) {
								//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
								break;
							};
							if(isEmpty(nX,nY)) {
								//console.log(`Skipping pixel (${nX},${nY}) (empty)`)
								continue;
							};
							if(!isEmpty(nX,nY,true)) {
								var newPixel = pixelMap[nX][nY];
								var newElement = newPixel.element;
								if(Object.keys(goodPixels).includes(newElement)) {
									//no dir flip
									if(Math.random() > goodPixels[newElement].panicChangeChance) {
										pixel.panic += goodPixels[newElement].panicChange;
										pixel.mood += goodPixels[newElement].moodChange;
									};
									pixel.dirLocked = true;
								} else if(Object.keys(badPixels).includes(newElement)) {
									if(hasPixel(pX,pY+1,"body")) {
										var body = pixelMap[pX][pY+1];
										body.dir = -1; //run away
									};
									if(Math.random() > badPixels[newElement].panicChangeChance) {
										pixel.panic += badPixels[newElement].panicChange;
										pixel.mood += badPixels[newElement].moodChange;
									};
									pixel.dirLocked = true;
								}; //good and bad should be mutually exclusive; good will be evaulated first because one inevitably has to be considered first
								if(otherPixels.includes(newElement)) {
									if(newElement === "head") {
										if(hasPixel(nX,nY+1,"body")) {
											var newBody = pixelMap[nX][nY+1];
											if(newPixel.dead || newBody.dead) {
												pixel.panic += 0.02;	//if it's seeing a whole human, it's likely to see the dead head and the dead body, thus double-executing
																		//it would be nice if there was a way to avoid double/multiple detection of the same human
												if(hasPixel(pX,pY+1,"body")) {
													var body = pixelMap[pX][pY+1];
													body.dir = -1; //run away
												};
											} else {
												if(newPixel.panic > 0.04) {
													if(newPixel.panic > 0.8) {
														pixel.panic += 0.015; //it will add up
													} else if(newPixel.panic > 0.6) {
														pixel.panic += 0.012;
													} else if(newPixel.panic > 0.4) {
														pixel.panic += 0.009;
													} else if(newPixel.panic > 0.2) {
														pixel.panic += 0.006;
													} else {
														pixel.panic += 0.003;
													};
												};
											};
										} else { //severed head
											newPixel.dead ? pixel.panic += 0.03 : pixel.panic += 0.04;
											if(hasPixel(pX,pY+1,"body")) {
												var body = pixelMap[pX][pY+1];
												body.dir = -1; //run away
											};
										};
									} else if(newElement === "body") {
										if(hasPixel(nX,nY-1,"head")) {
											var newHead = pixelMap[nX][nY-1];
											if(newPixel.dead || newHead.dead) {
												pixel.panic += 0.02;
												if(hasPixel(pX,pY+1,"body")) {
													var body = pixelMap[pX][pY+1];
													body.dir = -1; //run away
												};
											} else {
												if(newHead.panic > 0.04) {
													if(newHead.panic > 0.8) {
														pixel.panic += 0.014; //it will add up
													} else if(newHead.panic > 0.6) {
														pixel.panic += 0.011;
													} else if(newHead.panic > 0.4) {
														pixel.panic += 0.008;
													} else if(newHead.panic > 0.2) {
														pixel.panic += 0.005;
													} else {
														pixel.panic += 0.002;
													};
												};
											};
										} else { //severed body
											newPixel.dead ? pixel.panic += 0.025 : pixel.panic += 0.035;
											if(hasPixel(pX,pY+1,"body")) {
												var body = pixelMap[pX][pY+1];
												body.dir = -1; //run away
											};
										};
									};
								};
								//code outside of those three if blocks will be applied to pixels of all elements
								if(!elements[newElement].transparent) {
									break; //can't see through humans
								};
							};
						};
					};
				};
			};

			validatePanic(pixel);

			if(Math.random() < 0.01) { //1% chance each tick to lose interest
				pixel.dirLocked = false;
				//console.log("Meh.");
			};

			if(Math.random() < ((pixel.panic) > 0.8 ? 0.04 : 0.02)) { //2% chance each tick to decrease panic (4% if the panic is extreme)
				//console.log("Decreasing panic");
				pixel.panic < 0.05 ? pixel.panic = 0 : pixel.panic -= 0.05;
			};

		};
		elements.head.breakInto = ["bone","brain","brain","cerebrospinal_fluid","blood","blood","meat"];

		elements.head.onTryMoveInto = function(pixel,otherPixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			if(!pixel.dead) {
				var otherElement = otherPixel.element;
				var oX = otherPixel.x;
				var oY = otherPixel.y;
				if(oY !== (pY + 1)) { //exclude the body under this head
					if(otherElement === "head") { //if the pixel hitting this head is also a head
						//console.log("head.onTryMoveInto: Head has tried to move into head");
						if(hasPixel(oX,oY+1,"body")) { //if the pixel hitting this pixel has a body under it
							var otherBody = pixelMap[oX][oY+1];
							if(otherPixel.dead || otherBody.dead) { //if either part of that human is dead
								pixel.panic += 0.08; //being hit by a dead ******* body is terrifying
								//console.log("head.onTryMoveInto: panic increase, case: head hit by dead whole body (head's code branch)");
							} else {
								//if(otherPixel.panic > 0.04) { pixel.panic += 0.04; console.log("head.onTryMoveInto: panic increase, case: head hit by panicked whole body (head's code branch)"); }; //living, normal, headed bodies scare only if that incoming human is already scared
							};
						} else { //if it's a severed head
							if(otherPixel.dead) { //if the head is dead
								pixel.panic += 0.08; //being hit by a /severed ******* head/ is terrifying
								//console.log("head.onTryMoveInto: panic increase, case: head hit by dead severed head");
							} else {
								pixel.panic += 0.1; //being hit by a //******* severed head that's still alive// is even worse
								//console.log("head.onTryMoveInto: panic increase, case: head hit by living severed head");
							};					
						};
					} else if(otherElement === "body") { //if the pixel hitting this head is a body
						if(hasPixel(oX,oY-1,"head")) { //if the body hitting this pixel has a head on it
							var otherHead = pixelMap[oX][oY-1];
							if(otherPixel.dead || otherHead.dead) { //if either part of that human is dead
								pixel.panic += 0.03; //dead whole body case
								//console.log("head.onTryMoveInto: panic increase, case: head hit by dead whole body (body's code branch)");
							} else {
								if(otherHead.panic > 0.04) {
									pixel.panic += 0.03;
									//console.log("head.onTryMoveInto: panic increase, case: head crushed by panicked whole body (body's code branch)");
								} else {
									pixel.panic += 0.02;
									//console.log("head.onTryMoveInto: panic increase, case: head crushed by whole body (body's code branch)");
								};
							};
						} else { //severed body case
							if(otherPixel.dead) { //if the body is dead
								pixel.panic += 0.04; //imagine being hit by a severed human without the head
								//console.log("head.onTryMoveInto: panic increase, case: head hit by dead severed body");
							} else {
								pixel.panic += 0.05; //imagine the above but the heart is still beating
								//console.log("head.onTryMoveInto: panic increase, case: head hit by living severed body");
							};
						};
					} else {
						if(oX === pX && oY === pY-1) {
							var otherInfo = elements[otherElement];
							var otherState; typeof(otherInfo.state) === "undefined" ? otherState = null : otherState = otherInfo.state;
							var otherDensity = typeof(otherInfo.density) === "undefined" ? otherDensity = null : otherDensity = otherInfo.density;
							if(otherState === "solid") {
								if(otherDensity > 5000) {
									var chance = (0.1 + (otherDensity/50000)) / 5;
									if(Math.random() < chance) {
										breakPixel(pixel);
									};
								} else if(otherDensity >= 500) {
									pixel.panic += (0.01 * (otherDensity / 500));
								} else if(otherDensity >= 100) {
									pixel.panic += (0.001 * (otherDensity / 100));
								};
							};
						};
					};
				};
			};
		};

		//Worldgen preset for testing

		worldgentypes.basalt_dirt = {
			layers: [
				[0, "basalt", 0.05],
				[0, "dirt"]
			]
		};

		kep1er = [
								//Conn. 0	Conn. -	  Conn. 1
			["first_impact",	["#E34B6E","#FE9F19","#8E5ECE"]],
								//Lemon Bl.	B1ue Bl.
			["doublast",		["#FFFB1D","#2B8FFF"]],
								//not edition colors because i'm not doing all of those and they're all really similar anyway (the kep1ian editions are too similar amongst themselves)
			["fly-up",			["#f2f2f2","#15a667","#de0180"]],
								//K			Midn.	  Daydr.; physical albums do not look like their "theme colors"	and are mostly black
			["troubleshooter",	["#EE378E","#088EE7","#FDDC03"]],
								//see fly-up
			["fly-by",			["#e7e6dd","#fcf0ef","#efa1ba","#8d7cb6","#5e74ba","#2b5db5","#e292b7"]],
								//Eye Cont.	L. Str.	  1st Blush
			["lovestruck",		["#C1CCE6","#F5E4CE","#FFD9E0"]],
								//Beloved	S.kissed  Moonlighted
			["magic_hour",		["#EDC1D2","#EFCBB1","#7585B6"]]
		];

		for(index in kep1er) {
			index = parseInt(index);
			var newName = kep1er[index][0];
			var newColor = kep1er[index][1];
			var newDisplayName = newName.replaceAll("_"," ").replaceAll("-"," - ").split(" ").map(x => x.substring(0,1).toUpperCase() + x.substring(1)).join(" ").replace(" - ","-");
			elements[newName] = {
				name: newDisplayName,
				color: newColor,
				tempHigh: 200,
				stateHigh: ["ash","molten_plastic"],
				density: 332, //based off of First Impact: https://www.amazon.com/Kep1er-IMPACT-Contents-Tracking-Connect/dp/B09MQMNZ62
				tick: function(pixel) {
					if(!(tryMove(pixel,pixel.x,pixel.y+1))) {
						var directions = [];
						if(isEmpty(pixel.x-1,pixel.y+1) && isEmpty(pixel.x-1,pixel.y+2)) {
							directions.push(-1)
						};
						if(isEmpty(pixel.x+1,pixel.y+1) && isEmpty(pixel.x+1,pixel.y+2)) {
							directions.push(1)
						};
						if(directions.length > 0) {
							tryMove(pixel,pixel.x+directions[Math.floor(Math.random() * directions.length)],pixel.y)
						};
					};
					doHeat(pixel);
				},
				reactions: {
					water: { elem1: ["plastic","cellulose","cellulose"], elem2: ["water","water","cellulose",null,null], chance: 0.8 }
				},
				burn: 40,
				burnTime: 150,
				burnInto: ["ash","molten_plastic","carbon_dioxide","smoke"],
				category: "other"
			};

			goodPixels[newName] = { panicChange: 0.01, panicChangeChance: 0.2, moodChange: 0.035 };
		};

	//CHANGES TO SPONGES ##

		elements.sponge.properties ??= {};
		elements.sponge.properties.maxAbsorb = 250;

		elements.sponge.tick = function(pixel) {
			pixel.absorbed ??= {};
			var coordsToCheck = [
				[pixel.x-1,pixel.y],
				[pixel.x+1,pixel.y],
				[pixel.x,pixel.y-1],
				[pixel.x,pixel.y+1],
			];
			shuffleArray(coordsToCheck);
			for (var i = 0; i < coordsToCheck.length; i++) {
				if(sumNumericArray(Object.values(pixel.absorbed)) >= pixel.maxAbsorb) {
					break;
				};
				var coord = coordsToCheck[i];
				if (!isEmpty(coord[0],coord[1],true)) {
					var newPixel = pixelMap[coord[0]][coord[1]];
					if (elements[newPixel.element].state === "liquid" && ((elements[newPixel.element].density || 0) < 2500)) {
						pixel.absorbed[newPixel.element] ??= 0;
						pixel.absorbed[newPixel.element]++;
						deletePixel(coord[0],coord[1]);
					};
				};
			};
			if((pixel.absorbed.water ?? 0) < 5) {
				doBurning(pixel);
			};
		};

		elements.sponge.onTryMoveInto = function(pixel,otherPixel) {
			var absorbedElements = Object.keys(pixel.absorbed ?? {});
			if(absorbedElements.length == 0) {
				return false;
			};
			var otherInfo = elements[otherPixel.element]
			if((otherInfo.state ?? "solid") == "solid" && (otherInfo.density ?? 1000) >= 200) {
				//console.log(otherPixel.element,otherInfo.state);
				var outputOffsets = [pixel.x - otherPixel.x, pixel.y - otherPixel.y];
				var twiceOffsets = outputOffsets.map(x => x * 2);
				var newCoords = [pixel.x + outputOffsets[0], pixel.y + outputOffsets[1]];
				var twiceCoords = [pixel.x + twiceOffsets[0], pixel.y + twiceOffsets[1]];
				if(!isEmpty(newCoords[0],newCoords[1],true)) {
					if(outOfBounds(newCoords[0],newCoords[1])) { //fail if OOB
						return false;
					};
					var newPixel = pixelMap[newCoords[0]][newCoords[1]];
					if((elements[newPixel.element].state ?? "solid") !== "gas") { //only displace gases
						return false;
					};
					if(!tryMove(newPixel,twiceCoords[0],twiceCoords[1])) { //if it can't push the gas out to the next pixel over
						return false; //then return false because perpendicular expulsion too complex and multifaceted to deal with
					};
				};
				if(isEmpty(pixel.x,pixel.y+1)) {
					var randomElement = randomChoice(absorbedElements);
					if(pixel.absorbed[randomElement] > 0) {
						if(tryCreatePixel(randomElement,newCoords[0],newCoords[1])) { pixel.absorbed[randomElement]-- };
					} else {
						delete pixel.absorbed[randomChoice]
					};
				};
			};
		};

	//MIX OF PRIMORDIAL SOUP AND BIRTHPOOL (also primordial soup freezing, and state fixes) ##

		elements.primordial_soup.tempLow = 0;

		elements.birthpool.tempHigh = 100;
		elements.birthpool.stateHigh = ["magic","steam"];
		elements.birthpool.tempLow = 0;
		elements.birthpool.state = "liquid";
		elements.birthpool.forceAutoGen = true;

		elements.primordial_birthpool = {
			color:["#5E7453","#5E745D","#5E744B","#6C7C50","#6C7C59","#6C7C47"],
			state: "liquid",
			tempHigh: 100,
			stateHigh: ["steam","steam","magic"],
			behavior: [
				"CR:foam%1|CR:sapling,wheat_seed,flower_seed,algae,cell,mushroom_spore,lichen,yeast,antibody,cellulose%0.5 AND CR:foam%2|CR:foam%1",
				"M2|XX|M2",
				"M1|M1|M1",
			],
			reactions: {
				"cancer": { "elem1":["toxic_mistake","dirty_water"] },
				"cyanide": { "elem1":["toxic_mistake","dirty_water"] },
				"infection": { "elem1":["toxic_mistake","dirty_water"] },
				"plague": { "elem1":["toxic_mistake","dirty_water"] },
				"ammonia": { "elem1":["algae","cell","mushroom_spore","lichen","yeast","antibody"], "chance":0.05 },
				"radiation": { "elem1":["algae","cell","mushroom_spore","lichen","yeast","antibody"], "chance":0.15 },
				"light": { "elem1":["algae","cell","mushroom_spore","lichen","yeast","antibody"], "chance":0.5 },
				"oxygen": { "elem1":["algae","cell","mushroom_spore","lichen","yeast","antibody"], "chance":0.02 }
			},
			density: 1110,
			tempHigh: 100,
			stateHigh: "steam",
			conduct: 0.33,
			category: "liquids",
			hidden: true,
			reactions: {
				concoction: { "elem1": ["primordial_soup", "birthpool", "primordial_birthpool"], "elem2": ["primordial_soup", "birthpool", "primordial_birthpool"], "chance":0.0045}
			},
		};

		if(!elements.birthpool.reactions) {
			elements.birthpool.reactions = {}
		}

		elements.birthpool.reactions.primordial_soup = { "elem1":"primordial_birthpool", "elem2":"primordial_birthpool" }

	//AUTOGENERATED CONTENT ##

		//urlParams reads

			//Bombs

				if(urlParams.get('generateBombs') !== null) { //if the variable exists at all
					generateBombs = true
				} else { //if it doesn't (and it returns null)
					generateBombs = false
				}

				if(urlParams.get('bombAmount') != null) { //null check
					bombAmount = urlParams.get('bombAmount')
					if(isNaN(bombAmount) || bombAmount === "" || bombAmount === null) { //NaN check
						 bombAmount = 10
					}
					bombAmount = parseInt(bombAmount)
					if(bombAmount > 50) {
						alert("Maximum amount of additional bomb/anti-bomb pairs is 50.\nOnly 50 were added.")
					} else if(bombAmount < 1) {
						alert("Minimum amount of additional bomb/anti-bomb pairs is 1.\n1 pair was added.")
					}
					bombAmount = Math.min(50,Math.max(bombAmount,1))
				} else {
					bombAmount = 10
				}

			//Clouds

				if(urlParams.get('generateClouds') !== null) { //if the variable exists at all
					generateClouds = true
				} else { //if it doesn't (and it returns null)
					generateClouds = false
				}

				if(urlParams.get('cloudIncludeRandom') !== null) { //if the variable exists at all
					cloudIncludeRandom = true
				} else { //if it doesn't (and it returns null)
					cloudIncludeRandom = false
				}

			//Creepers

				//Include generated creepers in Random tool?
				if(urlParams.get('creeperIncludeRandom') !== null) { //if the variable exists at all
					creeperIncludeRandom = true
				} else { //if it doesn't (and it returns null)
					creeperIncludeRandom = false
				}

				//Generate creepers
				if(urlParams.get('generateCreepers') !== null) { //if the variable exists at all
					generateCreepers = true
				} else { //if it doesn't (and it returns null)
					generateCreepers = false
				}

			//Fairies

				if(urlParams.get('fairyIncludeRandom') !== null) { //if the variable exists at all
					fairyIncludeRandom = true
				} else { //if it doesn't (and it returns null)
					fairyIncludeRandom = false
				}

				//Generate fairies
				if(urlParams.get('generateFairies') !== null) { //if the variable exists at all
					generateFairies = true
				} else { //if it doesn't (and it returns null)
					generateFairies = false
				}

				if(urlParams.get('fairyAmount') != null) { //null check
					fairyAmount = urlParams.get('fairyAmount')
					if(isNaN(fairyAmount) || fairyAmount === "" || fairyAmount === null) { //NaN check
						 fairyAmount = 10
					}
					fairyAmount = parseInt(fairyAmount)
					if(fairyAmount > 10000) {
						alert("Maximum amount of additional fairies is 10000.\nOnly 10000 fairies were added.")
					} else if(fairyAmount < 1) {
						alert("Minimum amount of additional fairies is 1.\n1 fairy was added.")
					}
					fairyAmount = Math.min(10000,Math.max(fairyAmount,1))
				} else {
					fairyAmount = 10
	}

			//Spouts

				//Generate spouts
				if(urlParams.get('generateSpouts') !== null) { //if the variable exists at all
					generateSpouts = true
				} else { //if it doesn't (and it returns null)
					generateSpouts = false
				}

				if(urlParams.get('spoutIncludeRandom') !== null) { //if the variable exists at all
					spoutIncludeRandom = true
				} else { //if it doesn't (and it returns null)
					spoutIncludeRandom = false
				}

		//Requisite variables

			//Bombs

				amalgamatedBombFire = "plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,plasma,smoke,plasma,plasma,fire,smoke,fire,smoke,plasma,metal_scrap,metal_scrap,metal_scrap,metal_scrap,metal_scrap,acid,acid,oil,oil,oil,oil,oil,oil,oil,plasma,plasma,plasma,plasma,plasma,smoke,plasma,plasma,fire,smoke,plasma,metal_scrap,metal_scrap,metal_scrap,metal_scrap,metal_scrap,metal_scrap,flash,flash,flash,flash,flash,acid_gas,acid_gas,acid_gas,acid,oil,oil,oil,oil,oil,oil,oil,oil,oil,oil,plasma,plasma,plasma,plasma,metal_scrap,metal_scrap,metal_scrap,metal_scrap,metal_scrap,acid,acid,oil,oil,oil,oil,oil,oil,oil,plasma,plasma,plasma,plasma,plasma,smoke,plasma,plasma,fire,smoke,plasma,metal_scrap,metal_scrap,metal_scrap,metal_scrap,metal_scrap,metal_scrap,electric_cluster_bomb,electric_cluster_bomb,flash,flash,flash,flash,flash,acid_gas,acid_gas,acid_gas,acid,oil,oil,oil,oil,oil,oil,oil,oil,oil,oil,plasma,plasma,plasma,plasma,plague,plague,plague,plague,plague,plague,radiation,radiation,radiation,radiation,radiation,radiation,radiation,radiation,uranium,uranium,uranium,uranium,uranium,uranium,greek_fire,greek_fire,greek_fire,greek_fire,greek_fire,antimatter,antimatter,antimatter,antimatter,antimatter,smoke_grenade,antimatter,smoke_grenade,fireball,flash,acid_gas,acid_gas,acid_gas,plague,plague,plague,plague,plague,plague,radiation,radiation,radiation,radiation,radiation,radiation,radiation,radiation,uranium,uranium,uranium,uranium,uranium,uranium,greek_fire,greek_fire,greek_fire,greek_fire,greek_fire,antimatter,antimatter,antimatter,antimatter,antimatter,smoke_grenade,antimatter,flash,acid_gas,acid_gas,acid_gas,radiation,radiation,radiation,radiation,plague,acid_gas,acid_gas,acid_gas,chlorine,chlorine,chlorine"
				eLists.BOMB = ["bomb", "tnt", "c4", "grenade", "dynamite", "gunpowder", "firework", "nuke", "h_bomb", "dirty_bomb", "emp_bomb", "sticky_bomb", "cold_bomb", "hot_bomb", "electro_bomb", "water_bomb", "antimatter_bomb", "flashbang", "smoke_grenade", "fireball", "landmine", "cluster_bomb", "cluster_nuke", "op_hottester_bomb", "anti-bomb", "electric_bomblet", "electric_cluster_bomb", "radioactive_popper", "acid_bomb", "amalgamated_bomb"]; bombChoices = eLists.BOMB;
				var excludedBombElements = ["water", "antimatter", "acid"];

			//Clouds

				eLists.CLOUD = ["cloud", "rain_cloud", "snow_cloud", "fire_cloud", "hail_cloud", "acid_cloud", "pyrocumulus"]; cloudChoices = eLists.CLOUD;
				var includedClouds = ["cloud", "rain_cloud", "snow_cloud", "fire_cloud", "hail_cloud", "acid_cloud", "pyrocumulus"];
				var excludedCloudElements = ["snow", "fire", "hail", "acid"];
				if(typeof(behaviorGenerators) === "undefined") { behaviorGenerators = {} };

			//Creepers

				var placeholderColor = "#FF00FF";
				var hslOffsets = [[0, -5, 5], [0, -20, 10], [0, 0, 10], [0, -20, 10], [0, -35, 0], [0, -20, -30], [0, 10, -10], [0, 10, 20], [0, -20, 10], [0, -10, 5]];
				var colorOfRandomCreeper = ["#7ba883", "#8aba8a", "#87b292", "#8aba8a", "#71a171", "#346434", "#4d6d72", "#a0caad", "#8aba8a", "#7dac7f"]

			//Fairies

				var excludedFairyElements = []
				var backupCategoryWhitelist = ["land","powders","weapons","food","life","corruption","states","fey","Fantastic Creatures","dyes","energy liquids","random liquids","random gases","random rocks"];
				var backupElementWhitelist = ["mercury", "chalcopyrite_ore", "chalcopyrite_dust", "copper_concentrate", "fluxed_copper_concentrate", "unignited_pyrestone", "ignited_pyrestone", "everfire_dust", "extinguished_everfire_dust", "mistake", "polusium_oxide", "vaporized_polusium_oxide", "glowstone_dust", "redstone_dust", "soul_mud", "wet_soul_sand", "nitrogen_snow", "fusion_catalyst", "coal", "coal_coke", "blast_furnace_fuel", "molten_mythril"];

			//Spouts
				eLists.SPOUT = ["spout", "udder", "torch"]; spoutChoices = eLists.SPOUT;
				var excludedSpoutElements = ["ketchup", "liquid_cloner", "fire_cloner"]
				var includedSpouts = ["ketchup_spout", "spout", "udder", "torch"]
				var backupCategoryWhitelist = ["land","powders","weapons","food","life","corruption","states","fey","Fantastic Creatures","dyes","energy liquids","random liquids","random gases","random rocks"];
				var backupElementWhitelist = ["mercury", "chalcopyrite_ore", "chalcopyrite_dust", "copper_concentrate", "fluxed_copper_concentrate", "unignited_pyrestone", "ignited_pyrestone", "everfire_dust", "extinguished_everfire_dust", "mistake", "polusium_oxide", "vaporized_polusium_oxide", "glowstone_dust", "redstone_dust", "soul_mud", "wet_soul_sand", "nitrogen_snow", "fusion_catalyst", "coal", "coal_coke", "blast_furnace_fuel", "molten_mythril"];


		//Requisite non-generating functions

			//Bombs

				function firebombFire(pixel,x,y,radius,fire,smoke,power,damage) {
					var coords = circleCoords(pixel.x,pixel.y,radius);
					for (var i = 0; i < coords.length; i++) {
						var x = coords[i].x;
						var y = coords[i].y;
						if(!isEmpty(x,y,true)) {
							var pixel = pixelMap[x][y];
							var info = elements[pixel.element];
							var cursedFireChance = 0.15 + power;
							if (info.burn) { //Light everything on fire
								pixel.burning = true;
								pixel.burnStart = pixelTicks;
								pixel.temp += 10; //smoke prevention
							} else if(Math.random() < cursedFireChance) { //(15+power)%/px cursed burning
								pixel.burning = true;
								pixel.burnStart = pixelTicks;
								pixel.temp += 10;
							};
						} else if(isEmpty(x,y)) { //if there's space for fire
							if (Array.isArray(fire)) { //this should remain "fire"
								var newfire = fire[Math.floor(Math.random() * fire.length)];
							} else {
								var newfire = fire;
							};
							createPixel(newfire,x,y); //add fire
							var firePixel = pixelMap[x][y];
							firePixel.temp = Math.max(elements[newfire].temp ?? 0,elements.fire.temp,firePixel.temp ?? 0);
							firePixel.burning = true;
						};
					};
				};

				elements.disappear = {
					color: "#7f7f7f",
					behavior: behaviors.SELFDELETE,
					insulate: true,
					temp: -273.15,
					hardness: 0,
					excludeRandom: true,
					category: "other"
				};

				function forcebombVelocity(pixel,x,y,radius,fire,smoke,power,damage) {
					var coords = circleCoords(pixel.x,pixel.y,radius);
					for (var i = 0; i < coords.length; i++) {
						var coordX = coords[i].x;
						var coordY = coords[i].y;
						if(!isEmpty(coordX,coordY,true)) {
							let pixelle = pixelMap[coordX]?.[coordY];
							if(typeof(pixelle) !== "object" || pixelle === null) { continue };
							if(coordX === x && coordY === y) {
								if(pixelle.element === "force_bomb") { fuckingDeletePixel(pixelle); continue }
							};
							// set the pixelle.vx and pixelle.vy depending on the angle and power
							var angle = Math.atan2(pixelle.y-y,pixelle.x-x);
							pixelle.vx = Math.round(4 * (pixelle.vx|0) + (5 * Math.cos(angle) * (radius * power/5)));
							pixelle.vx += (3 * Math.sign(pixelle.vx));
							pixelle.vx = Math.sign(pixelle.vx) == -1 ? bound(pixelle.vx,-100,0) : bound(pixelle.vx,0,100);
							pixelle.vy = Math.round(4 * (pixelle.vy|0) + (5 * Math.sin(angle) * (radius * power/5)));
							pixelle.vy += (3 * Math.sign(pixelle.vy));
							pixelle.vy = Math.sign(pixelle.vx) == -1 ? bound(pixelle.vy,-100,0) : bound(pixelle.vy,0,100);
						}
					};
				};

				function hotterBomb(pixel,x,y,radius,fire,smoke,power,damage) {
					//console.log(`Radius: ${radius}\nPower: ${power}\nPixel: (${pixel.x},${pixel.y})\nDamage: ${damage}`);
					//console.log(`Expected temperature increase for pixel at (${pixel.x},${pixel.y}): ${800 * ((1 + (7 * damage)) ** 2) * ((power ** 2) * 1.5)}`);
					pixel.temp += (800 * ((1 + (7 * damage)) ** 2) * ((power ** 2) * 1.5));
				};

				//explodeAtPlus(x,y,radius,fire="fire",smoke="smoke",beforeFunction=null,afterFunction=null,changeTemp=true) {
				//beforeFunction(pixel,x,y,radius,fire,smoke,power,damage)
				function wallBombBeforeFunction(pixel,x,y,radius,fire,smoke,power,damage) {
					var hardness = (elements[pixel.element].hardness ?? 0) / 2;
					damage = damage * ((1 - hardness)*10);
					//console.log(damage);
					if(damage > 0.25) {
						pixel.breakInto ? breakPixel(pixel) : deletePixel(pixel.x,pixel.y)
					};
					return
				};
				
				elements.wall_bomb = {
					color: "#af8f8f",
					tick: function(pixel) {
						doDefaults(pixel);
						if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
							var newPixel = pixelMap[pixel.x][pixel.y-1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,10,"fire","smoke",wallBombBeforeFunction,null,false);
								if(pixel) { deletePixel(pixel.x,pixel.y) };
								return
							};
						};
						if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
							var newPixel = pixelMap[pixel.x][pixel.y+1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,10,"fire","smoke",wallBombBeforeFunction,null,false);
								if(pixel) { deletePixel(pixel.x,pixel.y) };
								return
							};
						};
						if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
							explodeAtPlus(pixel.x,pixel.y,10,"fire","smoke",wallBombBeforeFunction,null,false);
							if(pixel) { deletePixel(pixel.x,pixel.y) };
							return
						};
						if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
							Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
						};
					},
					category: "weapons",
					state: "solid",
					density: 3000,
					excludeRandom: true,
				};

				elements.tsunami = {
					color: ["#2449d1","#4b6adb","#8093d9"],
					behavior: behaviors.WALL,
					properties: {
						active: true,
					},
					tick: function(pixel) {
						//Iteration initial checks
							if(!pixel) {
								return;
							};
							if(!pixel.active) {
								deletePixel(pixel.x,pixel.y);
							};
						
						//Initial property-setting
							var pixelIsOnLeft = (pixel.x < (width/2));
							pixel.fromX ??= pixelIsOnLeft ? 1 : width - 1;
							pixel.direction ??= pixelIsOnLeft ? 1 : -1;

							var floorHeight = pixel.y + 1;
							while(isEmpty(pixel.x,floorHeight,false)) {
								floorHeight++
							};
							pixel.floorHeight ??= floorHeight;

						//Actual doer code
							var newX = pixel.fromX + pixel.direction;

							if(outOfBounds(newX,1)) {
								pixel.active = false;
								return
							};

							var bottomY = (pixel.floorHeight + 3); //extend 4 pixels below
							var topY = bottomY - 13; //topY < bottomY because in this game +Y is *downward*
							for(var i = bottomY; i >= topY; i--) {
								var waterToDo = randomChoice(["salt_water","dirty_water"]);
								var fc = {x: newX, y: i};
								if(outOfBounds(fc.x,fc.y)) {continue};
								if(isEmpty(fc.x,fc.y,false)) {
									//fill with water
									createPixel(waterToDo,fc.x,fc.y)
								} else {
									var newPixel = pixelMap[fc.x]?.[fc.y];
									if(!newPixel) { continue };
									//break
									tryBreak(newPixel,true,true);
									if(!newPixel) { continue };
									//water reaction steal
									if(elements[newPixel.element].reactions?.water) {
										var waterRxn = elements[newPixel.element].reactions.water;
										var elem1 = waterRxn.elem1;
										while(Array.isArray(elem1)) {
											elem1 = randomChoice(elem1)
										};
										if(elem1 !== null) {
											changePixel(newPixel,elem1,true)
										}
									} else if(elements.water.reactions[newPixel.element]) {
										var waterRxn2 = elements.water.reactions[newPixel.element];
										elem2 = waterRxn2.elem2;
										while(Array.isArray(elem2)) {
											elem2 = randomChoice(elem2)
										};
										if(elem2 !== null) {
											changePixel(newPixel,elem2,true)
										}
									};
									if(!newPixel) { continue };
									//add velocity;
									newPixel.vx ??= 0;
									newPixel.vy ??= 0;
									newPixel.vx += (pixel.direction * 5)
									newPixel.vy -= 3;
								};
							};
							pixel.fromX += pixel.direction
					},
					state: "solid",
					category: "special",
					density: elements.water.density
				};

				elements.megatsunami = {
					color: ["#1f2aa3","#2641c9","#3a57c9"],
					behavior: behaviors.WALL,
					properties: {
						active: true,
					},
					tick: function(pixel) {
						//Iteration initial checks
							if(!pixel) {
								return;
							};
							if(!pixel.active) {
								deletePixel(pixel.x,pixel.y);
							};
						
						//Initial property-setting
							var pixelIsOnLeft = (pixel.x < (width/2));
							pixel.fromX ??= pixelIsOnLeft ? 1 : width - 1;
							pixel.direction ??= pixelIsOnLeft ? 1 : -1;

							var floorHeight = pixel.y + 1;
							while(isEmpty(pixel.x,floorHeight,false)) {
								floorHeight++
							};
							pixel.floorHeight ??= floorHeight;

						//Actual doer code
							var bottomY = (pixel.floorHeight + 9); //extend 10 pixels below
							var topY = bottomY - 43; //topY < bottomY because in this game +Y is *downward*
							for(var h = 0; h < 2; h++) {
								var newX = pixel.fromX + pixel.direction;

								if(outOfBounds(newX,1)) {
									pixel.active = false;
									return
								};

								for(var i = bottomY; i >= topY; i--) {
									var waterToDo = randomChoice(["salt_water","salt_water","salt_water","dirty_water","dirty_water"]);
									var fc = {x: newX, y: i};
									if(outOfBounds(fc.x,fc.y)) {continue};
									if(isEmpty(fc.x,fc.y,false)) {
										//fill with water
										createPixel(waterToDo,fc.x,fc.y)
									} else {
										var newPixel = pixelMap[fc.x]?.[fc.y];
										if(!newPixel) { continue };
										//break
										tryBreak(newPixel,true,true);
										if(!newPixel) { continue };
										//water reaction steal
										if(elements[newPixel.element].reactions?.water) {
											var waterRxn = elements[newPixel.element].reactions.water;
											var elem1 = waterRxn.elem1;
											while(Array.isArray(elem1)) {
												elem1 = randomChoice(elem1)
											};
											if(elem1 !== null) {
												changePixel(newPixel,elem1,true)
											}
										} else if(elements.water.reactions[newPixel.element]) {
											var waterRxn2 = elements.water.reactions[newPixel.element];
											elem2 = waterRxn2.elem2;
											while(Array.isArray(elem2)) {
												elem2 = randomChoice(elem2)
											};
											if(elem2 !== null) {
												changePixel(newPixel,elem2,true)
											}
										};
										if(!newPixel) { continue };
										//add velocity;
										newPixel.vx ??= 0;
										newPixel.vy ??= 0;
										newPixel.vx += (pixel.direction * 13)
										newPixel.vy -= 6;
									};
								};
								pixel.fromX += pixel.direction
							};
					},
					state: "solid",
					category: "special",
					density: elements.water.density
				};

				elements.lava_tsunami = {
					color: ["#ff370a","#e84a23","#e67740"],
					behavior: behaviors.WALL,
					properties: {
						active: true,
					},
					tick: function(pixel) {
						//Iteration initial checks
							if(!pixel) {
								return;
							};
							if(!pixel.active) {
								deletePixel(pixel.x,pixel.y);
							};
						
						//Initial property-setting
							var pixelIsOnLeft = (pixel.x < (width/2));
							pixel.fromX ??= pixelIsOnLeft ? 1 : width - 1;
							pixel.direction ??= pixelIsOnLeft ? 1 : -1;

							var floorHeight = pixel.y + 1;
							while(isEmpty(pixel.x,floorHeight,false)) {
								floorHeight++
							};
							pixel.floorHeight ??= floorHeight;

						//Actual doer code
							var newX = pixel.fromX + pixel.direction;

							if(outOfBounds(newX,1)) {
								pixel.active = false;
								return
							};

							var bottomY = (pixel.floorHeight + 3); //extend 4 pixels below
							var topY = bottomY - 13; //topY < bottomY because in this game +Y is *downward*
							for(var i = bottomY; i >= topY; i--) {
								var fc = {x: newX, y: i};
								if(outOfBounds(fc.x,fc.y)) {continue};
								if(isEmpty(fc.x,fc.y,false)) {
									//fill with lava
									createPixelReturn("magma",fc.x,fc.y).temp = 1400;
								} else {
									var newPixel = pixelMap[fc.x]?.[fc.y];
									if(!newPixel) { continue };
									var data = elements[newPixel.element];
									//break
									for(var j = 0; j < 3; j++) {
										tryBreak(newPixel,true,j == 0);
										if(!newPixel) { break }
									};
									//lava reaction steal
									if(data.reactions?.magma) {
										var magmaRxn = data.reactions.magma;
										var elem2 = magmaRxn.elem2;
										while(Array.isArray(elem2)) {
											elem2 = randomChoice(elem2)
										};
										if(elem2 !== null) {
											changePixel(newPixel,elem2,true)
										}
									} else if(elements.magma.reactions[newPixel.element]) {
										var magmaRxn2 = elements.magma.reactions?.[newPixel.element];
										if(!magmaRxn2) {
										} else {
											elem2 = magmaRxn2.elem2;
											while(Array.isArray(elem2)) {
												elem2 = randomChoice(elem2)
											};
											if(elem2 !== null) {
												changePixel(newPixel,elem2,true)
											}
										}
									};
									if(!newPixel) { continue };
									newPixel.temp = Math.max(newPixel.temp,1400); pixelTempCheck(newPixel);
									if(!newPixel) { continue };
									if(newPixel.element == "fire") { changePixelReturn(newPixel,"magma",true).temp = 1400 };
									if(data.burn) { newPixel.burning = true; newPixel.burnStart = pixelTicks };
									if(Math.random() < 0.1 && newPixel.burnInto) { finishBurn(newPixel) };
									if(newPixel.element == "fire") { changePixelReturn(newPixel,"magma",true).temp = 1400 };
									if(!newPixel) { continue };
									//add velocity;
									newPixel.vx ??= 0;
									newPixel.vy ??= 0;
									newPixel.vx += (pixel.direction * 6)
									newPixel.vy -= 3;
								};
							};
							pixel.fromX += pixel.direction
					},
					state: "solid",
					category: "special",
					density: elements.magma.density
				};

				elements.lava_megatsunami = {
					color: ["#b32b10","#c24d1f","#d66924"],
					behavior: behaviors.WALL,
					properties: {
						active: true,
					},
					tick: function(pixel) {
						//Iteration initial checks
							if(!pixel) {
								return;
							};
							if(!pixel.active) {
								deletePixel(pixel.x,pixel.y);
							};
						
						//Initial property-setting
							var pixelIsOnLeft = (pixel.x < (width/2));
							pixel.fromX ??= pixelIsOnLeft ? 1 : width - 1;
							pixel.direction ??= pixelIsOnLeft ? 1 : -1;

							var floorHeight = pixel.y + 1;
							while(isEmpty(pixel.x,floorHeight,false)) {
								floorHeight++
							};
							pixel.floorHeight ??= floorHeight;

						//Actual doer code
							var bottomY = (pixel.floorHeight + 9); //extend 10 pixels below
							var topY = bottomY - 43; //topY < bottomY because in this game +Y is *downward*
							for(var h = 0; h < 2; h++) {
								var newX = pixel.fromX + pixel.direction;

								if(outOfBounds(newX,1)) {
									pixel.active = false;
									return
								};

								for(var i = bottomY; i >= topY; i--) {
									var fc = {x: newX, y: i};
									if(outOfBounds(fc.x,fc.y)) {continue};
									if(isEmpty(fc.x,fc.y,false)) {
										//fill with lava
										createPixelReturn("magma",fc.x,fc.y).temp = 1450;
									} else {
										var newPixel = pixelMap[fc.x]?.[fc.y];
										if(!newPixel) { continue };
										var data = elements[newPixel.element];
										//break
										for(var j = 0; j < 3; j++) {
											tryBreak(newPixel,true,j == 0);
											if(!newPixel) { break }
										};
										if(!newPixel) { continue };
										//lave reaction steal
										if(data.reactions?.magma) {
											var magmaRxn = data.reactions.magma;
											var elem2 = magmaRxn.elem2;
											while(Array.isArray(elem2)) {
												elem2 = randomChoice(elem2)
											};
											if(elem2 !== null) {
												changePixel(newPixel,elem2,true)
											}
										} else if(elements.magma.reactions[newPixel.element]) {
											var magmaRxn2 = elements.magma.reactions[newPixel.element];
											elem2 = magmaRxn2.elem2;
											while(Array.isArray(elem2)) {
												elem2 = randomChoice(elem2)
											};
											if(elem2 !== null) {
												changePixel(newPixel,elem2,true)
											}
										};
										if(!newPixel) { continue };
										newPixel.temp = Math.max(newPixel.temp,1450); pixelTempCheck(newPixel);
										if(!newPixel) { continue };
										if(newPixel.element == "fire") { changePixelReturn(newPixel,"magma",true).temp = 1400 };
										if(data.burn) { newPixel.burning = true; newPixel.burnStart = pixelTicks };
										if(Math.random() < 0.1 && newPixel.burnInto) { finishBurn(newPixel) };
										if(newPixel.element == "fire") { changePixelReturn(newPixel,"magma",true).temp = 1400 };
										if(!newPixel) { continue };
										//add velocity;
										newPixel.vx ??= 0;
										newPixel.vy ??= 0;
										newPixel.vx += (pixel.direction * 8)
										newPixel.vy -= 5;
									};
								};
								pixel.fromX += pixel.direction
							};
					},
					state: "solid",
					category: "special",
					density: elements.magma.density
				};

				function empCharge(pixel,x,y,radius,fire,smoke,power,damage) {
					var info = elements[pixel.element];
					if(info.conduct) {
						var distanceFromEdge = Math.max(0,radius - coordPyth(pixel.x,pixel.y,x,y));
						var radiusRelatedIncrease = Math.sqrt(distanceFromEdge/5);
						pixel.charge ??= 0;
						pixel.charge += (50 * radiusRelatedIncrease);
						pixel.temp += 700/bound(info.conduct,0.1,1);
					};
				};

				function starbombHeat(pixel,x,y,radius,fire,smoke,power,damage) { //Massively heats depending on distance from explosion center
					var distanceFromEdge = Math.max(0,radius - coordPyth(pixel.x,pixel.y,x,y));
					var radiusRelatedIncrease = 10 ** logN(distanceFromEdge,5);
					pixel.temp += (10000 * radiusRelatedIncrease);
					pixelTempCheck(pixel);
				};

			//Clouds

				behaviorGenerators.cloud = function(element) {
					if(element instanceof Array) { element = element.join(",") };
					return [
						["XX","XX","XX"],
						["XX",`CH:${element}%0.05`,"M1%2.5 AND BO"],
						["XX","XX","XX"]
					];
				};

				behaviorGenerators.heavy_cloud = function(element) {
					if(element instanceof Array) { element = element.join(",") };
					return [
						["XX","XX","XX"],
						["XX",`CH:${element}%0.1`,"M1%2.5 AND BO"],
						["XX",`CR:${element}%0.05`,"XX"]
					];
				};

				behaviorGenerators.heavier_cloud = function(element) {
					if(element instanceof Array) { element = element.join(",") };
					return [
						["XX","XX","XX"],
						["XX",`CH:${element}%0.2`,"M1%2.5 AND BO"],
						["XX",`CR:${element}%0.1`,"XX"]
					];
				};

				behaviorGenerators.heaviest_cloud = function(element) {
					if(element instanceof Array) { element = element.join(",") };
					return [
						["XX","XX","XX"],
						["XX",`CH:${element}%0.4`,"M1%2.5 AND BO"],
						["XX",`CR:${element}%0.2`,"XX"]
					];
				};

				behaviorGenerators.heaviester_cloud = function(element) {
					if(element instanceof Array) { element = element.join(",") };
					return [
						["XX","XX","XX"],
						["XX",`CH:${element}%0.8`,"M1%2.5 AND BO"],
						["XX",`CR:${element}%0.4`,"XX"]
					];
				};

				behaviorGenerators.heaviestest_cloud = function(element) {
					if(element instanceof Array) { element = element.join(",") };
					return [
						["XX",`CR:${element}%0.8`,"XX"],
						[`CR:${element}%0.05`,`CH:${element}%3`,`CR:${element}%0.05 AND M1%2.5 AND BO`],
						["XX",`CR:${element}%0.8`,"XX"]
					];
				};

				function placeDownwardColumn(element,xx,yy,creationChance) {
					if(element.includes(",")) { element = element.split(",") };
					var newElement = element;
					//console.log(JSON.stringify(element));
					//console.log("from " + xx + "," + yy)
					if(!outOfBounds(xx,yy)) {
						for(i = yy; i < pixelMap[xx].length; i++) {
							if(Array.isArray(element)) {
								newElement = element[Math.floor(Math.random() * element.length)];
							};
							//console.log(JSON.stringify(newElement));
							if(isEmpty(xx,i,false)) {
								if(Math.random() < creationChance) {
									createPixel(newElement,xx,i)
								}
							} else if(!isEmpty(xx,i,true)) {
								var newPixel = pixelMap[xx][i];
								var otherElement = newPixel.element;
								var newState = elements[otherElement].state ?? "solid"; //assume undefined = solid
								if(newState !== "solid") {
									continue;
								} else {
									break;
								};
							} else if(outOfBounds(xx,i)) {
								break;
							}; 
						};
					};
				};

				function singleColumn(pixel,element,chance,fillAmountDecimal) {
					if(Math.random() < chance) {
						placeDownwardColumn(element,pixel.x,pixel.y+1,fillAmountDecimal);
					}
				};

				function multiColumn(pixel,element,chance,fillAmountDecimal,columnRadius) {
					if(Math.random() < chance) {
						for(j = 0 - columnRadius; j <= columnRadius; j++) {
							placeDownwardColumn(element,pixel.x+j,pixel.y+1,fillAmountDecimal);
						};
					}
				};

			//Creepers

				autoCreeperPlacerTick = function(pixel) {
					var creeperElement = elements[pixel.element].creeperType;
					var headName,bodyName;
					if(typeof(creeperElement === "string")) { //comma separated string check
						if(creeperElement.includes(",")) { //if it is
							creeperElement = creeperElement.split(","); //to array
							creeperElement = creeperElement.filter(function(e) { //strip nonexistent elements
								return typeof(elements[e]) === "object";
							});
						};
					};
					if(Array.isArray(creeperElement)) {
						headName = `${creeperElement.join("_")}_creeper_head`; //auto head element name
						bodyName = `${creeperElement.join("_")}_creeper_body`; //auto body element name
					} else {
						headName = `${creeperElement}_creeper_head`; //auto head element name
						bodyName = `${creeperElement}_creeper_body`; //auto body element name
					};
					if (isEmpty(pixel.x, pixel.y+1)) {
						createPixel(bodyName, pixel.x, pixel.y+1);
						pixel.element = headName;
						pixel.color = pixelColorPick(pixel)
					} else if (isEmpty(pixel.x, pixel.y-1)) {
						createPixel(headName, pixel.x, pixel.y-1);
						pixel.element = bodyName;
						pixel.color = pixelColorPick(pixel)
					} else {
						deletePixel(pixel.x, pixel.y);
					};
				};

				autoCreeperBodyTick = function(pixel) {
					var creeperElement = elements[pixel.element].creeperType;
					var headName,bodyName,explodeInto;
					if(typeof(creeperElement === "string")) { //comma separated string check
						if(creeperElement.includes(",")) { //if it is
							creeperElement = creeperElement.split(","); //to array
							creeperElement = creeperElement.filter(function(e) { //strip nonexistent elements
								return typeof(elements[e]) === "object";
							});
						};
					};
					if(Array.isArray(creeperElement)) {
						headName = `${creeperElement.join("_")}_creeper_head`; //auto head element name
						bodyName = `${creeperElement.join("_")}_creeper_body`; //auto body element name
						explodeInto = creeperElement.join(","); //auto body element name
					} else {
						headName = `${creeperElement}_creeper_head`; //auto head element name
						bodyName = `${creeperElement}_creeper_body`; //auto body element name
						explodeInto = creeperElement; //auto body element name
					};
					if (tryMove(pixel, pixel.x, pixel.y+1)) { // Fall
						if (!isEmpty(pixel.x, pixel.y-2, true)) { // Drag head down
							var headPixel = pixelMap[pixel.x][pixel.y-2];
							if (headPixel.element == headName) {
								if (isEmpty(pixel.x, pixel.y-1)) {
									movePixel(pixelMap[pixel.x][pixel.y-2], pixel.x, pixel.y-1);
								}
								else {
									swapPixels(pixelMap[pixel.x][pixel.y-2], pixelMap[pixel.x][pixel.y-1]);
								}
							}
						}
					}
					doHeat(pixel);
					doBurning(pixel);
					doElectricity(pixel);
					if (pixel.dead) {
						// Turn into rotten_meat if pixelTicks-dead > 500
						if (pixelTicks-pixel.dead > 200) {
							Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
						}
						return
					}

					// Find the head
					if (!isEmpty(pixel.x, pixel.y-1, true) && pixelMap[pixel.x][pixel.y-1].element == headName) {
						var head = pixelMap[pixel.x][pixel.y-1];
						if (head.dead) { // If head is dead, kill body
							pixel.dead = head.dead;
						}
					}
					else { var head = null }

					if (isEmpty(pixel.x, pixel.y-1)) {
						// create blood if decapitated 10% chance
						if (Math.random() < 0.1) {
							createPixel("blood", pixel.x, pixel.y-1);
							// set dead to true 15% chance
							if (Math.random() < 0.15) {
								pixel.dead = pixelTicks;
							}
						}
					}
					else if (head == null) { return }
					else if (Math.random() < 0.1) { // Move 10% chance
						var movesToTry = [
							[1*pixel.dir,0],
							[1*pixel.dir,-1],
						];
						// While movesToTry is not empty, tryMove(pixel, x, y) with a random move, then remove it. if tryMove returns true, break.
						while (movesToTry.length > 0) {
							var move = movesToTry.splice(Math.floor(Math.random() * movesToTry.length), 1)[0];
							if (isEmpty(pixel.x+move[0], pixel.y+move[1]-1)) {
								if (tryMove(pixel, pixel.x+move[0], pixel.y+move[1])) {
									movePixel(head, head.x+move[0], head.y+move[1]);
									break;
								};
							};
						};
						// 15% chance to change direction while not chasing a human
						if(!head.following) {
							if (Math.random() < 0.15) {
								pixel.dir *= -1;
								//console.log("*turns around cutely to face ${pixel.dir < 0 ? 'left' : 'right'}*");
							};
						}/* else {
							//console.log("*chases cutely*");
						};*/
					};

					if(pixel.charge) {
						pixel.charged = true;
					};

					if(head) {
						if(typeof(head.charge) !== "undefined") {
							if(head.charge) {
								pixel.charged = true;
							};
						};
						if(typeof(head.charged) !== "undefined") {
							if(head.charged) {
								pixel.charged = true;
							};
						};
					};

					if(typeof(pixel.charged) === "undefined") {
						pixel.charged = false;
					};

					if(pixel.charged) {
						var explosionRadius = 7;
						if(!pixel.didChargeBlueTinted) { //do once, on initial charge
							//console.log("something something halsey lyric");
							var color = pixel.color;
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + 51);
								green = rgbColorBound(green + 51);
								blue = rgbColorBound(blue + 102);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								hue = hue % 360; //piecewise hue shift
								if(hue <= 235 && hue >= 135) {
									hue = 185;
								} else if(hue < 135) {
									hue += 50;
								} else if(hue > 235 && hue < 360) {
									hue -= 50;
								};
								saturation = slBound (saturation + 10);
								luminance = slBound(luminance + 20);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
							pixel.didChargeBlueTinted = true;
						};
					} else {
						var explosionRadius = 5;
					};

					if(pixel.burning) {
						pixel.hissing = true;
						if(!pixel.hissStart) {
							pixel.hissStart = pixelTicks;
						};
						if(!pixel.burnStart) { //I don't like errors.
							pixel.burnStart = pixel.ticks;
						};
						if(pixelTicks - pixel.burnStart > 30) {
							//console.log("Kaboom?");
							explodeAt(pixel.x,pixel.y,explosionRadius,creeperElement);
							//console.log("Yes, Rico, kaboom.");
						};
					};

					//Head hissing color handler: keeps track of head's hissing for coloring purposes
					for(i = 0; i < 1; i++) { //dummy for loop
						if(pixel.dead || !head || head.dead) { //can't hiss without a head according to the classic creeper anatomy
							//console.log("ss-- oof");
							pixel.hissing = false;
							break;
						};
						if(head.hissing) {
							//console.log("Ssssssss");
							if(!head.hissStart) {
								//console.log("t-30 ticks or whatever it was");
								head.hissStart = pixelTicks;
							};

							//Color code {
								var ticksHissing = pixelTicks - head.hissStart;
								var color = pixel.color; //do on each hissing tick
								if(color.startsWith("rgb")) {
									//console.log("rgb detected");
									color = color.split(","); //split color for addition
									var red = parseFloat(color[0].substring(4));
									var green = parseFloat(color[1]);
									var blue = parseFloat(color[2].slice(0,-1));
									red = rgbColorBound(red + ticksHissing);
									green = rgbColorBound(green + ticksHissing);
									blue = rgbColorBound(blue + ticksHissing);
									color = `rgb(${red},${green},${blue})`;
									pixel.color = color;
									//console.log("color set");
								} else if(color.startsWith("hsl")) {
									//console.log("hsl detected");
									color = color.split(","); //split color for addition
									var hue = parseFloat(color[0].substring(4));
									var saturation = parseFloat(color[1].slice(0,-1));
									var luminance = parseFloat(color[2].slice(0,-2));
									//console.log("the j");
									luminance = slBound(luminance + 1.176);
									//console.log(luminance);
									color = `hsl(${hue},${saturation}%,${luminance}%)`;
									pixel.color = color;
									//console.log("color set");
								};
							//}
						};
					};
				};

				autoCreeperHeadTick = function(pixel) {
					var creeperElement = elements[pixel.element].creeperType;
					var headName,bodyName,explodeInto;
					if(typeof(creeperElement === "string")) { //comma separated string check
						if(creeperElement.includes(",")) { //if it is
							creeperElement = creeperElement.split(","); //to array
							creeperElement = creeperElement.filter(function(e) { //strip nonexistent elements
								return typeof(elements[e]) === "object";
							});
						};
					};
					if(Array.isArray(creeperElement)) {
						headName = `${creeperElement.join("_")}_creeper_head`; //auto head element name
						bodyName = `${creeperElement.join("_")}_creeper_body`; //auto body element name
						explodeInto = creeperElement.join(","); //auto body element name
					} else {
						headName = `${creeperElement}_creeper_head`; //auto head element name
						bodyName = `${creeperElement}_creeper_body`; //auto body element name
						explodeInto = creeperElement; //auto body element name
					};
					doHeat(pixel);
					doBurning(pixel);
					doElectricity(pixel);
					if (pixel.dead) {
						// Turn into rotten_meat if pixelTicks-dead > 500
						if (pixelTicks-pixel.dead > 200) {
							Math.random() < 0.1 ? changePixel(pixel,"gunpowder") : changePixel(pixel,"rotten_meat");
							return
						}
					}

					// Find the body
					if (!isEmpty(pixel.x, pixel.y+1, true) && pixelMap[pixel.x][pixel.y+1].element == bodyName) {
						var body = pixelMap[pixel.x][pixel.y+1];
						if (body.dead) { // If body is dead, kill head
							pixel.dead = body.dead;
						}
					}
					else { var body = null }

					if(body) {
						if(body.dir !== pixel.dir) { //hacky workaround: lock head dir to body dir
							pixel.dir = body.dir;
						};
					};

					if (isEmpty(pixel.x, pixel.y+1)) {
						tryMove(pixel, pixel.x, pixel.y+1);
						// create blood if severed 10% chance
						if (isEmpty(pixel.x, pixel.y+1) && !pixel.dead && Math.random() < 0.1) {
							createPixel("blood", pixel.x, pixel.y+1);
							// set dead to true 15% chance
							if (Math.random() < 0.15) {
								pixel.dead = pixelTicks;
							}
						}
					}

					//start of most new code
					var pX = pixel.x;
					var pY = pixel.y;

					if(pixel.charge) {
						pixel.charged = true;
					};

					if(body) {
						if(typeof(body.charge) !== "undefined") {
							if(body.charge) {
								pixel.charged = true;
							};
						};
						if(typeof(body.charged) !== "undefined") {
							if(body.charged) {
								pixel.charged = true;
							};
						};
					};

					if(typeof(pixel.charged) === "undefined") {
						pixel.charged = false;
					};

					if(pixel.charged) {
						var explosionRadius = 10;
						if(!pixel.didChargeBlueTinted) { //do once, on initial charge
							//console.log("something something halsey lyric");
							var color = pixel.color;
							if(color.startsWith("rgb")) {
								//console.log("rgb detected");
								color = color.split(","); //split color for addition
								var red = parseFloat(color[0].substring(4));
								var green = parseFloat(color[1]);
								var blue = parseFloat(color[2].slice(0,-1));
								red = rgbColorBound(red + 51);
								green = rgbColorBound(green + 51);
								blue = rgbColorBound(blue + 102);
								color = `rgb(${red},${green},${blue})`;
								pixel.color = color;
								//console.log("color set");
							} else if(color.startsWith("hsl")) {
								//console.log("hsl detected");
								color = color.split(","); //split color for addition
								var hue = parseFloat(color[0].substring(4));
								var saturation = parseFloat(color[1].slice(0,-1));
								var luminance = parseFloat(color[2].slice(0,-2));
								hue = hue % 360; //piecewise hue shift
								if(hue <= 235 && hue >= 135) {
									hue = 185;
								} else if(hue < 135) {
									hue += 50;
								} else if(hue > 235 && hue < 360) {
									hue -= 50;
								};
								saturation = slBound (saturation + 10);
								luminance = slBound(luminance + 20);
								color = `hsl(${hue},${saturation}%,${luminance}%)`;
								pixel.color = color;
								//console.log("color set");
							};
							pixel.didChargeBlueTinted = true;
						};
					} else {
						var explosionRadius = 7;
					};

					//Human detection loop (looks ahead according to direction and sets the "following" variable to true, telling the body to lock the direction)
					var directionAdverb = "left";
					if(pixel.dir > 0) {
						directionAdverb = "right";
					};
					//console.log(`Looking ${directionAdverb}`)
					if(pixel.dir === -1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = (-1); j > (-16 - 1); j--) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) {
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Start "hissing" if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 3.15) { //probably misapplying the tolerance from the MC Wiki line: "Creepers will chase after any player, as long as it is within a 16 block (±5%) radius"
												pixel.hissing = true;
												if(!pixel.hissStart) {
													pixel.hissStart = pixelTicks;
												};
											};
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break; //can't see through humans
									};
								};
							};
						};
					} else if(pixel.dir === 1) {
						for(i = -4; i < 4+1; i++) {
							var oY = i;
							//console.log(`Starting row look at row ${pY+oY}`)
							for(j = 1; j < 16 + 1; j++) {
								var oX = j;
								var nX = pX+oX;
								var nY = pY+oY;
								if(outOfBounds(nX,nY)) {
									//console.log(`Stopping row look at pixel (${nX},${nY}) due to OoB`)
									break;
								};
								if(isEmpty(nX,nY)) {
									////console.log(`Skipping pixel (${nX},${nY}) (empty)`)
									continue;
								};
								if(!isEmpty(nX,nY,true)) {
									var newPixel = pixelMap[nX][nY];
									var newElement = newPixel.element;
									if(enemyHumanoidArray.includes(newElement)) {
										//console.log(`Human part found at (${nX},${nY})`)
										if(!newPixel.dead) {
											pixel.following = true;
											//console.log(`Human detected at (${nX},${nY})`)
											//Start "hissing" if a human is close enough
											if(coordPyth(pX,pY,nX,nY) <= 3.15) {
												pixel.hissing = true;
												if(!pixel.hissStart) {
													pixel.hissStart = pixelTicks;
												};
											};
											break;
										};
									} else {
										//console.log(`Stopping row look at pixel (${nX},${nY}) due to non-human pixel in the way`)
										break;
									};
								};
							};
						};
					};

					//Pre-explosion handler: keeps track of time before the kaboom
					for(i = 0; i < 1; i++) { //dummy for loop
						if(pixel.hissing) {
							//console.log("Ssssssss");
							if(pixel.dead || !body || body.dead) { //can't explode without a body according to the classic creeper anatomy
								//console.log("ss-- oof");
								pixel.hissing = false;
								break;
							};
							if(!pixel.hissStart) {
								//console.log("t-30 ticks or whatever it was");
								pixel.hissStart = pixelTicks;
							};
							//Color code {
								var ticksHissing = pixelTicks - pixel.hissStart;
								var color = pixel.color; //do on each hissing tick
								if(color.startsWith("rgb")) {
									//console.log("rgb detected");
									color = color.split(","); //split color for addition
									var red = parseFloat(color[0].substring(4));
									var green = parseFloat(color[1]);
									var blue = parseFloat(color[2].slice(0,-1));
									red = rgbColorBound(red + ticksHissing);
									green = rgbColorBound(green + ticksHissing);
									blue = rgbColorBound(blue + ticksHissing);
									color = `rgb(${red},${green},${blue})`;
									pixel.color = color;
									//console.log("color set");
								} else if(color.startsWith("hsl")) {
									//console.log("hsl detected");
									color = color.split(","); //split color for addition
									var hue = parseFloat(color[0].substring(4));
									var saturation = parseFloat(color[1].slice(0,-1));
									var luminance = parseFloat(color[2].slice(0,-2));
									luminance = slBound(luminance + 1.176);
									color = `hsl(${hue},${saturation}%,${luminance}%)`;
									pixel.color = color;
									//console.log("color set");
								};
							//}

							if(pixelTicks - pixel.hissStart > 30) {
								//console.log("Kaboom?");
								//console.log(`Exploding with element ${creeperElement} and radius ${explosionRadius} (charged: ${pixel.charged})`);
								explodeAt(body.x,body.y,explosionRadius,explodeInto);
								//console.log("Yes, Rico, kaboom.");
							};
						};
					};

					if(Math.random() < 0.01) { //1% chance each tick to lose interest
						pixel.following = false;
						//console.log("Meh.");
					};
				};

			//Several

				function commonMovableCriteria(name) {
					if(typeof(elements[name]) !== "object") {
						throw new Error(`Nonexistent element ${name}`);
					};
					var info = elements[name];
					//console.log(`${name} (${JSON.stringify(elements[name])})`);
					if(typeof(info.state) === "undefined") {
						var state = null;
					} else {
						var state = info.state;
					};
					if(typeof(info.category) === "undefined") {
						var category = "other";
					} else {
						var category = info.category;
					};
					if(excludedFairyElements.includes(name) || excludedCloudElements.includes(name)) {
						return false
					};
					if(elements[name].behavior && elements[name].behavior.toString() == elements.wall.behavior.toString() && !elements[name].tick) {
						return false;
					};
					if(["liquid","gas"].includes(state)) {
						return true;
					};
					if(info.movable) {
						return true;
					};
					if(backupCategoryWhitelist.includes(category)) {
						return true;
					};
					if(backupElementWhitelist.includes(name)) {
						return true;
					};
					if(category.includes("mudstone")) {
						return true;
					};
					return false;
				};

		//runAfterLoad calls

			//Bombs

				runAfterLoad(function() {
					amalgamatedBombFire += ",poisonwater".repeat(8);
					amalgamatedBombFire += ",mystic_fire".repeat(4);
					amalgamatedBombFire += ",firesea".repeat(6);
					amalgamatedBombFire += ",lektre".repeat(6);
					amalgamatedBombFire += ",flamer".repeat(3);
					amalgamatedBombFire += ",flamebomb".repeat(3);
					amalgamatedBombFire += ",toxin".repeat(3);
					amalgamatedBombFire += ",burning_unrealistically_flammable_gas".repeat(4);
					amalgamatedBombFire += ",warp".repeat(6);
					amalgamatedBombFire += ",bomb_3".repeat(3);
					amalgamatedBombFire += ",op_hottester_bomb".repeat(3);
					eLists.BOMB.push("unrealistically_flammable_bomb");
					eLists.BOMB.push("warp_bomb");
					amalgamatedBombFire += ",electric_gas".repeat(3);
					amalgamatedBombFire += ",corrosive_gas".repeat(3);
					amalgamatedBombFire += ",iocalfaeus_gas".repeat(3);
					amalgamatedBombFire += ",ignited_gas".repeat(3);
					amalgamatedBombFire += ",finine".repeat(3);
					amalgamatedBombFire += ",acidic_vapour".repeat(3);
					amalgamatedBombFire += ",nitrous_gas".repeat(3);
					amalgamatedBombFire += ",void_gas".repeat(3);
					amalgamatedBombFire += ",black_damp".repeat(3);
					amalgamatedBombFire += ",blazing_pyrotheum".repeat(5);
					amalgamatedBombFire += ",tectonic_petrotheum".repeat(7);
					amalgamatedBombFire += ",resonant_ender".repeat(5);
					amalgamatedBombFire += ",FOOF".repeat(8);
					amalgamatedBombFire += ",liquid_irradium".repeat(7);
					amalgamatedBombFire += ",bioooze".repeat(8);
				});

			//Fairies

				runAfterLoad(function() {
					if(typeof(eLists.FAIRY) === "undefined") { eLists.FAIRY = [] };
					eLists.FAIRY.push("acid_fairy");
					eLists.FAIRY.push("oil_fairy");
					eLists.FAIRY.push("honey_fairy");
					fairyChoices = eLists.FAIRY;
					for(i = 0; i < eLists.FAIRY.length; i++) {
						var fairyName = eLists.FAIRY[i];
						if(!fairyChoices.includes(fairyName)) {
							fairyChoices.push(fairyName);
						};
					};
				});

		//Non-generated elements

			//Bombs

				elements.upward_bomb = {
					color: "#625c71",
					behavior: [
						"M2|M1 AND EX:10|M2",
						"XX|XX|XX",
						"XX|EX:10|XX",
					],
					category: "weapons",
					state: "solid",
					density: 1300,
					excludeRandom: true,
					cooldown: defaultCooldown,
				};

				elements.firebomb = {
					color: "#ee7e3e",
					tick: function(pixel) {
						if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
							var newPixel = pixelMap[pixel.x][pixel.y-1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,10,"fire,fire,fire,fire,fire,greek_fire","fire",null,firebombFire);
								changePixel(pixel,"disappear");
							};
						};
						if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
							var newPixel = pixelMap[pixel.x][pixel.y+1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,10,"fire,fire,fire,fire,fire,greek_fire","fire",null,firebombFire);
								changePixel(pixel,"disappear");
							};
						};
						if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
							explodeAtPlus(pixel.x,pixel.y,10,"fire,fire,fire,fire,fire,greek_fire","fire",null,firebombFire);
							changePixel(pixel,"disappear");
						};
						if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
							Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
						};
					},
					category: "weapons",
					state: "solid",
					density: 1500,
					excludeRandom: true,
					desc: "An advanced incendiary weapon. <br/>To enable automatic bomb generation, set the generateBombs query parameter.",
				};

				function fuckingDeletePixel(pixel) {
					if(
						typeof(pixel) === "object" &&
						pixel !== null &&
						typeof(pixel?.x) === "number" &&
						typeof(pixel?.y) === "number"
					) {
						var pX = pixel.x;
						var pY = pixel.y;
						deletePixel(pX,pY);
						if(typeof(pixel) === "object" && pixel !== null) {
							var index = currentPixels.indexOf(pixel);
							if(index > 0) {
								currentPixels.splice(index)
							};
							pixelMap[pX][pY] = undefined
							pixel = undefined
							return
						}
						return
					};
					return
				};

				elements.force_bomb = {
					color: ["#7a6749", "#828680", "#89a6b6", "#91c5ed"],
					tick: function(pixel) {
						var radius = 8;
						var fire = ["light",null,null,null,null,null,null,null];
						if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
							var newPixel = pixelMap[pixel.x][pixel.y-1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,radius+7,fire,null,null,forcebombVelocity);
								fuckingDeletePixel(pixel); return
							};
						};
						if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
							var newPixel = pixelMap[pixel.x][pixel.y+1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,radius+7,fire,null,null,forcebombVelocity);
								fuckingDeletePixel(pixel); return
							};
						};
						if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
							explodeAtPlus(pixel.x,pixel.y,radius+7,fire,null,null,forcebombVelocity);
							fuckingDeletePixel(pixel); return
						};
						if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
							Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
						};
						return
					},
					category: "weapons",
					state: "solid",
					density: 2000,
					excludeRandom: true,
					desc: "A bomb that sends pixels flying. <br/>To enable automatic bomb generation, set the generateBombs query parameter.",
				};

				elements.cluster_nuke = {
					color: "#e3f636",
					behavior: [
						"CR:radiation%5|EX:90>plasma,plasma,plasma,nuke,nuke,nuke,radiation,radiation,radiation,rad_steam,rad_steam,radiation,rad_steam AND CR:radiation%5|CR:radiation%5",
						"CR:radiation%5|XX|CR:radiation%5",
						"M2 AND CR:radiation%5|M1 AND EX:90>plasma,plasma,plasma,nuke,nuke,nuke,radiation,radiation,radiation,rad_steam,rad_steam,radiation,rad_steam AND CR:radiation%5|M2 AND CR:radiation%5",
					],
					category: "weapons",
					state: "solid",
					density: 1500,
					excludeRandom: true,
					desc: "It's a nuke that drops more nukes. <br/>To enable automatic bomb generation, set the generateBombs query parameter.",
				};

				elements.upward_bomb = {
					color: "#525c61",
					behavior: [
						"M2|M1 AND EX:10|M2",
						"XX|XX|XX",
						"XX|EX:10|XX",
					],
					category: "weapons",
					state: "solid",
					density: 1300,
					excludeRandom: true,
				};

				elements.electric_bomblet = {
					color: "#ffffff",
					behavior: [
						"SH%50|EX:8>electric AND SH%50|SH%50",
						"SH%50|EX:9>electric%0.5|SH%50",
						"M2 AND SH%50|M1 AND SH%50 AND EX:8>electric AND SW:electric|M2 AND SH%50",
					],
					category: "weapons",
					state: "solid",
					density: 1200,
					hidden: true,
					excludeRandom: true,
					hardness: 0.3,
				};
				
				//Wall bomb

				elements.electric_cluster_bomb = {
					color: "#ffffff",
					behavior: [
						"SH%50|EX:8>electric_bomblet AND SH%50|SH%50",
						"SH%50|XX|SH%50",
						"M2 AND SH%50|M1 AND SH%50 AND EX:8>electric_bomblet AND SW:electric|M2 AND SH%50",
					],
					category: "weapons",
					state: "solid",
					density: 1800,
					hidden: true,
					excludeRandom: true,
					hardness: 0.3,
				};

				elements.radioactive_popper = {
					color: "#d6ce72",
					behavior: [
						"XX|EX:7>radiation|XX",
						"XX|XX|XX",
						"M2|M1 AND EX:7>radiation|M2",
					],
					category: "weapons",
					state: "solid",
					density: 1200,
					hidden: true,
					excludeRandom: true,
					hardness: 0.3,
					cooldown: 3,
				};

				elements.acid_bomb = {
					color: "#7d8a63",
					behavior: [
						"XX|EX:15>acid_gas|XX",
						"XX|XX|XX",
						"M2|M1 AND EX:15>acid_gas|M2",
					],
					category: "weapons",
					state: "solid",
					density: 1400,
					excludeRandom: true,
					cooldown: defaultCooldown,
				};

				elements.amalgamated_bomb = {
					color: ["#FF0000","#FF0000","#FFFF00","#FFFF00","#00FF00","#00FF00","#0000FF","#0000FF"],
					tick: function(pixel) {
						doDefaults(pixel);
						if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
							var newPixel = pixelMap[pixel.x][pixel.y-1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,70,amalgamatedBombFire,amalgamatedBombFire);
							};
						};
						if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
							var newPixel = pixelMap[pixel.x][pixel.y+1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,70,amalgamatedBombFire,amalgamatedBombFire);
							};
						};
						if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
							explodeAtPlus(pixel.x,pixel.y,70,amalgamatedBombFire,amalgamatedBombFire);
						};
						if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
							Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
						};
					},
					category: "weapons",
					state: "solid",
					temp: 7065,
					density: 158000,
					excludeRandom: true,
				};

				elements.op_hottester_bomb = {
					color: "#cc436e",
					properties: {
						radius: 15, //just so people can edit it per pixel to be stupidly high
					},
					tick: function(pixel) {
						doDefaults(pixel);
						if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
							var newPixel = pixelMap[pixel.x][pixel.y-1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,pixel.radius,"plasma","plasma",hotterBomb,hotterBomb,false);
							};
						};
						if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
							var newPixel = pixelMap[pixel.x][pixel.y+1];
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,pixel.radius,"plasma","plasma",hotterBomb,hotterBomb,false);
							};
						};
						if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
							explodeAtPlus(pixel.x,pixel.y,pixel.radius,"plasma","plasma",hotterBomb,hotterBomb,false);
						};
						if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
							Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
						};
					},
					category: "weapons",
					state: "solid",
					temp: 7065,
					density: 1900,
					excludeRandom: true,
				};

				elements.op_electromagneticester_emp = {				
					color: "#818253",
					tick: function(pixel) { //replace EMP
						if (pixel.start===pixelTicks) {return}
						if (!tryMove(pixel,pixel.x,pixel.y+1)) {
							if (outOfBounds(pixel.x,pixel.y+1) || (pixelMap[pixel.x][pixel.y+1].element!=="emp_bomb" && elements[pixelMap[pixel.x][pixel.y+1].element].state!=="gas")) {
								for (i = 0; i < currentPixels.length; i++) {
									var newPixel = currentPixels[i];
									if (newPixel.charge) {
										delete newPixel.charge;
										newPixel.chargeCD = 50;
									}
								}
								explodeAtPlus(pixel.x,pixel.y+1,20,"electric","smoke",empCharge,null,false);
							}
						}
						doDefaults(pixel);
					},
					maxSize: 1,
					category: "weapons",
					state: "solid",
					density: 1500,
					excludeRandom: true,
					alias: "overpowered electromagnetic pulse bomb",
					cooldown: defaultCooldown
				};

				elements.star_bomb = {
					color: "#fffbb5",
					properties: {
						radius: 50, //just so people can edit it per pixel to be stupidly high
					},
					tick: function(pixel) {
						var starFire = "stellar_plasma,stellar_plasma,stellar_plasma,liquid_stellar_plasma,liquid_stellar_plasma,plasma,plasma";
						var starSmoke = "light,light,radiation";
						doDefaults(pixel);
						if(!isEmpty(pixel.x,pixel.y-1,true)) { //[0][1] EX (ignore bounds)
							var newPixel = pixelMap[pixel.x][pixel.y-1];
							newPixel.temp += 10000000; //[0][1] HT:10000000
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,pixel.radius,starFire,starSmoke,starbombHeat,starbombHeat,false);
							};
						};
						if(!isEmpty(pixel.x,pixel.y+1,true)) { //[2][1] EX (don't ignore bounds, non-bound case)
							var newPixel = pixelMap[pixel.x][pixel.y+1];
							newPixel.temp += 10000000;
							var newElement = newPixel.element;
							var newInfo = elements[newElement];
							if(newInfo.state !== "gas" && newElement !== pixel.element) {
								explodeAtPlus(pixel.x,pixel.y,pixel.radius,starFire,starSmoke,starbombHeat,starbombHeat,false);
							};
						};
						if(outOfBounds(pixel.x,pixel.y+1)) { //[2][1] EX (don't ignore bounds, bound case)
							explodeAtPlus(pixel.x,pixel.y,pixel.radius,starFire,starSmoke,starbombHeat,starbombHeat,false);
						};
						if(!tryMove(pixel,pixel.x,pixel.y+1)) { //behaviors.POWDER
							Math.random() < 0.5 ? tryMove(pixel,pixel.x-1,pixel.y+1) : tryMove(pixel,pixel.x+1,pixel.y+1);
						};
					},
					category: "weapons",
					state: "solid",
					density: 3.663e33,
					excludeRandom: true,
					cooldown: defaultCooldown
				};

			//Fairies

				elements.acid_fairy = {
					name: "acid fairy",
					color: ["#e2f777","#d1ff94","#d8f7c1"],
					behavior: [
						"XX|M1|M1",
						"XX|FX%5|XX",
						"XX|CR:acid%0.5 AND CR:fairy_dust%0.005 AND M1|M1",
					],
					state: "solid",
					category: "fey",
					desc: "Like the other fairies, but with acid. <br/>To enable automatic fairy generation, set the generateFairies query parameter.",
				}

				elements.oil_fairy = {
					name: "oil fairy",
					color: ["#636360","#a6956f","#a3816d","#cfc191"],
					behavior: [
						"XX|M1|M1",
						"XX|FX%5|XX",
						"XX|CR:oil%0.5 AND CR:fairy_dust%0.005 AND M1|M1",
					],
					state: "solid",
					category: "fey",
					desc: "Like the other fairies, but with oil. <br/>To enable automatic fairy generation, set the generateFairies query parameter.",
				}

				elements.honey_fairy = {
					name: "honey fairy",
					color: ["#ffeaa6","#ffe987","#f2e7c2"],
					behavior: [
						"XX|M1|M1",
						"XX|FX%5|XX",
						"XX|CR:honey%0.5 AND CR:fairy_dust%0.005 AND M1|M1",
					],
					state: "solid",
					category: "fey",
					desc: "Like the other fairies, but with sweet honey. <br/>To enable automatic fairy generation, set the generateFairies query parameter.",
				}

		//Sequential generations

			//Bombs

				for (var i = 2; i <= bombAmount + 1; i++) {
					elements[`bomb_${i}`] = {
						name: `bomb ${i}`,
						color: "#624c41",
						behavior: [
							`XX|EX:${5*(i+1)}>fire|XX`,
							"XX|XX|XX",
							`M2|M1 AND EX:${5*(i+1)}>fire|M2`,
						],
						state: "solid",
						density: 1300 * 8**((i-1)/2),
						excludeRandom:true,
						category: "weapons",
						desc: `${5*(i+1)/10} times the radius of the regular bomb`,
						cooldown: defaultCooldown,
					};
					eLists.BOMB.push(`bomb_${i}`);
				};

				for (var i = 2; i <= bombAmount + 1; i++) {
					elements[`upward_bomb_${i}`] = {
						color: "#625c71",
						behavior: [
							`M2|M1 AND EX:${5*(i+1)}>fire|M2`,
							"XX|XX|XX",
							`XX|EX:${5*(i+1)}>fire|XX`,
						],
						state: "solid",
						density: 1300 * 8**((i-1)/2),
						excludeRandom:true,
						category: "weapons",
						desc: `${5*(i+1)/10} times the radius of the regular anti-bomb`,
						cooldown: defaultCooldown,
					};
					eLists.BOMB.push(`upward_bomb_${i}`);
				};

			//Fairies

				//For statement by charPointer
				for (var i = 2; i <= fairyAmount + 1; i++) {
				  elements[`${i}-fairy`] = {
					name: `${i}-fairy`,
					color: ["#33007a","#8e009f","#09009f"],
					behavior: [
					  "XX|M1|M1",
					  "XX|FX%5|XX",
					  `XX|CR:${i-1}-fairy%1 AND CR:fairy_dust%0.005 AND M1|M1`,
					],
					reactions: {
					  "glitter": { "elem1": `${i+1}-fairy`, "elem2": null }
					},
					state: "solid",
					excludeRandom:true,
					category: "fey",
					hidden: true,
				  }
				  if (i == fairyAmount) { elements[`${i}-fairy`]["reactions"] = {}; }
				  if (i == 2) elements[`${i}-fairy`]["behavior"][2] = `XX|CR:fairy%1 AND CR:fairy_dust%0.005 AND M1|M1`;
				  eLists.FAIRY.push(`${i}-fairy`);
				}
				//post-gen
				elements.rainbow.reactions.fairy = { "elem1": "2-fairy", "elem2": null }
				//remove last fairy's reaction
				delete elements[`${fairyAmount + 1}-fairy`].reactions

		//Main generator functions

			//Bombs

				generateBomb = function(bombElements,isAfterScriptLoading=false,bombNumber=1) {//it can be a single element, though
					bombNumber = Math.max(0,bombNumber);

					//To specify an array bomb, have the array be inside another array.
					/*For reasons related to how element colors are loaded, if this function is being run from a JS mod file, isAfterScriptLoading should be false.
					Otherwise, you'll get TypeErrors for some reason when trying to place your bomb.  If this is being run after the game has loaded (e.g. in the console),
					then isAfterScriptLoading should be true or you might also get TypeErrors (this latter case was a bit inconsistent when I tested it, but 
					the former case wasn't. **isAfterScriptLoading must be false when this function is run from a JS mod file**.*/
					if(typeof(bombElements) === "string") { //it should be an array, so string check
						//console.log("String detected");
						if(bombElements.includes(",")) { //comma-separated string?
							//console.log("Splitting string to array");
							bombElements = bombElements.split(","); //,SS to array
						} else {
							//console.log("Wrapping string in array");
							bombElements = [bombElements]; //single string to array 
						};
					};
					var returns = [];
					for(aaf = 0; aaf < bombElements.length; aaf++) {
						var elementOfBomb = bombElements[aaf];
						//console.log("1",elementOfBomb);
						var allElementsHaveColors = ((elementOfBomb instanceof Array ? elementOfBomb : [elementOfBomb]).map(function(name) { return !!(elements[name]?.color)}).reduce(function(a,b) { return a*b }) == 1)
						if(!allElementsHaveColors) { continue };
						var startColor;
						var randomExcl = 0;
						//console.log(elementOfBomb);

						var bombName;

						//console.log("2-1");
						if(typeof(elementOfBomb === "string")) { //comma separated string check
							if(elementOfBomb.includes(",")) { //if it is
								elementOfBomb = elementOfBomb.split(","); //to array
								elementOfBomb = elementOfBomb.filter(function(e) { //strip nonexistent elements
									//console.log("3 a");
									return typeof(elements[e]) === "object";
								});
							};
						};
						if(Array.isArray(elementOfBomb)) {
							bombName = `${elementOfBomb.join("_")}_bomb`; //auto placer element name

							//array case color concatenator (bombs are always excludeRandom)
							startColor = [];
							//console.log(elementOfBomb);
							for(ll = 0; ll < elementOfBomb.length; ll++) {
								if(typeof(elements[elementOfBomb[ll]].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
									if(elements[elementOfBomb[ll]].excludeRandom) { //it it's true
										randomExcl = 1; //the whole array bomb is excluded (from spawnRandomBomb)
										//console.log("array nyet" + elementOfBomb);
									};
								};
								startColor = startColor.concat(elements[elementOfBomb[ll]].color);
							};
						} else { //they should all be strings, so here
							bombName = `${elementOfBomb}_bomb`; //auto placer element name
							startColor = elements[elementOfBomb].color;
							if(typeof(elements[elementOfBomb].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
								if(elements[elementOfBomb].excludeRandom) { //it it's true
									//console.log("nyet " + elementOfBomb);
									randomExcl = 1; //the bomb is excluded
								} else {
									//console.log("allow " + elementOfBomb);
									randomExcl = 0;
								};
							};
						};
						//console.log("e",bombName);

							//Color gen
						if(Array.isArray(startColor)) { //Average arrays, make colors rgb()
							startColor = averageRgbPrefixedColorArray(startColor);
						} else {
							startColor = rgbHexCatcher(startColor);
						};

						startColor = addColors(changeLuminance(changeSaturation(startColor,0.6,"multiply","hsl_json"),0.5,"multiply","rgb"),"rgb(24,0,0)","rgb");

						var newColorObject = rgbStringToObject(startColor);

							//End color gen

														//The bomb

						//console.log(elementOfBomb);
						var firstInfo, firstTemp;
						if(Array.isArray(elementOfBomb)) {
							firstInfo = elements[elementOfBomb[0]];
							firstTemp = airTemp;
							if(typeof(firstInfo.temp) !== "undefined") {
								firstTemp = firstInfo.temp;
							};
						} else {
							firstInfo = elements[elementOfBomb];
							firstTemp = airTemp;
							if(typeof(firstInfo.temp) !== "undefined") {
								firstTemp = firstInfo.temp;
							};
						};

						elementOfBomb = tryJoin(elementOfBomb,",");
						descElement = tryJoin(elementOfBomb,", ");

						//console.log(elementOfBomb);

						if(bombNumber !== 1) {
							bombName += `_${bombNumber}`;
						};

						if(!elementExists(bombName)) {
							elements[bombName] = {
								color: startColor,
								insulate: true,
								flippableX: true,
								colorObject: newColorObject,
								behavior: [
									["XX",`EX:${5*(bombNumber+1)}>${elementOfBomb}`,"XX"],
									["XX","XX","XX"],
									["M2",`M1 AND EX:${5*(bombNumber+1)}>${elementOfBomb}`,"M2"]
								],
								category: "auto_bombs",
								desc: `Explodes into ${descElement}<br/>Radius: ${5*(bombNumber+1)}`,
								temp: firstTemp,
								excludeRandom: true,
								autoType: "bomb",
							};
							if(typeof(eLists) === "undefined") {
								eLists = {};
							};
							if(typeof(eLists.BOMB) === "undefined") {
								eLists.BOMB = [];
							};
							eLists.BOMB.push(bombName);
							if(!randomExcl) {
								if(typeof(bombChoices) === "undefined") {
									bombChoices = []
								};
								if(!bombChoices.includes(bombName)) {
									bombChoices.push(bombName);
								};
							}
							if(isAfterScriptLoading) {
								elements[bombName].flippableX = true;
								elementCount++; //increment for new bomb element
								createElementButton(bombName);
								elements[bombName].id = nextid++;
								document.getElementById("extraInfo").innerHTML = "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>"; //update extra info counts (and the copyright year, due to the method used)
							};
						};
						returns.push(bombName);
					};
					return returns;
				};

			//Clouds

			//Clouds

				generateCloud = function(cloudElements,cloudType=0,isAfterScriptLoading=false) {//it can be a single element, though
					//To specify an array cloud, have the array be inside another array.
					/*For reasons related to how element colors are loaded, if this function is being run from a JS mod file, isAfterScriptLoading should be false.
					Otherwise, you'll get TypeErrors for some reason when trying to place your cloud.  If this is being run after the game has loaded (e.g. in the console),
					then isAfterScriptLoading should be true or you might also get TypeErrors (this latter case was a bit inconsistent when I tested it, but 
					the former case wasn't. **isAfterScriptLoading must be false when this function is run from a JS mod file**.*/
					if(cloudType !== "*") {
						if(typeof(cloudElements) === "string") { //it should be an array, so string check
							//console.log("String detected");
							if(cloudElements.includes(",")) { //comma-separated string?
								//console.log("Splitting string to array");
								cloudElements = cloudElements.split(","); //,SS to array
							} else {
								//console.log("Wrapping string in array");
								cloudElements = [cloudElements]; //single string to array 
							};
						};
						var returns = [];
						for(aaf = 0; aaf < cloudElements.length; aaf++) {
							var elementOfCloud = cloudElements[aaf];
							var allElementsHaveColors = ((elementOfCloud instanceof Array ? elementOfCloud : [elementOfCloud]).map(function(name) { return !!(elements[name]?.color)}).reduce(function(a,b) { return a*b }) == 1)
							if(!allElementsHaveColors) { continue };
							var startColor;
							var randomExcl = 0;
							//console.log("randomExcl set")
							//console.log(elementOfCloud);

							var cloudName, cloudPrefix, cloudBehavior;

							switch(cloudType) {
								case 0:
									cloudPrefix = "";
									cloudBehavior = behaviorGenerators.cloud(elementOfCloud);
									break;
								case 1:
									cloudPrefix = "heavy_";
									cloudBehavior = behaviorGenerators.heavy_cloud(elementOfCloud);
									break;
								case 2:
									cloudPrefix = "heavier_";
									cloudBehavior = behaviorGenerators.heavier_cloud(elementOfCloud);
									break;
								case 3:
									cloudPrefix = "heaviest_";
									cloudBehavior = behaviorGenerators.heaviest_cloud(elementOfCloud);
									break;
								case 4:
									cloudPrefix = "heaviester_";
									cloudBehavior = behaviorGenerators.heaviester_cloud(elementOfCloud);
									break;
								case 5:
									cloudPrefix = "heaviestest_";
									cloudBehavior = behaviorGenerators.heaviestest_cloud(elementOfCloud);
									break;
								default:
									throw new RangeError("Cloud type must be between 0 and 5");
							};

							if(typeof(elementOfCloud === "string")) { //comma separated string check
								if(elementOfCloud.includes(",")) { //if it is
									elementOfCloud = elementOfCloud.split(","); //to array
									elementOfCloud = elementOfCloud.filter(function(e) { //strip nonexistent elements
										return typeof(elements[e]) === "object";
									});
								};
							};
							if(Array.isArray(elementOfCloud)) {
								cloudName = `${elementOfCloud.join("_")}_cloud`; //auto placer element name

								//array case color concatenator and excludeRandom handler
								startColor = [];
								//console.log(elementOfCloud);
								for(ll = 0; ll < elementOfCloud.length; ll++) {
									if(typeof(elements[elementOfCloud[ll]].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
										if(elements[elementOfCloud[ll]].excludeRandom) { //it it's true
											randomExcl = 1; //the whole array cloud is excluded
											//console.log("array nyet" + elementOfCloud);
										};
									};
									//console.log(elementOfCloud[ll]);
									startColor = startColor.concat(elements[elementOfCloud[ll]].color);
								};
							} else { //they should all be strings, so here
								cloudName = `${elementOfCloud}_cloud`; //auto placer element name
								startColor = elements[elementOfCloud].color;
								if(typeof(elements[elementOfCloud].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
									if(elements[elementOfCloud].excludeRandom) { //it it's true
										//console.log("nyet " + elementOfCloud);
										randomExcl = 1; //the cloud is excluded
									} else {
										//console.log("allow " + elementOfCloud);
										randomExcl = 0;
									};
								};
							};
								//Color gen
							if(Array.isArray(startColor)) { //Average arrays, make colors rgb()
								startColor = averageRgbPrefixedColorArray(startColor);
							} else {
								startColor = rgbHexCatcher(startColor);
							};

							startColor = changeLuminance(
								changeSaturation(
									startColor,
									(0.4 + (cloudType/10)),
									"multiply",
									"hsl_json"
								),
								(0.6 - (cloudType/10)),
								"multiply",
								"rgb"
							);

							var newColorObject = rgbStringToObject(startColor);

								//End color gen

															//The cloud

							//console.log(elementOfCloud);
							var firstInfo, firstTemp;
							if(Array.isArray(elementOfCloud)) {
								firstInfo = elements[elementOfCloud[0]];
								firstTemp = airTemp;
								if(typeof(firstInfo.temp) !== "undefined") {
									firstTemp = firstInfo.temp;
								};
							} else {
								firstInfo = elements[elementOfCloud];
								firstTemp = airTemp;
								if(typeof(firstInfo.temp) !== "undefined") {
									firstTemp = firstInfo.temp;
								};
							};

							elementOfCloud = tryJoin(elementOfCloud,",");

							//console.log(elementOfCloud);

							cloudName = cloudPrefix + cloudName;

							if(elementExists(cloudName)) {
								if(elements[cloudName].autoType) { continue } else { cloudName = "auto_" + cloudName }
							};

							elements[cloudName] = {
								color: startColor,
								cloudType: elementOfCloud,
								insulate: true,
								colorObject: newColorObject,
								behavior: cloudBehavior,
								category: "clouds",
								temp: firstTemp,
								state: "gas",
								density: 0.6 * (2**cloudType),
								ignoreAir: true,
								conduct: 0.01 * (2**cloudType),
								autoType: "cloud",
								isGas: true
							};

							if(cloudType === 4) { //column tick for heaviester clouds
								elements[cloudName].tick = function(pixel) {
									singleColumn(pixel,elements[pixel.element].cloudType,0.01,0.3);
								};
							};

							if(cloudType === 5) { //three-column tick for heaviestest clouds
								elements[cloudName].tick = function(pixel) {
									multiColumn(pixel,elements[pixel.element].cloudType,0.02,0.4,1);
								};
							};

							eLists.CLOUD.push(cloudName);

							if(!randomExcl) {
								if(typeof(cloudChoices) === "undefined") {
									cloudChoices = []
								};
								if(!cloudChoices.includes(cloudName)) {
									cloudChoices.push(cloudName);
								};
							}
							if(cloudIncludeRandom) {
								randomExcl ? elements[cloudName].excludeRandom = true : elements[cloudName].excludeRandom = false;
							} else {
								elements[cloudName].excludeRandom = true;
							};
							if(isAfterScriptLoading) {
								elementCount++; //increment for new cloud element
								createElementButton(cloudName);
								elements[cloudName].id = nextid++;
								document.getElementById("extraInfo").innerHTML = "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>"; //update extra info counts (and the copyright year, due to the method used)
							};
							returns.push(cloudName);
						};
						return returns;
					} else if(cloudType === "*") {
						var trueReturns = [];
						for(cloudTypeForIndex = 0; cloudTypeForIndex <= 5; cloudTypeForIndex++) {
							trueReturns = trueReturns.concat(generateCloud(cloudElements,cloudTypeForIndex,isAfterScriptLoading));
						};
						return trueReturns;
					};
				};

			//Creepers

				generateCreeper = function(creeperElements,isAfterScriptLoading=false) {//it can be a single element, though
					//To specify an array creeper, have the array be inside another array.
					/*For reasons related to how element colors are loaded, if this function is being run from a JS mod file, isAfterScriptLoading should be false.
					Otherwise, you'll get TypeErrors for some reason when trying to place your creeper.  If this is being run after the game has loaded (e.g. in the console),
					then isAfterScriptLoading should be true or you might also get TypeErrors (this latter case was a bit inconsistent when I tested it, but 
					the former case wasn't. **isAfterScriptLoading must be false when this function is run from a JS mod file**.
					If isAfterScriptLoading is true, buttons (depending on the hiding setting) will be added to the auto creeper category, the 3 new elements per creeper will be assigned IDs, and the footer will be updated with the increased element counts.*/
					if(typeof(creeperElements) === "string") { //it should be an array, so string check
						//console.log("String detected");
						if(creeperElements.includes(",")) { //comma-separated string?
							//console.log("Splitting string to array");
							creeperElements = creeperElements.split(","); //,SS to array
						} else {
							//console.log("Wrapping string in array");
							creeperElements = [creeperElements]; //single string to array 
						};
					};
					var returns = [];
					for(aaf = 0; aaf < creeperElements.length; aaf++) {
						var elementOfCreeper = creeperElements[aaf];
						var allElementsHaveColors = ((elementOfCreeper instanceof Array ? elementOfCreeper : [elementOfCreeper]).map(function(name) { return !!(elements[name]?.color)}).reduce(function(a,b) { return a*b }) == 1)
						if(!allElementsHaveColors) { continue };
						var startColor;
						var randomExcl = 0;
						//console.log("randomExcl set")
						//console.log(elementOfCreeper);

						var headName,bodyName,placerName,descElement;

						if(typeof(elementOfCreeper === "string")) { //comma separated string check
							if(elementOfCreeper.includes(",")) { //if it is
								elementOfCreeper = elementOfCreeper.split(","); //to array
								elementOfCreeper = elementOfCreeper.filter(function(e) { //strip nonexistent elements
									return typeof(elements[e]) === "object";
								});
							};
						};
						if(Array.isArray(elementOfCreeper)) {
							headName = `${elementOfCreeper.join("_")}_creeper_head`; //auto head element name
							bodyName = `${elementOfCreeper.join("_")}_creeper_body`; //auto body element name
							placerName = `${elementOfCreeper.join("_")}_creeper`; //auto placer element name
							descElement = elementOfCreeper.join(", "); //auto explosion element list

							//array case color concatenator and excludeRandom handler
							startColor = [];
							//console.log(elementOfCreeper);
							for(ll = 0; ll < elementOfCreeper.length; ll++) {
								if(typeof(elements[elementOfCreeper[ll]].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
									if(elements[elementOfCreeper[ll]].excludeRandom) { //it it's true
										randomExcl = 1; //the whole array creeper is excluded
										//console.log("array nyet" + elementOfCreeper);
									};
								};
								//console.log(elementOfCreeper[ll]);
								startColor = startColor.concat(elements[elementOfCreeper[ll]].color);
							};
						} else { //they should all be strings, so here
							headName = `${elementOfCreeper}_creeper_head`; //auto head element name
							bodyName = `${elementOfCreeper}_creeper_body`; //auto body element name
							placerName = `${elementOfCreeper}_creeper`; //auto placer element name
							descElement = elementOfCreeper; //auto explosion element
							startColor = elements[elementOfCreeper].color;
							if(typeof(elements[elementOfCreeper].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
								if(elements[elementOfCreeper].excludeRandom) { //it it's true
									//console.log("nyet " + elementOfCreeper);
									randomExcl = 1; //the creeper is excluded
								} else {
									//console.log("allow " + elementOfCreeper);
									randomExcl = 0;
								};
							};
						};
							//Color gen
						if(Array.isArray(startColor)) { //Average arrays, make colors rgb()
							startColor = averageRgbPrefixedColorArray(startColor);
						} else {
							startColor = rgbHexCatcher(startColor);
						};
						var preColor = rgbStringToHSL(startColor);
						var colorsArray = [preColor, preColor, preColor, preColor, preColor, preColor, preColor, preColor, preColor, preColor]
						var colorObjectArray = [];
						for(q = 0; q < hslOffsets.length; q++) {
							colorsArray[q] = addArraysInPairs(colorsArray[q],hslOffsets[q]);
							colorsArray[q] = hslToHex(...colorsArray[q]);
							colorObjectArray[q] = hexToRGB(colorsArray[q]); //outputs hex
							if(isAfterScriptLoading) { // if it's after the hex -> RGB conversion
								var coq = colorObjectArray[q]; //pull the object
								//console.log(coq.r);
								colorsArray[q] = `rgb(${coq.r},${coq.g},${coq.b})`; //and change to the RGB from its values
							};
						};

							//End color gen

						//console.log(`${headName}; ${bodyName}; ${placerName}; ${descElement}`)

														//Placer
						elements[placerName] = {
							movable: true,
							creeperType: elementOfCreeper,
							color: colorsArray,
							colorObject: colorObjectArray,
							category: "auto creepers",
							properties: {
								dead: false,
								dir: 1,
								panic: 0,
								following: false,
							},
							tick: function(pixel) {
								autoCreeperPlacerTick(pixel);
							},
							related: [bodyName,headName,"creeper"],
							desc: `Auto-generated creeper.<br/>Explodes into ${descElement}.`,
							autoType: "creeper",
						};

						eLists.CREEPER.push(placerName);

														//Body
						elements[bodyName] = {
							movable: true,
							creeperType: elementOfCreeper,
							color: colorsArray,
							colorObject: colorObjectArray,
							category: "auto creepers",
							hidden: true,
							excludeRandom: true,
							density: 1500,
							state: "solid",
							conduct: 25,
							tempHigh: 250,
							stateHigh: "cooked_meat",
							tempLow: -30,
							stateLow: "frozen_meat",
							burn: 10,
							burnTime: 250,
							burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
							breakInto: ["blood","gunpowder"],
							reactions: {
								"cancer": { "elem1":"cancer", "chance":0.005 },
								"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
								"plague": { "elem1":"plague", "chance":0.05 },
							},
							properties: {
								dead: false,
								dir: 1,
								panic: 0,
								charged: false,
								didChargeBlueTinted: false,
							},
							tick: function(pixel) {
								autoCreeperBodyTick(pixel);
							},
							autoType: "creeper",
						};

														//Head
						elements[headName] = {
							movable: true,
							creeperType: elementOfCreeper,
							color: colorsArray,
							colorObject: colorObjectArray,
							category: "auto creepers",
							hidden: true,
							excludeRandom: true,
							density: 1080,
							state: "solid",
							conduct: 25,
							tempHigh: 250,
							stateHigh: "cooked_meat",
							tempLow: -30,
							stateLow: "frozen_meat",
							burn: 10,
							burnTime: 250,
							burnInto: ["cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","cooked_meat","gunpowder"],
							breakInto: "blood",
							reactions: {
								"cancer": { "elem1":"cancer", "chance":0.005 },
								"radiation": { "elem1":["ash","meat","rotten_meat","cooked_meat"], "chance":0.4 },
								"plague": { "elem1":"plague", "chance":0.05 },
								"oxygen": { "elem2":"carbon_dioxide", "chance":0.5 },
							},
							properties: {
								dead: false,
								following: false,
								hissing: false,
								charged: false,
								didChargeBlueTinted: false,
							},
							tick: function(pixel) {
								autoCreeperHeadTick(pixel);
							},
							autoType: "creeper",
						};
						if(isAfterScriptLoading) {
							elementCount += 3; //placer, body, head
							createElementButton(placerName)
							if(settings["unhide"] === 0) { //hide some elements: body and head would be hidden, so only update hidden count
								hiddenCount += 2;
							} else if(settings["unhide"] === 1) { //unhide all elements: b/h would not be hidden, so only create their buttons
								createElementButton(bodyName);
								createElementButton(headName);
							} else if(settings["unhide"] === 2) {
								settings.unlocked[bodyName] ? createElementButton(bodyName) : hiddenCount++; //ternary: if headName is unlocked, create button, else increase hiddenCount
								settings.unlocked[headName] ? createElementButton(headName) : hiddenCount++; //above with headName
							};
							elements[placerName].id = nextid++; //set placer's id to nextid and then increment nextid 
							elements[bodyName].id = nextid++; //repeat with body and head
							elements[headName].id = nextid++;
							headBodyObject[headName] = bodyName;
							document.getElementById("extraInfo").innerHTML = "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>"; //update extra info counts (and the copyright year, due to the method used)
						};
						if(creeperIncludeRandom) {
							randomExcl ? elements[placerName].excludeRandom = true : elements[placerName].excludeRandom = false;
						} else {
							elements[placerName].excludeRandom = true;
						};
						if(!randomExcl) {
							//console.log("spawn enabling " + placerName);
							spawnCreepers.push(placerName);
						} else {
							//console.log("nyetted " + placerName);
						};
						returns.push(placerName);
					};
					return returns;
				};

			//Fairies

				generateFairy = function(fairyElements,isAfterScriptLoading=false) {//it can be a single element, though
					//To specify an array fairy, have the array be inside another array.
					/*For reasons related to how element colors are loaded, if this function is being run from a JS mod file, isAfterScriptLoading should be false.
					Otherwise, you'll get TypeErrors for some reason when trying to place your fairy.  If this is being run after the game has loaded (e.g. in the console),
					then isAfterScriptLoading should be true or you might also get TypeErrors (this latter case was a bit inconsistent when I tested it, but 
					the former case wasn't. **isAfterScriptLoading must be false when this function is run from a JS mod file**.*/
					if(typeof(fairyElements) === "string") { //it should be an array, so string check
						//console.log("String detected");
						if(fairyElements.includes(",")) { //comma-separated string?
							//console.log("Splitting string to array");
							fairyElements = fairyElements.split(","); //,SS to array
						} else {
							//console.log("Wrapping string in array");
							fairyElements = [fairyElements]; //single string to array 
						};
					};
					var returns = [];
					for(aaf = 0; aaf < fairyElements.length; aaf++) {
						var elementOfFairy = fairyElements[aaf];
						var allElementsHaveColors = ((elementOfFairy instanceof Array ? elementOfFairy : [elementOfFairy]).map(function(name) { return !!(elements[name]?.color)}).reduce(function(a,b) { return a*b }) == 1)
						if(!allElementsHaveColors) { continue };
						var startColor;
						var randomExcl = 0;
						var isNocheer = 0;
						//console.log("randomExcl set")
						//console.log(elementOfFairy);

						var fairyName;

						if(typeof(elementOfFairy === "string")) { //comma separated string check
							if(elementOfFairy.includes(",")) { //if it is
								elementOfFairy = elementOfFairy.split(","); //to array
								elementOfFairy = elementOfFairy.filter(function(e) { //strip nonexistent elements
									return typeof(elements[e]) === "object";
								});
							};
						};
						if(Array.isArray(elementOfFairy)) {
							fairyName = `${elementOfFairy.join("_")}_fairy`; //auto placer element name

							//array case color concatenator and excludeRandom handler
							startColor = [];
							//console.log(elementOfFairy);
							for(ll = 0; ll < elementOfFairy.length; ll++) {
								if(typeof(elements[elementOfFairy[ll]].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
									if(elements[elementOfFairy[ll]].excludeRandom) { //it it's true
										randomExcl = 1; //the whole array fairy is excluded
										//console.log("array nyet" + elementOfFairy);
									};
								};
								//console.log(elementOfFairy[ll]);
								startColor = startColor.concat(elements[elementOfFairy[ll]].color);
							};

							for(ll = 0; ll < elementOfFairy.length; ll++) {
								if(typeof(elements[elementOfFairy[ll]].nocheer !== "undefined")) { //if nocheer exists (prevent TypeError)
									if(elements[elementOfFairy[ll]].nocheer) { //it it's true
										isNocheer = 1; //the whole array fairy is excluded
										//console.log("array nyet" + elementOfFairy);
									};
								};
								//console.log(elementOfFairy[ll]);
								startColor = startColor.concat(elements[elementOfFairy[ll]].color);
							};
						} else { //they should all be strings, so here
							fairyName = `${elementOfFairy}_fairy`; //auto placer element name
							startColor = elements[elementOfFairy].color;
							if(typeof(elements[elementOfFairy].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
								if(elements[elementOfFairy].excludeRandom) { //it it's true
									//console.log("nyet " + elementOfFairy);
									randomExcl = 1; //the fairy is excluded
								} else {
									//console.log("allow " + elementOfFairy);
									randomExcl = 0;
								};
							};

							if(typeof(elements[elementOfFairy].nocheer !== "undefined")) { //if nocheer exists (prevent TypeError)
								if(elements[elementOfFairy].nocheer) { //it it's true
									//console.log("nyet " + elementOfFairy);
									isNocheer = 1; //the fairy is excluded
								} else {
									//console.log("allow " + elementOfFairy);
									isNocheer = 0;
								};
							};
						};
							//Color gen
						if(Array.isArray(startColor)) { //Average arrays, make colors rgb()
							startColor = averageRgbPrefixedColorArray(startColor);
						} else {
							startColor = rgbHexCatcher(startColor);
						};

						//console.log(`rgbStringToObject(${startColor}) from more_fairies.js`)
						var newColorObjectArray = [];			
						var newColorArray = [];

						for(i = 0; i < elements.fairy.color.length; i++) {
							var newFairyColorlet = elements.fairy.color[i];
							var newColor = multiplyColors(startColor,newFairyColorlet);
							var newColorJSON = multiplyColors(startColor,newFairyColorlet,"json");
							newColorArray.push(newColor);
							newColorObjectArray.push(newColorJSON);
						};

							//End color gen

														//The fairy

						//console.log(elementOfFairy);
						var firstInfo, firstTemp;
						if(Array.isArray(elementOfFairy)) {
							firstInfo = elements[elementOfFairy[0]];
							firstTemp = airTemp;
							if(typeof(firstInfo.temp) !== "undefined") {
								firstTemp = firstInfo.temp;
							};
						} else {
							firstInfo = elements[elementOfFairy];
							firstTemp = airTemp;
							if(typeof(firstInfo.temp) !== "undefined") {
								firstTemp = firstInfo.temp;
							};
						};

						elementOfFairy = tryJoin(elementOfFairy,",");

						//console.log(elementOfFairy);

						if(!elementExists(fairyName)) {
							elements[fairyName] = {
								color: newColorArray,
								insulate: true,
								flippableX: true,
								colorObject: newColorObjectArray,
								behavior: [
									["XX","M1","M1"],
									["XX","FX%5","XX"],
									["XX",`CR:${elementOfFairy}%0.5 AND CR:fairy_dust%0.005 AND M1`,"M1"]
								],
								category: "auto_fey",
								temp: firstTemp,
								hardness: 1,
								autoType: "fairy",
							};
							if(typeof(eLists) === "undefined") {
								eLists = {};
							};
							if(typeof(eLists.FAIRY) === "undefined") {
								eLists.FAIRY = [];
							};
							eLists.FAIRY.push(fairyName);
							if(!randomExcl) {
								if(typeof(fairyChoices) === "undefined") {
									fairyChoices = []
								};
								if(!fairyChoices.includes(fairyName)) {
									fairyChoices.push(fairyName);
								};
							}
							if(isNocheer) {
								elements[fairyName].nocheer = true;
							}
							if(fairyIncludeRandom) {
								randomExcl ? elements[fairyName].excludeRandom = true : elements[fairyName].excludeRandom = false;
							} else {
								elements[fairyName].excludeRandom = true;
							};
							if(isAfterScriptLoading) {
								elements[fairyName].flippableX = true;
								elementCount++; //increment for new fairy element
								if (settings.cheerful && elements[fairyName].nocheer) {
									elements[fairyName].hidden = true;
									hiddenCount++;
								} else {						
									createElementButton(fairyName);
								};
								elements[fairyName].id = nextid++;
								document.getElementById("extraInfo").innerHTML = "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>"; //update extra info counts (and the copyright year, due to the method used)
							};
						};
						returns.push(fairyName);
					};
					return returns;
				};

			//Spouts

				generateSpout = function(spoutElements,isAfterScriptLoading=false) {//it can be a single element, though
					//To specify an array spout, have the array be inside another array.
					/*For reasons related to how element colors are loaded, if this function is being run from a JS mod file, isAfterScriptLoading should be false.
					Otherwise, you'll get TypeErrors for some reason when trying to place your spout.  If this is being run after the game has loaded (e.g. in the console),
					then isAfterScriptLoading should be true or you might also get TypeErrors (this latter case was a bit inconsistent when I tested it, but 
					the former case wasn't. **isAfterScriptLoading must be false when this function is run from a JS mod file**.*/
					if(typeof(spoutElements) === "string") { //it should be an array, so string check
						//console.log("String detected");
						if(spoutElements.includes(",")) { //comma-separated string?
							//console.log("Splitting string to array");
							spoutElements = spoutElements.split(","); //,SS to array
						} else {
							//console.log("Wrapping string in array");
							spoutElements = [spoutElements]; //single string to array 
						};
					};
					var returns = [];
					for(aaf = 0; aaf < spoutElements.length; aaf++) {
						var elementOfSpout = spoutElements[aaf];
						var allElementsHaveColors = ((elementOfSpout instanceof Array ? elementOfSpout : [elementOfSpout]).map(function(name) { return !!(elements[name]?.color)}).reduce(function(a,b) { return a*b }) == 1)
						if(!allElementsHaveColors) { continue };
						var startColor;
						var randomExcl = 0;
						var isNocheer = 0;
						//console.log("randomExcl set")
						//console.log(elementOfSpout);

						var spoutName;

						if(typeof(elementOfSpout === "string")) { //comma separated string check
							if(elementOfSpout.includes(",")) { //if it is
								elementOfSpout = elementOfSpout.split(","); //to array
								elementOfSpout = elementOfSpout.filter(function(e) { //strip nonexistent elements
									return typeof(elements[e]) === "object";
								});
							};
						};
						if(Array.isArray(elementOfSpout)) {
							spoutName = `${elementOfSpout.join("_")}_spout`; //auto placer element name

							//array case color concatenator and excludeRandom handler
							startColor = [];
							//console.log(elementOfSpout);
							for(ll = 0; ll < elementOfSpout.length; ll++) {
								if(typeof(elements[elementOfSpout[ll]].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
									if(elements[elementOfSpout[ll]].excludeRandom) { //it it's true
										randomExcl = 1; //the whole array spout is excluded
										//console.log("array nyet" + elementOfSpout);
									};
								};
								//console.log(elementOfSpout[ll]);
								startColor = startColor.concat(elements[elementOfSpout[ll]].color);
							};

							for(ll = 0; ll < elementOfSpout.length; ll++) {
								if(typeof(elements[elementOfSpout[ll]].nocheer !== "undefined")) { //if nocheer exists (prevent TypeError)
									if(elements[elementOfSpout[ll]].nocheer) { //it it's true
										isNocheer = 1; //the whole array spout is excluded
										//console.log("array nyet" + elementOfSpout);
									};
								};
								//console.log(elementOfSpout[ll]);
								startColor = startColor.concat(elements[elementOfSpout[ll]].color);
							};
						} else { //they should all be strings, so here
							spoutName = `${elementOfSpout}_spout`; //auto placer element name
							startColor = elements[elementOfSpout].color;
							if(typeof(elements[elementOfSpout].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
								if(elements[elementOfSpout].excludeRandom) { //it it's true
									//console.log("nyet " + elementOfSpout);
									randomExcl = 1; //the spout is excluded
								} else {
									//console.log("allow " + elementOfSpout);
									randomExcl = 0;
								};
							};

							if(typeof(elements[elementOfSpout].nocheer !== "undefined")) { //if nocheer exists (prevent TypeError)
								if(elements[elementOfSpout].nocheer) { //it it's true
									//console.log("nyet " + elementOfSpout);
									isNocheer = 1; //the spout is excluded
								} else {
									//console.log("allow " + elementOfSpout);
									isNocheer = 0;
								};
							};
						};
							//Color gen
						if(Array.isArray(startColor)) { //Average arrays, make colors rgb()
							startColor = averageRgbPrefixedColorArray(startColor);
						} else {
							startColor = rgbHexCatcher(startColor);
						};

						var newColorObject = rgbStringToObject(startColor);

							//End color gen

														//The spout

						//console.log(elementOfSpout);
						var firstInfo, firstTemp;
						if(Array.isArray(elementOfSpout)) {
							firstInfo = elements[elementOfSpout[0]];
							firstTemp = airTemp;
							if(typeof(firstInfo.temp) !== "undefined") {
								firstTemp = firstInfo.temp;
							};
						} else {
							firstInfo = elements[elementOfSpout];
							firstTemp = airTemp;
							if(typeof(firstInfo.temp) !== "undefined") {
								firstTemp = firstInfo.temp;
							};
						};

						elementOfSpout = tryJoin(elementOfSpout,",");

						//console.log(elementOfSpout);

						elements[spoutName] = {
							color: startColor,
							insulate: true,
							colorObject: newColorObject,
							behavior: [
								["XX",`CR:${elementOfSpout}`,"XX"],
								[`CR:${elementOfSpout}`,"XX",`CR:${elementOfSpout}`],
								["XX",`CR:${elementOfSpout}`,"XX"]
							],
							category: "spouts",
							temp: firstTemp,
							hardness: 1,
							autoType: "spout",
						};
						if(!randomExcl) {
							if(typeof(spoutChoices) === "undefined") {
								spoutChoices = []
							};
							if(!spoutChoices.includes(spoutName)) {
								spoutChoices.push(spoutName);
							};
						}
						if(spoutIncludeRandom) {
							randomExcl ? elements[spoutName].excludeRandom = true : elements[spoutName].excludeRandom = false;
						} else {
							elements[spoutName].excludeRandom = true;
						};
						if(isNocheer) {
							elements[spoutName].nocheer = true;
						}
						if(isAfterScriptLoading) {
							elementCount++; //increment for new spout element
							if (settings.cheerful && elements[spoutName].nocheer) {
								elements[spoutName].hidden = true;
								hiddenCount++;
							} else {						
								createElementButton(spoutName);
							};
							elements[spoutName].id = nextid++;
							document.getElementById("extraInfo").innerHTML = "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>"; //update extra info counts (and the copyright year, due to the method used)
						};

						eLists.SPOUT.push(spoutName);
						returns.push(spoutName);
					};
					return returns;
				};

		//Other runAfterAutogen

			//Bombs

				elements.molten_dirt.tempHigh = 3000;
				elements.molten_dirt.stateHigh = "vaporized_rock";

			//Modless orphaned code that I don't want meeting La Maison

				runAfterAutogen(function() {
					var solidBlacklist = ["mistake", "birthpool", "firesea"]; //exclude these since they seem to be liquid

					solids = Object.keys(elements).filter(function(e) {
						return elements[e].category === "solids" && !solidBlacklist.includes(e);
					});

					for(i = 0; i < solids.length; i++) { //A lot of elements in solids, particularly metals, are missing a "state: solid".
						var solidName = solids[i]
						elements[solidName].state = "solid";
					};
				});

		//Mass generation calls

			//Bombs

				runAfterAutogen(function() {
					if(generateBombs) {
						var tempArray = Object.keys(elements);
						tempArray.push(["rock", "sand"]);
						generateBomb(tempArray,false)
					};
				});

			//Creepers

				runAfterAutogen(function() {
					if(generateCreepers) {
						var tempArray = Object.keys(elements);
						tempArray.push(["rock", "sand"]);
						generateCreeper(tempArray,false)
					};
				});

			//Several

				runAfterAutogen(function() {
					cloudElements = [...Object.keys(elements).filter(function(e) { //same criteria as spouts
						return (commonMovableCriteria(e,excludedCloudElements));
					}),["rock","sand"]];
					fairyElements = [...Object.keys(elements).filter(function(e) { //same criteria as spouts
						return (commonMovableCriteria(e,excludedFairyElements));
					}),["rock","sand"]];
					spoutElements = [...Object.keys(elements).filter(function(e) { //same criteria as spouts
						return (commonMovableCriteria(e,excludedSpoutElements));
					}),["rock","sand"]];
					if(generateFairies) { 
						generateFairy(fairyElements,false);
					};
					if(generateClouds) { 
						generateCloud(cloudElements,"*",false);
					};
					if(generateSpouts) { 
						generateSpout(spoutElements,false);
					};
				});

		//Random spawners

			//Bombs

				elements.spawn_random_bomb = {
					color: ["#141c23","#340f3c","#4b2d3f","#35463f","#244633","#460c1b","#294725","#34290c"],
					behavior: behaviors.WALL,
					category: "special",
					excludeRandom: true,
					tick: function(pixel) {
						if(bombChoices == undefined || bombChoices.length == 0) {
							deletePixel(pixel.x,pixel.y);
							return false;
						};
						changePixel(pixel,bombChoices[Math.floor(Math.random() * bombChoices.length)])
					},
				};

			//Clouds

				elements.spawn_random_cloud = {
					color: ["#3e5f8a","#a334ec","#ea96f9","#a6ecf6","#70ebc8","#d9286b","#7eed91","#a18b30"],
					behavior: behaviors.WALL,
					category: "special",
					excludeRandom: true,
					tick: function(pixel) {
						if(cloudChoices == undefined || cloudChoices.length == 0) {
							deletePixel(pixel.x,pixel.y);
							return false;
						};
						changePixel(pixel,cloudChoices[Math.floor(Math.random() * cloudChoices.length)])
					},
				};

			//Creepers

				elements.spawn_random_creeper = {
					color: colorOfRandomCreeper,
					behavior: behaviors.WALL,
					category: "special",
					excludeRandom: false, //see below
					movable: true,
					tick: function(pixel) {
						if(spawnCreepers == undefined || spawnCreepers.length == 0) {
							deletePixel(pixel.x,pixel.y);
							return false;
						};
						changePixel(pixel,spawnCreepers[Math.floor(Math.random() * spawnCreepers.length)]) //spawnCreepers is already excludeRandom filtered
					},
				};

			//Fairies

				elements.spawn_random_fairy = {
					color: ["#3e5f8a","#a334ec","#ea96f9","#a6ecf6","#70ebc8","#d9286b","#7eed91","#a18b30"],
					behavior: behaviors.WALL,
					category: "special",
					excludeRandom: true,
					tick: function(pixel) {
						if(fairyChoices == undefined || fairyChoices.length == 0) {
							deletePixel(pixel.x,pixel.y);
							return false;
						};
						changePixel(pixel,fairyChoices[Math.floor(Math.random() * fairyChoices.length)])
					},
				};

			//Spouts

				elements.spawn_random_spout = {
					color: ["#3e5f8a","#a334ec","#ea96f9","#a6ecf6","#70ebc8","#d9286b","#7eed91","#a18b30"],
					behavior: behaviors.WALL,
					category: "special",
					excludeRandom: true,
					tick: function(pixel) {
						if(spoutChoices == undefined || spoutChoices.length == 0) {
							deletePixel(pixel.x,pixel.y);
							return false;
						};
						changePixel(pixel,spoutChoices[Math.floor(Math.random() * spoutChoices.length)])
					},
				};

		//Other post-generation

			//Fairies

				//FAIRYKILL
				behaviors.FAIRYKILL_OLD = behaviors.FAIRYKILL;
				behaviors.FAIRYKILL = function(pixel) {
					if (pixel.start === pixelTicks) {return}
					if (pixel.charge && elements[pixel.element].behaviorOn) {
						pixelTick(pixel)
					}
					var ignore = (elements[pixel.element].ignore == "undefined" ? [] : elements[pixel.element].ignore);
					for(i = 0; i < adjacentCoords.length; i++) {
						var coord = adjacentCoords[i];
						var offsetX = coord[0];
						var offsetY = coord[1];
						var newX = pixel.x+offsetX;
						var newY = pixel.y+offsetY;
						if(!isEmpty(newX,newY,true)) {
							var newPixel = pixelMap[newX][newY];
							var newElement = newPixel.element;
							var isIgnored = (newElement === ignore || ignore.includes(newElement))
							if(eLists.FAIRY.includes(newElement) && !isIgnored) {
								deletePixel(newX,newY);
							};
						};
					};
					doDefaults(pixel);
				};

				//Add ignores
				var ignoreArray = ["acid", "iron", "silver", "steel", "tungstensteel", "void", "liquid_void", "chute", "vute", "drute", "drolute", "volute", "alkahest", "acid_gas"];
				for(l = 0; l < ignoreArray.length; l++) {
					var name = ignoreArray[l];
					var fairyName = `${ignoreArray[l]}_fairy`;
					if(elementExists(name) && elementExists(fairyName)) {
						var baseInfo = elements[name];
						if(typeof(baseInfo.ignore) === "undefined") {
							baseInfo.ignore = [];
						} else if(typeof(baseInfo.ignore) === "string") {
							baseInfo.ignore = [baseInfo.ignore];
						};
						baseInfo.ignore.push(fairyName);
					};
				};

				//Update FAIRYKILL elements
				var fairykillElements = ["iron", "silver", "steel", "tungstensteel"];
				for(q = 0; q < fairykillElements.length; q++) {
					var name = fairykillElements[q];
					if(elementExists(name)) {
						elements[name].behavior = behaviors.FAIRYKILL;
					};
				};

	//AUTOGENERATED CLUSTER BOMBS

		for (var i = 3; i <= 15; i++) {
			elements[`cluster_bomb_${i}`] = {
				name: `${i}- cluster bomb`,
				color: "#7d776d",
				behavior: [
					`XX|EX:8>smoke,smoke,smoke,smoke,smoke,cluster_bomb_${i-1}%3|XX`,
					"XX|XX|XX",
					`M2|M1 AND EX:8>smoke,smoke,smoke,smoke,smoke,cluster_bomb_${i-1}%3|M2`,
				],
				category: "weapons",
				state: "solid",
				density: 1300,
				tempHigh: 1455.5,
				stateHigh: "molten_steel",
				excludeRandom: true,
			}
		}

		elements.cluster_bomb_2 = {
			color: "#7d776d",
			behavior: [
				"XX|EX:8>smoke,smoke,smoke,smoke,smoke,cluster_bomb%3|XX",
				"XX|XX|XX",
				"M2|M1 AND EX:8>smoke,smoke,smoke,smoke,smoke,cluster_bomb%3|M2",
			],
			category: "weapons",
			state: "solid",
			density: 1300,
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			excludeRandom: true,
		}

	//EXPERIMENTAL RANDOMLY GENERATED LIQUIDS AND ROCKS ##

		if(urlParams.get('liquidAmount') != null) { //null check
			liquidAmount = urlParams.get('liquidAmount')
			if(isNaN(liquidAmount) || liquidAmount === "" || liquidAmount === null) { //NaN check
				 liquidAmount = 10
			}
			liquidAmount = parseInt(liquidAmount)
			if(liquidAmount > 10000) {
				alert("Maximum amount of liquids is 10000.\nOnly 10000 liquids were added.")
			} else if(liquidAmount < 1) {
				alert("Minimum amount of liquids is 1.\n1 liquid was added.")
			}
			liquidAmount = Math.min(10000,Math.max(liquidAmount,1))
		} else {
			liquidAmount = 10
		}

		if(urlParams.get('makeLiquidString') !== null) { //if the variable exists at all
			makeLiquidString = true
		} else { //if it doesn't (and it returns null)
			makeLiquidString = false
		}

		//arbitrarily picked
		randomNameGraphemes = {
			initials: ["m","n","p","t","ch","k","b","d","j","g","f","th","s","sh","h","l","r","y","w","z","sp","st","sk","sl","spl","stl","skl","sr","spr","str","skr","sl","fl","fr","pl","pr","tl","tr","kl","kr","shr","fl","fr","thr"],
			vowels: ["a","e","i","o","u","ay","ee","ie","oa","ew","oo","oi","ow"],
			medials: ["m","n","p","t","k","b","d","g","f","th","s","sh","h","l","r","y","z","sp","st","sk","sl","spl","stl","skl","sr","spr","str","skr","sl","fl","fr","pl","pr","tl","tr","kl","kr","shr","fl","fr","thr"],
			finals: ["m","n","p","t","k","b","d","g","f","th","s","sh","l","r","y","z","sp","st","sk","sl","spl","stl","skl","sr","spr","str","skr","pl","pr","tl","tr","bl","vr"]
		};

		function generateName() {
			//these are picked arbitrarily

			//console.log("getting random type")
			var randomInt1 = randomIntegerFromZeroToValue(6)

			//console.log("generating type " + randomInt1)
			if(randomInt1 == 0) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 1) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 2) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals) + "e"
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 3) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) +	 randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 4) {
				var randomName = randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 5) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 6) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels)
				//console.log("generated T" + randomInt1 + " name")
			} else {
				var randomName = randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("warning: type was above 6 somehow")
			}
			//console.log(randomName)
			return randomName
		}

		function randomLiquidColorGen() {
			var randomR = randomIntegerFromZeroToValue(255)
			var randomG = randomIntegerFromZeroToValue(255)
			var randomB = randomIntegerFromZeroToValue(255)
			var randomColor = {r: randomR, g: randomG, b: randomB}
			var newLiquidColor = averageColorObjects(whiteColor,randomColor,weight1=0.1)
			var newSolidColor = averageColorObjects(whiteColor,randomColor,weight1=0.4)
			var newGasColor = averageColorObjects(whiteColor,randomColor,weight1=0.7)
			var newLiquidColor = rgbToHex(newLiquidColor)
			var newSolidColor = rgbToHex(newSolidColor)
			var newGasColor = rgbToHex(newGasColor)
			return [newLiquidColor, newSolidColor, newGasColor]
		}

		function _generateAveragedRandoms() {
			return averageNumericArray([Math.random(),Math.random(),Math.random()])
		}

		function avgRndToMult() {
			return 1 + (0.55 - _generateAveragedRandoms())
		}


		if(makeLiquidString == true) {
			liquidString = ""
		}

		for(i = 0; i < liquidAmount; i++) {
			var name = generateName();
			var meltingAdjustment = avgRndToMult();
			var densityAdjustment = avgRndToMult();
			var hardnessAdjustment = avgRndToMult();
			var conductivity = Math.random() ** 1.8;
			var conductivityEnabled = Math.random();
			var conducts = null;
			conductivityEnabled < 1/3 ? conducts = true : conducts = false;
			var burn = (Math.random() ** 2) * 100;
			var burnTime = 10 ** ((0.5 + Math.random()) ** 3);
			var burningEnabled = Math.random();
			var burns = null;
			burningEnabled < 1/3 ? burns = true : burns = false;
			var colors = randomLiquidColorGen();
			var viscosity = (1 + Math.random()) ** 10;
			var solidDensityMultiplier = 1+(Math.abs(1-avgRndToMult()));
			var solidDensityRelationRandomizer = Math.random();
			var solidIsLessDense = null;
			var solidDensity = null;
			solidDensityRelationRandomizer < 0.1 ? solidIsLessDense = true : solidIsLessDense = false;
			solidIsLessDense == true ? solidDensity = (1000 / solidDensityMultiplier) * densityAdjustment : solidDensity = (1000 * solidDensityMultiplier) * densityAdjustment;
			var freezingPoint = (273 * (meltingAdjustment ** 10)) - 273;
			var boilingPoint = freezingPoint + Math.abs((273 * (avgRndToMult() ** 9)) - 273);
			var gasDensity = 0.00143 * avgRndToMult() * 1000 * densityAdjustment;
			if(typeof(elements[name]) != "undefined") {
				name = name + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals);
			};
			liquidConductivityAdjust = 0.5 * Math.sqrt(avgRndToMult());

			elements[name] = {
				name: name,
				color: colors[0],
				behavior: behaviors.LIQUID,
				tempLow: freezingPoint,
				temp: freezingPoint + 20,
				tempHigh: boilingPoint,
				stateLow: `${name}_ice`,
				stateHigh: `${name}_gas`,
				category: "random liquids",
				state: "liquid",
				density: 1000 * densityAdjustment,
				//conduct: goes here
				//burn and burnTime go here
				hardness: 0.3 * hardnessAdjustment,
				viscosity: viscosity,
				breakInto: `${name}_gas`,
			};

			elements[`${name}_ice`] = {
				name: `${name} ice`,
				color: colors[1],
				behavior: behaviors.WALL,
				tempHigh: freezingPoint,
				temp: freezingPoint - 20,
				stateHigh: name,
				category: "random solids",
				state: "solid",
				density: solidDensity,
				//conduct: goes here
				//burn and burnTime go here
				hardness: 0.6 * hardnessAdjustment,
				breakInto: name,
			};

			elements[`${name}_gas`] = {
				name: `${name} gas`,
				color: colors[2],
				behavior: behaviors.GAS,
				tempLow: boilingPoint,
				temp: boilingPoint + 20,
				stateLow: name,
				category: "random gases",
				state: "gas",
				density: gasDensity,
				//burn and burnTime go here
				hardness: 1,
			};

			if(burns == true) {
				elements[name].burn = burn
				elements[name].burnTime = burnTime
				elements[`${name}_ice`].burn = burn
				elements[`${name}_ice`].burnTime = burnTime
				elements[`${name}_gas`].burn = burn
				elements[`${name}_gas`].burnTime = burnTime
			}

			if(conducts == true) {
				elements[name].conduct = conductivity * liquidConductivityAdjust
				elements[`${name}_ice`].conduct = conductivity
			}

			if(makeLiquidString == true) {
				//Append moddable code for the liquid state to liquidString
				liquidString = liquidString + `elements.${name} = {\n	name: \"${name}\",\n	color: \"${colors[0]}\",\n	behavior: behaviors.LIQUID,\n	tempLow: ${freezingPoint},\n	temp: ${freezingPoint + 20},\n	tempHigh: ${boilingPoint},\n	stateLow: \"${name}_ice\",\n	stateHigh: \"${name}_gas\",\n	category: \"random liquids\",\n	state: \"liquid\",\n	density: ${1000 * densityAdjustment},\n	`
				if(conducts == true) { liquidString = liquidString + `conduct: ${conductivity * liquidConductivityAdjust},\n	` }
				if(burns == true) { liquidString = liquidString + `burn: ${burn},\n	burnTime: ${burnTime},\n	` }
				liquidString = liquidString + `hardness: ${0.3 * hardnessAdjustment},\n	viscosity: ${viscosity},\n	breakInto: \"${name}_gas\",\n};\n\n`

				//Append moddable code for the solid state to liquidString
				liquidString = liquidString + `elements.${name}_ice = {\n	name: \"${name} ice\",\n	color: \"${colors[1]}\",\n	behavior: behaviors.WALL,\n	tempHigh: ${freezingPoint},\n	temp: ${freezingPoint - 20},\n	stateHigh: \"${name}\",\n	category: \"random solids\",\n	state: \"solid\",\n	density: ${solidDensity},\n	`
				if(conducts == true) { liquidString = liquidString + `conduct: ${conductivity},\n	` }
				if(burns == true) { liquidString = liquidString + `burn: ${burn},\n	burnTime: ${burnTime},\n	` }
				liquidString = liquidString + `hardness: ${0.6 * hardnessAdjustment},\n	breakInto: \"${name}\",\n};\n\n`

				//Append moddable code for the gaseous state to liquidString
				liquidString = liquidString + `elements.${name}_gas = {\n	name: \"${name} gas\",\n	color: \"${colors[2]}\",\n	behavior: behaviors.GAS,\n	tempLow: ${boilingPoint},\n	temp: ${boilingPoint + 20},\n	stateLow: \"${name}\",\n	category: \"random gases\",\n	state: \"solid\",\n	density: ${gasDensity},\n	`
				if(burns == true) { liquidString = liquidString + `burn: ${burn},\n	burnTime: ${burnTime},\n	` }
				liquidString = liquidString + `hardness: 1,\n};\n\n`
			}

		}

		if(makeLiquidString == true) {
			console.log(`Liquids added to liquidString (length ${liquidString.length})`)
		}

		if(urlParams.get('rockAmount') != null) { //null check
			rockAmount = urlParams.get('rockAmount')
			if(isNaN(rockAmount) || rockAmount === "" || rockAmount === null) { //NaN check
				 rockAmount = 10
			}
			rockAmount = parseInt(rockAmount)
			if(rockAmount > 10000) {
				alert("Maximum amount of rocks is 10000.\nOnly 10000 rocks were added.")
			} else if(rockAmount < 1) {
				alert("Minimum amount of rocks is 1.\n1 rock was added.")
			}
			rockAmount = Math.min(10000,Math.max(rockAmount,1))
		} else {
			rockAmount = 10
		}

		if(urlParams.get('makeRockString') !== null) { //if the variable exists at all
			makeRockString = true
		} else { //if it doesn't (and it returns null)
			makeRockString = false
		}

		function generateName() {
			//these are picked arbitrarily

			//console.log("getting random type")
			var randomInt1 = randomIntegerFromZeroToValue(6)

			//console.log("generating type " + randomInt1)
			if(randomInt1 == 0) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 1) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 2) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals) + "e"
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 3) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) +	 randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 4) {
				var randomName = randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 5) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels)
				//console.log("generated T" + randomInt1 + " name")
			} else if(randomInt1 == 6) {
				var randomName = randomChoice(randomNameGraphemes.initials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels)
				//console.log("generated T" + randomInt1 + " name")
			} else {
				var randomName = randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.medials) + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
				//console.log("warning: type was above 6 somehow")
			}
			//console.log(randomName)
			return randomName
		}

		randomRockColorTemplateLuma = {
			rock1: {r: 128, g: 128, b: 128},
			rock2: {r: 79, g: 79, b: 79},
			rock3: {r: 148, g: 148, b: 148},
			gravel1: {r: 227, g: 224, b: 223},
			gravel2: {r: 177, g: 171, b: 163},
			gravel3: {r: 116, g: 115, b: 109},
			gravel4: {r: 82, g: 75, b: 71}
		};

		function randomRockColorGen() {
			var randomR = randomIntegerFromZeroToValue(255)
			var randomG = randomIntegerFromZeroToValue(255)
			var randomB = randomIntegerFromZeroToValue(255)
			var randomColor = {r: randomR, g: randomG, b: randomB}
			var newRockColor1 = averageColorObjects(randomRockColorTemplateLuma.rock1,randomColor,weight1=0.65)
			var newRockColor2 = averageColorObjects(randomRockColorTemplateLuma.rock2,randomColor,weight1=0.65)
			var newRockColor3 = averageColorObjects(randomRockColorTemplateLuma.rock3,randomColor,weight1=0.65)
			var newGravelColor1 = averageColorObjects(randomRockColorTemplateLuma.gravel1,randomColor,weight1=0.675)
			var newGravelColor2 = averageColorObjects(randomRockColorTemplateLuma.gravel2,randomColor,weight1=0.675)
			var newGravelColor3 = averageColorObjects(randomRockColorTemplateLuma.gravel3,randomColor,weight1=0.675)
			var newGravelColor4 = averageColorObjects(randomRockColorTemplateLuma.gravel4,randomColor,weight1=0.675)
			var newRockColor1 = rgbToHex(newRockColor1)
			var newRockColor2 = rgbToHex(newRockColor2)
			var newRockColor3 = rgbToHex(newRockColor3)
			var newGravelColor1 = rgbToHex(newGravelColor1)
			var newGravelColor2 = rgbToHex(newGravelColor2)
			var newGravelColor3 = rgbToHex(newGravelColor3)
			var newGravelColor4 = rgbToHex(newGravelColor4)
			return [newRockColor1, newRockColor2, newRockColor3, newGravelColor1, newGravelColor2, newGravelColor3, newGravelColor4]
		}

		function _generateAveragedRandoms() {
			return averageNumericArray([Math.random(),Math.random(),Math.random()])
		}

		function avgRndToMult() {
			return 1 + (0.55 - _generateAveragedRandoms())
		}

		elements.gravel.breakInto = "dust"

		if(makeRockString == true) {
			rockString = ""
		}

		for(i = 0; i < rockAmount; i++) {
			var name = generateName()
			var meltingAdjustment = avgRndToMult()
			var densityAdjustment = avgRndToMult()
			var hardnessAdjustment = avgRndToMult()
			var colors = randomRockColorGen()
			if(typeof(elements[name]) != "undefined") {
				name = name + randomChoice(randomNameGraphemes.vowels) + randomChoice(randomNameGraphemes.finals)
			}
			elements[name] = {
				name: name,
				color: [colors[0], colors[1], colors[2]],
				behavior: behaviors.POWDER,
				tempHigh: 950 * meltingAdjustment,
				category: "random rocks",
				state: "solid",
				density: 2550 * densityAdjustment,
				hardness: 0.5 * hardnessAdjustment,
				breakInto: ["dust",`${name}_gravel`],
			}

			if(makeRockString == true) {
				rockString = rockString + `elements.${name} = {\n	name: \"${name}\",\n	color: [\"${colors[0]}\", \"${colors[1]}\", \"${colors[2]}\"],\n	behavior: behaviors.POWDER,\n	tempHigh: ${950 * meltingAdjustment},\n	category: \"random rocks\",\n	state: \"solid\",\n	density: ${2550 * densityAdjustment},\n	hardness: ${0.5 * hardnessAdjustment},\n	breakInto: [\"dust\",\"${name}_gravel\"],\n};\n\n`
			}

			elements[`${name}_gravel`] = {
				name: `${name} gravel`,
				color: [colors[3], colors[4], colors[5], colors[6]],
				behavior: behaviors.POWDER,
				tempHigh: 950 * meltingAdjustment,
				stateHigh: name,
				category: "random rocks",
				state: "solid",
				density: 1680 * densityAdjustment,
				hardness: 0.2 * (hardnessAdjustment ** (2/3)),
				breakInto: "dust",
			}

			if(makeRockString == true) {
				rockString = rockString + `elements.${name}_gravel = {\n	name: \"${name} gravel\",\n	color: [\"${colors[3]}\", \"${colors[4]}\", \"${colors[5]}\", \"${colors[6]}\"],\n	behavior: behaviors.POWDER,\n	tempHigh: ${950 * meltingAdjustment},\n	stateHigh: \"${name}\",\n	category: \"random rocks\",\n	state: \"solid\",\n	density: ${1680 * densityAdjustment},\n	hardness: ${0.2 * (hardnessAdjustment ** (2/3))},\n	breakInto: \"dust\",\n};\n\n`
			}

		}

		if(makeRockString == true) {
			console.log(`Rocks added to rockString (length ${rockString.length})`)
		}

	//PRIMITIVELY RANDOMLY GENERATED ELEMENTS ##

		elements.ogqwwrko = {
			name: "ogqWWRKo",
			color: "#b5f134",
			behavior: [
				"DB|XX|XX",
				"CH:molten_silver>antifire|XX|BO",
				"SA|SA|XX",
			],
			category: "hyperrandom elements",
			burn: 57,
			burnTime: 451,
			tempHigh: 1055,
			density: 6632.184014557264,
		};

		elements.kibifwpd = {
			name: "KIBiFwpD",
			color: "#b8a3a5",
			behavior: [
				"XX|RT|CH:light%32",
				"SW|DL%25|SH",
				"DB|SH|SW:firefly",
			],
			category: "hyperrandom elements",
			density: 1382.2019914825285,
		};

		elements.sezhorqq = {
			name: "SEzhorqQ",
			color: "#f8f19c",
			behavior: [
				"DB|XX|M1",
				"SA|CH:wheat%23|ST",
				"M1|CH:molten_bronze>charcoal|XX",
			],
			category: "hyperrandom elements",
			burn: 26,
			burnTime: 228,
			density: 11054.763724436774,
		};

		elements.bgcelthv = {
			name: "bGceLthV",
			color: "#2a8885",
			behavior: [
				"CC:#ec80f8|XX|ST",
				"SW:salt|HT:10|RT",
				"CH:glitter%4|SH|SW:salt",
			],
			reactions: {
				"meat": { "elem1":"mushroom_stalk", "elem2":"bee", "chance":0.07255393753591068 },
				"ball": { "elem1":"frog", "elem2":"acid_gas", "chance":0.8120527233695284 },
				"antimatter": { "elem1":"molten_salt", "elem2":"dirty_water", "chance":0.18656242174277482 },
				"burner": { "elem1":"electric", "elem2":"charcoal", "chance":0.9864527094278326 },
				"oil": { "elem1":"epsom_salt", "elem2":"bamboo_plant", "chance":0.7298237285611298 },
			},
			category: "hyperrandom elements",
			density: 1446.9471325970142,
		};

		elements.hpwfukak = {
			name: "hpWfukAK",
			color: "#f22c87",
			behavior: [
				"XX|SW:bronze|SW",
				"CR:cancer|LB:slug|CO:1",
				"BO|SW:bronze|BO",
			],
			category: "hyperrandom elements",
			burn: 71,
			burnTime: 449,
			density: 10877.887548154851,
		};

		elements.ggysmbid = {
			name: "GgYSMbID",
			color: "#81ff73",
			behavior: [
				"SW|CR:molten_sterling|BO",
				"DL:magma|C2:wood|XX",
				"CC:#de4dbe|XX|DL:magma",
			],
			reactions: {
				"slug": { "elem1":"mushroom_cap", "elem2":"plastic", "chance":0.7318844051213007 },
				"wheat": { "elem1":"frozen_meat", "elem2":"mushroom_spore", "chance":0.7895678110819928 },
				"sulfur": { "elem1":"caramel", "elem2":"liquid_hydrogen", "chance":0.8659314154325147 },
				"bamboo_plant": { "elem1":"magma", "elem2":"glitter", "chance":0.5424222221105207 },
			},
			category: "hyperrandom elements",
			tempHigh: 2956,
			density: 6284.617729241624,
		};

		elements.fpnklukg = {
			name: "FpNkluKG",
			color: "#a68793",
			behavior: [
				"DB|DL:calcium|DB",
				"SW|LB:algae|ST",
				"DL:calcium|HT:4|CR:copper",
			],
			reactions: {
				"dirt": { "elem1":"sand", "elem2":"water", "chance":0.9366010380552752 },
			},
			category: "hyperrandom elements",
			tempHigh: 1300,
			density: 3828.9615486132043,
		};

		elements.efgtdvzx = {
			name: "EfgTDvzx",
			color: "#96ca55",
			behavior: [
				"SA|CO:7|SH",
				"SW:potato_seed|L2:slaked_lime|CH:sugar_water>soap",
				"BO|CC:#0bd2dd|CO:7",
			],
			category: "hyperrandom elements",
			density: 18274.111700368052,
		};

		elements.qacwivpp = {
			name: "QACWIVPp",
			color: "#8f57c0",
			behavior: [
				"HT:8|CH:quicklime>copper|CR:dioxin",
				"CC:#f64cc7|L1:mudstone|M2",
				"CH:quicklime>copper|RT|CH:firefly%3",
			],
			category: "hyperrandom elements",
			density: 12926.310301027497,
		};

		elements.qfgdrsay = {
			name: "qFgDRSAY",
			color: "#c06b4e",
			behavior: [
				"CC:#bb68ee|DB|DL:molten_brick",
				"CR:iron|CO:1|M1",
				"CO:1|M2|SP",
			],
			category: "hyperrandom elements",
			burn: 50,
			burnTime: 376,
			density: 17821.827734771083,
		};

		elements.qbesguoy = {
			name: "QbeSGuOy",
			color: "#4b9513",
			behavior: [
				"CC:#39d412|CO:10|CO:10",
				"HT:5|FY|SW:snow_cloud",
				"M1|SH|CO:10",
			],
			reactions: {
				"ant": { "elem1":"tin", "elem2":"acid", "chance":0.015683479804684108 },
				"liquid_hydrogen": { "elem1":"molten_calcium", "elem2":"tinder", "chance":0.5541216729460897 },
				"molten_borax": { "elem1":"wall", "elem2":"bee", "chance":0.037733528661837115 },
				"rock": { "elem1":"wire", "elem2":"wall", "chance":0.5515341715699367 },
				"chocolate": { "elem1":"wet_sand", "elem2":"bamboo_plant", "chance":0.34082969121459494 },
				"vinegar": { "elem1":"rock", "elem2":"flour", "chance":0.3551432786489651 },
				"molten_iron": { "elem1":"led_r", "elem2":"udder", "chance":0.2856279501232143 },
				"floating_cloner": { "elem1":"udder", "elem2":"copper", "chance":0.3669477762812875 },
				"cancer": { "elem1":"yeast", "elem2":"bone_marrow", "chance":0.7241288621811043 },
			},
			category: "hyperrandom elements",
			burn: 34,
			burnTime: 408,
			density: 7097.343018249547,
		};

		elements.omsdvddh = {
			name: "oMsDvDdH",
			color: "#e2d948",
			behavior: [
				"HT:8|RT|M1",
				"ST|L1:chocolate_syrup|M2",
				"SA|CR:flea|SH",
			],
			category: "hyperrandom elements",
			burn: 98,
			conduct: 0.6023663023285293,
			burnTime: 199,
			tempHigh: 2767,
			density: 10910.501840659097,
		};

		elements.izhsemyl = {
			name: "IzhSEMYl",
			color: "#7fafff",
			behavior: [
				"ST|SW:clay_soil|CO:11",
				"SA|LB:infection|BO",
				"SW:clay_soil|SW:clay_soil|CR:dough",
			],
			category: "hyperrandom elements",
			density: 2810.029487411252,
		};

		elements.ayvnmlrq = {
			name: "AyvnmLRQ",
			color: "#668a95",
			behavior: [
				"HT:1|M2|SA",
				"CR:molten_nickel|CH:light%6|CO:4",
				"SP|HT:1|SW:liquid_oxygen",
			],
			category: "hyperrandom elements",
			tempHigh: 893,
			density: 11509.655636160245,
		};

		elements.pjrdtuta = {
			name: "pJRDtuTA",
			color: "#b12040",
			behavior: [
				"BO|SW:sodium_acetate|CO:1",
				"BO|L1:laser|CC:#e40c6e",
				"SW:sodium_acetate|SW|BO",
			],
			category: "hyperrandom elements",
			conduct: 0.33534106397322677,
			density: 18826.565148369646,
		};

		elements.qodfmdfo = {
			name: "QodfMdFo",
			color: "#fe530b",
			behavior: [
				"ST|DL:cheese|RT",
				"DB|FX|HT:11",
				"CH:molten_sterling>wood|SP|CR:molten_iron",
			],
			category: "hyperrandom elements",
			conduct: 0.3465345689563781,
			density: 11140.091847406393,
		};

		elements.rwwkmgjn = {
			name: "rWWkMgJN",
			color: "#2eeaa0",
			behavior: [
				"CR:ant|CH:salt%7|DB",
				"DL:freezer|FX|XX",
				"CC:#c82b89|BO|HT:5",
			],
			category: "hyperrandom elements",
			conduct: 0.698741020714595,
			tempHigh: 2686,
			density: 17409.619803480866,
		};

		/*elements.yckrmplh = {
			name: "yckRMplh",
			color: "#0807ad",
			behavior: [
				"XX|DL:dead_cum_water|CR:firesea",
				"SH|HT:11|M2",
				"BO|SH|M2",
			],
			reactions: {
				"fairy_fairy_fairy_fairy": { "elem1":"molten_uranium", "elem2":"tritium", "chance":0.4602898017566186 },
				"fluxed_matte_copper": { "elem1":"pure_water", "elem2":"enchanted_ketchup", "chance":0.8272186749812657 },
				"molten_neutronium": { "elem1":"amogus9", "elem2":"fallout", "chance":0.9302153671106638 },
				"bone_beast": { "elem1":"celie_leaves", "elem2":"dirt", "chance":0.3741482072147079 },
				"fairy_fairy": { "elem1":"led_g", "elem2":"smoke", "chance":0.29352648263376613 },
				"fairy_fairy_fairy": { "elem1":"old_celie_leaves", "elem2":"green_dye", "chance":0.5993662901794029 },
				"flamer": { "elem1":"amogus1", "elem2":"brass", "chance":0.5319076167877976 },
			},
			category: "hyperrandom elements",
			tempHigh: 537,
			density: 9215.024069840034,
		}*/

		elements.urhhhqjs = {
			name: "urhhhqJS",
			color: "#31a6bf",
			behavior: [
				"M2|CH:anesthesia%7|CH:anesthesia%7",
				"SH|L1:sugar|CC:#55eda7",
				"M1|DL:t_center|M2",
			],
			category: "hyperrandom elements",
			density: 373.9243313453792,
		};

		elements.budgkclj = {
			name: "BUDGKClj",
			color: "#ba657f",
			behavior: [
				"DL:blister_copper|SW:fire_fairy|XX",
				"SA|CC:#3e65ab|ST",
				"CC:#3e65ab|SP|CC:#3e65ab",
			],
			category: "hyperrandom elements",
			burn: 5,
			burnTime: 480,
			tempHigh: 2895,
			density: 4123.593585708072,
		};

		elements.yzkluoho = {
			name: "yzKluohO",
			color: "#3e297d",
			behavior: [
				"RT|CH:molten_borax>anesthesia|M2",
				"M1|DL%11|SW:molten_amogus1",
				"M2|CC:#0d4b06|SA",
			],
			category: "hyperrandom elements",
			burn: 22,
			burnTime: 172,
			tempHigh: 2838,
			density: 16097.788434828502,
		};

		elements.pcqahdke = {
			name: "PCQAHdkE",
			color: "#425618",
			behavior: [
				"CR:molten_uranium|CO:4|CH:led_b%10",
				"DL:magic|HT:11|ST",
				"SP|SP|SW:cocoon",
			],
			category: "hyperrandom elements",
			density: 6338.489583058846,
		};
		elements.ywdgkhcr = {
			name: "ywDgKHcr",
			color: "#1bb24d",
			behavior: [
				"SW:fly|SW|CR:burning_unnamed_powder",
				"CO:9|CC:#71a6b1|CH:fairy_fairy_fairy_fairy>amogus3",
				"BO|XX|CH:bone%6",
			],
			category: "hyperrandom elements",
			conduct: 0.18249512087981343,
			tempHigh: 2926,
			density: 5349.2882949054265,
		};

		elements.acsjdbsp = {
			name: "ACsjdBSP",
			color: "#c25375",
			behavior: [
				"CH:antigas>fairy_fairy_fairy_fairy|BO|XX",
				"DL:chocolate|CH:fairy_fairy_fairy%10|BO",
				"SW:tree_branch|XX|SW:tree_branch",
			],
			category: "hyperrandom elements",
			density: 19429.0198815719,
		};

		elements.bxxbbugd = {
			name: "BXXBBUgd",
			color: "#63fb15",
			behavior: [
				"DB|SH|DB",
				"XX|XX|SW:aluminum",
				"SW|SW|CH:a4left%7",
			],
			category: "hyperrandom elements",
			density: 11933.76730202344,
		};

		/*
		elements.kkhxgsiw = {
			name: "KkHXGSiW",
			color: "#30ee04",
			behavior: [
				"SW:molten_copper|CH:fairy_fairy_fairy%8|SW:molten_copper",
				"XX|DL%5|ST",
				"CH:dead_cum>molten_tungstensteel|DB|DL:fairy",
			],
			reactions: {
				"led_r": { "elem1":"ketchup_metal", "elem2":"mystic_fire", "chance":0.032394734075084766 },
				"plague": { "elem1":"flameshockwave3", "elem2":"stardust", "chance":0.13908761952404325 },
				"blood": { "elem1":"rocket", "elem2":"fairy_fairy_fairy", "chance":0.16814629599115316 },
				"yeast": { "elem1":"oil", "elem2":"juice", "chance":0.9787877797027756 },
				"stardust": { "elem1":"molten_AyvnmLRQ", "elem2":"fairy_fairy", "chance":0.18375417180105702 },
				"cooler": { "elem1":"celie_leaves", "elem2":"corn_seed", "chance":0.15359338811668444 },
			},
			category: "hyperrandom elements",
			burn: 60,
			burnTime: 491,
			density: 6333.626319914362,
		}*/

		elements.gqyhbmuq = {
			name: "GQYHBMuQ",
			color: "#3e3969",
			behavior: [
				"CR:flea|CH:led_b%5|BO",
				"SW|CC:#aaf599|SW:molten_dirt",
				"SW|ST|SW",
			],
			category: "hyperrandom elements",
			tempHigh: 2972,
			density: 3881.977856937143,
		};

		elements.ookioobo = {
			name: "OOKIoObo",
			color: "#f072da",
			behavior: [
				"SW|SW|DL:fairy_fairy_fairy_fairy",
				"RT|XX|SW:copper",
				"BO|XX|XX",
			],
			category: "hyperrandom elements",
			density: 34.228020163470646,
		};

		elements.qqnuejnt = {
			name: "qQNuEjNT",
			color: "#0a4f5d",
			behavior: [
				"BO|CH:tin>udder|ST",
				"CH:molten_amogus7%1|DL%9|SW:magma",
				"M1|XX|XX",
			],
			category: "hyperrandom elements",
			burn: 47,
			burnTime: 183,
			tempHigh: 2096,
			density: 9679.340844240462,
		};

		elements.hgshruqe = {
			name: "HgSHruqE",
			color: "#d59786",
			behavior: [
				"HT:3|M1|RT",
				"XX|FY|RT",
				"M2|CC:#885997|M1",
			],
			category: "hyperrandom elements",
			burn: 85,
			burnTime: 90,
			tempHigh: 346,
			density: 11697.491509311361,
		};

		/*
		elements.hyvrnxqq = {
			name: "hyVrNXQQ",
			color: "#a214b9",
			behavior: [
				"SW|XX|CH:dead_cum_water%2",
				"CO:4|LB:molten_amogus4|CH:fairy_fairy>molten_potassium_salt",
				"CH:fairy_fairy_fairy_fairy>molten_potassium_salt|CR:rainbow|CR:rainbow",
			],
			reactions: {
				"fairy_fairy_fairy": { "elem1":"concoction", "elem2":"straw", "chance":0.17509068546150386 },
			},
			category: "hyperrandom elements",
			burn: 69,
			burnTime: 76,
			tempHigh: 1122,
			density: 7158.798415310327,
		}*/

		elements.xxystxnm = {
			name: "xxYsTXnM",
			color: "#763817",
			behavior: [
				"XX|SW|CC:#210b1d",
				"SW:blue_dye|CO:6|SW:blue_dye",
				"SW:blue_dye|HT:9|XX",
			],
			category: "hyperrandom elements",
			tempHigh: 2187,
			density: 15160.604263202209,
		};

		elements.sowljgtd = {
			name: "SowljGtd",
			color: "#c554eb",
			behavior: [
				"BO|XX|DL:t2",
				"XX|FY|XX",
				"XX|SW:ionized_hydrogen|HT:8",
			],
			reactions: {
				"petal": { "elem1":"fairy_fairy_fairy", "elem2":"molten_sterling", "chance":0.13858328633550154 },
				"fairy_fairy_fairy_fairy": { "elem1":"glass_shard", "elem2":"oxidized_copper", "chance":0.8228073223526745 },
				"molten_corrupt_land": { "elem1":"everfire_dust", "elem2":"matte_copper", "chance":0.6538956182983582 },
				"tralphium": { "elem1":"ice_fairy", "elem2":"mycelium", "chance":0.015319150698328909 },
				"tritium": { "elem1":"molten_zinc", "elem2":"fairy_fairy", "chance":0.13333787328877345 },
			},
			category: "hyperrandom elements",
			burn: 82,
			conduct: 0.5267298783471723,
			burnTime: 313,
			density: 10531.689830022671,
		};

		elements.givrbdjd = {
			name: "giVRBDjd",
			color: "#c08adf",
			behavior: [
				"SH|DB|M1",
				"BO|CH:slug%9|XX",
				"M1|RT|SP",
			],
			category: "hyperrandom elements",
			density: 8965.264917972576,
		};


		eLists.RANDOM = [ //commented-out names correspond to elements commented out because they reference cum
			"ogqwwrko", "kibifwpd", "sezhorqq", "bgcelthv", "hpwfukak",
			"ggysmbid", "fpnklukg", "efgtdvzx", "qacwivpp", "qfgdrsay",
			"qbesguoy", "omsdvddh", "izhsemyl", "ayvnmlrq", "pjrdtuta",
			"qodfmdfo", "rwwkmgjn", /*"yckrmplh",*/ "urhhhqjs", "budgkclj",
			"yzkluoho", "pcqahdke", "ywdgkhcr", "acsjdbsp", "bxxbbugd",
			/*"kkhxgsiw",*/ "gqyhbmuq", "ookioobo", "qqnuejnt", "hgshruqe",
			/*"hyvrnxqq",*/ "xxystxnm", "sowljgtd", "givrbdjd"
		];

		//To do: recipe;

	//NEUTRONIUM COMPRESSOR ##

		var singularityColorTemplate = ["#202020", "#505050", "#b0b0b0", "#c7c7c7"];

		singularityNumber = 10000;

		if(urlParams.get('singularityIncludeRandom') !== null) { //if the variable exists at all
			singularityIncludeRandom = true
		} else { //if it doesn't (and it returns null)
			singularityIncludeRandom = false
		}

		//Generate singularities
		if(urlParams.get('generateSingularities') !== null) { //if the variable exists at all
			generateSingularities = true
		} else { //if it doesn't (and it returns null)
			generateSingularities = false
		}

		function haseulitoidSingularityTick(pixel) {
			var s = 1;
			if(elements[pixel.element].singularityNumber !== undefined) {
				s = elements[pixel.element].singularityNumber;
			};
			if(pixel.value == undefined) { pixel.value = 0 };
			valueFunction(pixel,haseuliteValueObject,haseuliteSpreadWhitelist);
			if(pixel.oldColor === undefined) { pixel.oldColor = pixelColorPick(pixel) };
			if(pixel.oldColor === null) { pixel.oldColor = pixel.color };
			pixel.color = lightenColor(pixel.oldColor,pixel.value / 3);

			var mVal = elements[pixel.element].haseulitoidMaxValue ?? 350;
			if(pixel.value >= mVal) {
				var coldBoomChance = Math.max(0.006 * ((pixel.value - mVal) / (400/3)), 0.000075);
				if(Math.random() < coldBoomChance) {
					var coldBoomRadius = Math.min(120,Math.floor((7 + s) + ((pixel.value - mVal) / Math.max(20,100 - s - s))));
					explodeAtPlus(pixel.x,pixel.y,coldBoomRadius,"cold_fire","cold_smoke",null,coldExplosionAfterCooling);
				};
			};

		};

		function generateSingularity(singularityElements,isAfterScriptLoading=false) {//it can be a single element, though
			var count = 0;
			if(typeof(singularityElements) === "string") { //it should be an array, so string check
				//console.log("String detected");
				if(singularityElements.includes(",")) { //comma-separated string?
					//console.log("Splitting string to array");
					singularityElements = singularityElements.split(","); //,SS to array
				} else {
					//console.log("Wrapping string in array");
					singularityElements = [singularityElements]; //single string to array 
				};
			};
			for(aaf = 0; aaf < singularityElements.length; aaf++) {
				var elementOfSingularity = singularityElements[aaf];
				var startColor;
				var randomExcl = 0;
				var isNocheer = 0;
				//console.log("randomExcl set")
				//console.log(elementOfSingularity);

				var singularityName;

				if(typeof(elementOfSingularity === "string")) { //comma separated string check
					if(elementOfSingularity.includes(",")) { //if it is
						elementOfSingularity = elementOfSingularity.split(","); //to array
						elementOfSingularity = elementOfSingularity.filter(function(e) { //strip nonexistent elements
							return typeof(elements[e]) === "object";
						});
					};
				};
				if(Array.isArray(elementOfSingularity)) {
					singularityName = `${elementOfSingularity.join("_")}_singularity`; //auto placer element name

					//array case color concatenator and excludeRandom handler
					startColor = [];
					//console.log(elementOfSingularity);
					for(ll = 0; ll < elementOfSingularity.length; ll++) {
						if(typeof(elements[elementOfSingularity[ll]].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
							if(elements[elementOfSingularity[ll]].excludeRandom) { //it it's true
								randomExcl = 1; //the whole array singularity is excluded
								//console.log("array nyet" + elementOfSingularity);
							};
						};
						//console.log(elementOfSingularity[ll]);
						startColor = startColor.concat(elements[elementOfSingularity[ll]].color);
					};

					for(ll = 0; ll < elementOfSingularity.length; ll++) {
						if(typeof(elements[elementOfSingularity[ll]].nocheer !== "undefined")) { //if nocheer exists (prevent TypeError)
							if(elements[elementOfSingularity[ll]].nocheer) { //it it's true
								isNocheer = 1; //the whole array singularity is excluded
								//console.log("array nyet" + elementOfSingularity);
							};
						};
						//console.log(elementOfSingularity[ll]);
						startColor = startColor.concat(elements[elementOfSingularity[ll]].color);
					};
				} else { //they should all be strings, so here
					singularityName = `${elementOfSingularity}_singularity`; //auto placer element name
					startColor = elements[elementOfSingularity].color;
					if(typeof(elements[elementOfSingularity].excludeRandom !== "undefined")) { //if excludeRandom exists (prevent TypeError)
						if(elements[elementOfSingularity].excludeRandom) { //it it's true
							//console.log("nyet " + elementOfSingularity);
							randomExcl = 1; //the singularity is excluded
						} else {
							//console.log("allow " + elementOfSingularity);
							randomExcl = 0;
						};
					};

					if(typeof(elements[elementOfSingularity].nocheer !== "undefined")) { //if nocheer exists (prevent TypeError)
						if(elements[elementOfSingularity].nocheer) { //it it's true
							//console.log("nyet " + elementOfSingularity);
							isNocheer = 1; //the singularity is excluded
						} else {
							//console.log("allow " + elementOfSingularity);
							isNocheer = 0;
						};
					};
				};
					//Color gen
				if(Array.isArray(startColor)) { //Average arrays, make colors rgb()
					startColor = averageRgbPrefixedColorArray(startColor);
				} else {
					startColor = rgbHexCatcher(startColor);
				};

				//console.log(`rgbStringToObject(${startColor}) from more_singularities.js`)
				var newColorObjectArray = [];			
				var newColorArray = [];

				for(i = 0; i < singularityColorTemplate.length; i++) {
					var newSingularityColorlet = singularityColorTemplate[i];
					var newColor = multiplyColors(startColor,newSingularityColorlet);
					var newColorJSON = multiplyColors(startColor,newSingularityColorlet,"json");
					newColorArray.push(newColor);
					newColorObjectArray.push(newColorJSON);
				};

					//End color gen

												//The singularity

				//console.log(elementOfSingularity);
				var firstInfo, firstTemp;
				if(Array.isArray(elementOfSingularity)) {
					firstInfo = elements[elementOfSingularity[0]];
					firstTemp = airTemp;
					if(typeof(firstInfo.temp) !== "undefined") {
						firstTemp = firstInfo.temp;
					};
				} else {
					firstInfo = elements[elementOfSingularity];
					firstTemp = airTemp;
					if(typeof(firstInfo.temp) !== "undefined") {
						firstTemp = firstInfo.temp;
					};
				};

				var finalDensity = 0;
				if(Array.isArray(elementOfSingularity)) {
					for(i = 0; i < elementOfSingularity.length; i++) {
						info = elements[elementOfSingularity[i]];
						finalDensity += (info.density || 1000) * singularityNumber;
					};
					finalDensity /= elementOfSingularity.length;
				} else {
					info = elements[elementOfSingularity];
					finalDensity = (info.density || 1000) * singularityNumber;
				};

				elementOfSingularity = tryJoin(elementOfSingularity,",");

				//console.log(elementOfSingularity);

				var returns = [];

				if(!elementExists(singularityName)) {
					elements[singularityName] = {
						color: newColorArray,
						colorObject: newColorObjectArray,
						behavior: behaviors.POWDER_OLD,
						category: "singularities",
						temp: firstTemp,
						hardness: 0.995,
						singularityNumber: null,
						originalElementDisplay: elements[elementOfSingularity.replaceAll(",","_")]?.originalElementKey ?? elementOfSingularity.split(","),
						originalElementKey: elements[elementOfSingularity.replaceAll(",","_")]?.originalElementKey ?? elementOfSingularity.split(","),
						state: "solid",
						density: finalDensity,
					};
					var newInfo = elements[singularityName];
					elements[singularityName].originalElementDisplay = newInfo.originalElementKey.map(x => elements[x].name ?? x);
					if(elements[singularityName].originalElementDisplay.length == 1) { elements[singularityName].originalElementDisplay = elements[singularityName].originalElementDisplay[0] };
					if(singularityElements[0] instanceof Array) {
						elements[singularityName].singularityNumber = 1;
					} else {
						if(typeof(elements[singularityElements[0]].singularityNumber) === "undefined") {
							elements[singularityName].singularityNumber = 1
						} else {
							elements[singularityName].singularityNumber = elements[singularityElements[0]].singularityNumber + 1;
						};
					};
					var num = newInfo.singularityNumber ?? NaN;
					var descTypeString = (num == 1 ? "singularity" : `${num.toString()}-singularity`);
					var descNumberString = (num < 4 ? (10 ** (num * 4)).toLocaleString("en-US") : `10<sup>${num * 4}</sup>`);
					var descElementString = tryJoin((newInfo.originalElementDisplay ?? "[Original element could not be determined]"),", ");
					elements[singularityName].desc = `A ${descTypeString} normally made of ${descNumberString} pixels of ${descElementString}.`.replaceAll("NaN","[Quantity could not be determined]");
					if(singularityName.includes("haseulite") && !singularityName.includes("haseulite_vent")) {
						elements[singularityName].tick = function(pixel) { haseulitoidSingularityTick(pixel) };
						haseuliteSpreadWhitelist.push(singularityName);
					};
					if(typeof(eLists) === "undefined") {
						eLists = {};
					};
					if(typeof(eLists.SINGULARITY) === "undefined") {
						eLists.SINGULARITY = [];
					};
					eLists.SINGULARITY.push(singularityName);
					if(!randomExcl) {
						if(typeof(singularityChoices) === "undefined") {
							singularityChoices = []
						};
						if(!singularityChoices.includes(singularityName)) {
							singularityChoices.push(singularityName);
						};
					}
					if(isNocheer) {
						elements[singularityName].nocheer = true;
					}
					if(singularityIncludeRandom) {
						randomExcl ? elements[singularityName].excludeRandom = true : elements[singularityName].excludeRandom = false;
					} else {
						elements[singularityName].excludeRandom = true;
					};
					if(isAfterScriptLoading) {
						elementCount++; //increment for new singularity element
						if (settings.cheerful && elements[singularityName].nocheer) {
							elements[singularityName].hidden = true;
							hiddenCount++;
						} else {						
							createElementButton(singularityName);
						};
						elements[singularityName].id = nextid++;
						document.getElementById("extraInfo").innerHTML = "<small><p>There are " + elementCount + " elements, including " + hiddenCount + " hidden ones.</p><p>©2021-" + new Date().getFullYear() + ". All Rights Reserved. <a href='https://r74n.com'>R74n</a></p></small>"; //update extra info counts (and the copyright year, due to the method used)
					};
				};
				count++;
				returns.push(singularityName);
			};
			return returns;
		};

		elements.neutronium_compressor = {
			color: "#e7e7ee",
			properties: {
				range: 4,
				outputOffset: [0, 5],
				absorbed: {}
			},
			category: "machines",
			behavior: behaviors.WALL,
			state: "solid",
			density: 54000,
			hardness: 1,
			excludeRandom: true,
			tick: function(pixel) {
				if(pixel.range == undefined) {
					pixel.range = 4;
				};
				if(pixel.outputOffset == undefined) {
					pixel.outputOffset = [0, 5];
				};
				if(pixel.absorbed == undefined) {
					pixel.absorbed = {};
				};
				for(i = -(pixel.range); i <= (pixel.range); i++) {
					for(j = -(pixel.range); j <= (pixel.range); j++) {
						if(i == 0 & j == 0) { continue };
						var fX = pixel.x+i;
						var fY = pixel.y+j;
						if(!isEmpty(fX,fY,true)) {
							var newPixel = pixelMap[fX][fY];
							var newElement = newPixel.element;
							if(newElement !== "neutronium_compressor") {
								//Jinsoulite handling
								if(typeof(jinsouliteSpreadWhitelist) !== "undefined") {
									if(jinsouliteSpreadWhitelist.includes(newPixel.element)) {
										if(newPixel.value > 0) { //if jinsoulitoid and value is positive
											//if compressor has no recorded water, initialize to zero
											if(typeof(pixel.absorbed.water) === "undefined") { pixel.absorbed.water = 0 };
											//add jinsoulite's water to compressor water
											pixel.absorbed.water += newPixel.value;
										};
									};
								};

								//Alkahest handling
								if(newPixel.element === "alkahest") {
									//get properties that are actually elements
									var elementEntries = Object.keys(newPixel).filter(function(key) { return elementExists(key) });
									for(i = 0; i < elementEntries.length; i++) {
										//iterate through element properties
										//store elemname for readability
										var key = elementEntries[i];
										//store amount for readability
										var value = newPixel[key];
										//initialize nonexistent names
										if(typeof(pixel.absorbed[key]) === "undefined") {
											pixel.absorbed[key] = 0;
										};
										//add amounts
										pixel.absorbed[key] += value;
									};
								};

								if(typeof(pixel.absorbed[newElement]) === "undefined") {
									pixel.absorbed[newElement] = 0;
								};
								pixel.absorbed[newElement]++;
								deletePixel(fX,fY);
							};
						};
					};
				};
				var outputPos = {x: pixel.x+pixel.outputOffset[0], y: pixel.y+pixel.outputOffset[1]};
				if(Object.keys(pixel.absorbed).length > 0) {
					for(elementName in pixel.absorbed) {
						if(pixel.absorbed[elementName] >= singularityNumber) {
							if(isEmpty(outputPos.x,outputPos.y,false)) {
								if(!elementExists(`${elementName}_singularity`)) {
									generateSingularity(elementName,true);
								};
								createPixel(`${elementName}_singularity`,outputPos.x,outputPos.y);
								pixel.absorbed[elementName] -= singularityNumber;
							} else {
								break;
							};
						};
					};
				};
				/*
				for(q = -2; q <= 2; q++) {
					for(q2 = -2; q2 <= 2; q2++) {
						if(Object.keys(pixel.absorbed).length > 0) {
							for(elementName in pixel.absorbed) {
								if(pixel.absorbed[elementName] >= singularityNumber) {
									if(isEmpty(outputPos.x+q,outputPos.y+q2,false)) {
										if(!elementExists(`${elementName}_singularity`)) {
											generateSingularity(elementName,true);
										};
										createPixel(`${elementName}_singularity`,outputPos.x+q,outputPos.y+q2);
										pixel.absorbed[elementName] -= singularityNumber;
									} else {
										break;
									};
								};
							};
						};
					};
				};
				*/
			},
		};

		runAfterAutogen(function() {
			if(generateSingularities) {
				singularityArray = Object.keys(elements);
				generateSingularity(singularityArray,false);
			};
		});

		if(typeof(singularityChoices) === "undefined") {
			singularityChoices = [];
		};

		elements.spawn_random_singularity = {
			color: ["#3e5f8a","#a334ec","#ea96f9","#a6ecf6","#70ebc8","#d9286b","#7eed91","#a18b30"],
			behavior: behaviors.WALL,
			category: "special",
			excludeRandom: true,
			tick: function(pixel) {
				singularityChoices.length == 0 ? deletePixel(pixel.x,pixel.y) : changePixel(pixel,singularityChoices[Math.floor(Math.random() * singularityChoices.length)]);
			},
		};

	//POST-LOADING ELEMENT GENERATION ##

		var lategenOptions = ["creeper","spout","fairy","cloud","bomb","singularity"];
		lgoDisplayString = lategenOptions.join(", ");

		document.addEventListener("keydown", function(e) { //prop prompt listener
			// , = propPrompt()
			if (e.keyCode == 71) { //G
				if(shiftDown) { generatorPrompt() };
			};
		});

		function generatorPrompt() {
			var type = prompt(`Enter what kind of thing you want to generate
			Valid options: ${lgoDisplayString}`);
			var elements, typePlural;
			if(type === null) {
				return false;
			};
			if(!lategenOptions.includes(type)) {
				alert("Type is not valid!");
				return false;
			} else {
				if(type === "fairy") {
					typePlural = "fairies"
				} else {
					typePlural = type + "s";
				};
				elements = prompt(`Enter the element(s) you want to generate ${typePlural} for.
				Elements are separated by commas; to use a combination of elements, the elements are separated by plus signs (like "gold_coin+diamond").`);
				elements = parseForLateGenerationParameter(elements);
				try {
					var amount = 0;
					switch(type) {
						case "creeper":
							amount += generateCreeper(elements,true).length;
							break;
						case "spout":
							amount += generateSpout(elements,true).length;
							break;
						case "fairy":
							amount += generateFairy(elements,true).length;
							break;
						case "cloud":
							var number = prompt(`Enter a cloud number between 0 and 5 (default: 0)
							A higher number means a rainier cloud.`);
							if(number !== "*") { isNaN(parseFloat(number)) ? number = 0 : number = parseFloat(number) };
							amount += generateCloud(elements,number,true).length;
							break;
						case "singularity":
							amount += generateSingularity(elements,true).length;
							break;
						case "bomb":
							var number = prompt(`Enter a bomb number (default: 1)
							1 corresponds to radius 10, 2 corresponds to radius 15, etc.`);
							isNaN(parseFloat(number)) ? number = 1 : number = parseFloat(number);
							amount += generateBomb(elements,true,number).length;
							break;
						default:
							alert("An invalid type made it past the if statement. You shouldn't ever see this error.");
							throw new Error("An invalid type made it through the if statement.");
					};
					alert(`Generated ${amount} ${amount == 1 ? "element" : "elements"}`);
				} catch (error) {
					var errorString = error.toString();
					var errorText = "";
					if(errorString.includes("Cannot read properties of undefined")) {
						errorText += "\r\n(This is most likely from a nonexistent or misspelled element)";
					};
					alert("There was an error!\r\n" + error.toString() + errorText);
					throw error; //for console
				};
			};
		};

		elements.generator_prompt = {
			color: ["#000000","#666666","#886622","#558800"],
			behavior: behaviors.SELFDELETE,
			desc: "<span style='color:#FF00FF;' onClick=generatorPrompt()>Click here or press Shift+G to open the generator prompt.</span>",
			category:"special",
		};

		function parseForLateGenerationParameter(input) {
			if(input == null) { return };
			if(input.startsWith("*")) {
				var elemList = Object.keys(elements);
				input = input.toLowerCase().substring(1);
				if(input === "all") {
					return elemList;
				} else {
					input = input.split(" "); //'*nocreeper nofairy' to ['nocreeper' 'nofairy']
					//include no auto elements
					if(input.includes("noauto")) {
						return Object.keys(elements).filter(function(name) { return elements[name].autoType === undefined });
					};


					var filteredList = elemList;

					//include only auto elements
					if(input.includes("auto")) {
						filteredList = filteredList.filter(function(name) { return elements[name].autoType !== undefined });
						//console.log("limit to autogens", filteredList.length);
					};
					//exclude fairies
					if(input.includes("nofairy") || input.includes("nofairies")) {
						filteredList = filteredList.filter(function(name) { return elements[name].autoType !== "fairy" });
						//console.log("remove fairies", filteredList.length);
					};
					//exclude spouts
					if(input.includes("nospout") || input.includes("nospouts")) {
						filteredList = filteredList.filter(function(name) { return elements[name].autoType !== "spout" });
						//console.log("remove spouts", filteredList.length);
					};
					//exclude creepers
					if(input.includes("nocreeper") || input.includes("nocreepers")) {
						filteredList = filteredList.filter(function(name) { return elements[name].autoType !== "creeper" });
						//console.log("remove creepers", filteredList.length);
					};
					//exclude clouds
					if(input.includes("nocloud") || input.includes("noclouds")) {
						filteredList = filteredList.filter(function(name) { return elements[name].autoType !== "cloud" });
						//console.log("remove clouds", filteredList.length);
					};
					//exclude bombs
					if(input.includes("nobomb") || input.includes("nobombs")) {
						filteredList = filteredList.filter(function(name) { return elements[name].autoType !== "bomb" });
						//console.log("remove bombs", filteredList.length);
					};

					//console.log(input);
					return filteredList;
				};
			};
			if(typeof(input) === "string") { //it should be an array, so string check
				input = input.replace(/ /g,"_");
				//console.log("String detected");
				if(input.includes(",")) { //comma-separated string?
					//console.log("Splitting string to array");
					input = input.split(","); //,SS to array
				} else {
					//console.log("Wrapping string in array");
					input = [input]; //single string to array 
				};
			};
			for(i = 0; i < input.length; i++) {
				input[i] = input[i].replace(/ /g,"_");
				if(input[i].includes("+")) {
					input[i] = input[i].split("+")
				};
			};
			return input;
		};

	//WEATHER MACHINE ##

		function pixelRain(element,density=0.1,rainRelativeBottom=0.35,rainRelativeTop=0) {
			for(i = 1; i < width; i++) {
				for(j = Math.round(height * rainRelativeTop); j < Math.round(height * rainRelativeBottom); j++) {
					if(Math.random() < density && isEmpty(i,j)) {
						while(element instanceof Array) { element = randomChoice(element) };
						createPixel(element,i,j)
					}
				}
			}
		};

		generateCloud("water",3,false);
		generateCloud("blood",0,false);
		generateCloud("snow",3,false);
		generateCloud("sand",1,false);

		_weatherElems = ["cloud", "rain_cloud", "heaviest_water_cloud", "blood_cloud", "snow_cloud", "heaviest_snow_cloud", "hail_cloud", "hail_cloud", "fireball", "fire_cloud", "magma_cloud", "heavy_sand_cloud", "electric", "lightning"];

		elements.weather_controller = {
			color: "#ebdf91",
			behavior: [
				"XX|M2 AND SA|XX",
				"SA|XX|SA",
				"XX|M1|XX"
			],
			breakInto: ["steel_scrap","iron_scrap","copper_scrap","gold_scrap","cloud","emerald","magic"],
			tempHigh: 50000,
			stateHigh: ["molten_steel","molten_iron","molten_copper","molten_gold","hydrogen","oxygen","molten_aluminum","silica_gas","magic"],
			tick: function(pixel) {
				if(!pixel.charge) { return };
				if(pixel.charge) {
					if(isEmpty(pixel.x,pixel.y+1,true)) { return };
					var pixelUnder = pixelMap[pixel.x]?.[pixel.y+1];
					if(!pixelUnder) { return };
					switch(pixelUnder.element) {
						case "cloud":
							pixelRain("cloud",0.1,0.3);
							break;
						case "steam":
							pixelRain("cloud",0.2,0.35);
							break;
						case "rain_cloud":
							pixelRain("rain_cloud",0.2,0.25);
							break;
						case "water":
							pixelRain("rain_cloud",0.4,0.3);
							break;
						case "ketchup":
							pixelRain("ketchup_cloud",0.3,0.35);
							break;
						case "jinsoulite_powder":
						case "jinsoulite_gas":
						case "molten_jinsoulite":
						case "jinsoulite":
							pixelRain("heaviest_water_cloud",0.8,0.35);
							break;
						case "blood":
							pixelRain("blood_cloud",0.2,0.25);
							break;
						case "snow":
							pixelRain("snow_cloud",0.2,0.25);
							break;
						case "packed_snow":
							pixelRain("snow_cloud",0.4,0.3);
							break;
						case "haseulite":
						case "haseulite_powder":
						case "molten_haseulite":
						case "haseulite_gas":
							pixelRain("heaviest_snow_cloud",0.5,0.35);
							pixelRain("diamond",0.002,0.35);
							pixelRain("hail_cloud",0.05,0.2);
							break;
						case "liquid_nitrogen":
							pixelRain("liquid_nitrogen",	0.3,1);
							pixelRain("liquid_oxygen",		0.1,1);
							pixelRain("liquid_argon",		0.004,1);
							currentPixels.forEach(pixel => pixel.temp = Math.min(pixel.temp,elements.liquid_nitrogen.tempLow - 5));
							break;
						case "ice":
							pixelRain("hail_cloud",0.2,0.3);
							break;
						case "fire":
							pixelRain("fire_cloud",0.15,0.3);
							break;
						case "magma":
							pixelRain("magma_cloud",0.2,0.3);
							break;
						case "sand":
							pixelRain("heavy_sand_cloud",0.4,1,0.2);
							pixelRain("sand",0.05,1,0.2);
							break;
						case "battery":
							pixelRain("electric",0.3,0.35);
							pixelRain("lightning",0.002,0.2);
							break;
						case "sun":
						case "stellar_plasma":
						case "liquid_stellar_plasma":
						case "plasma":
							settings.bg = "#93c3e1"
							break;
						case "moonrock":
							settings.bg = "#000000";
							break;
						case "oxygen":							
							for(var i in "six  ") {
								currentPixels.forEach(function(pixel) {
									if(_weatherElems.includes(pixel.element)) {
										deletePixel(pixel.x,pixel.y);
										return
									}
								});
							};

							currentPixels.forEach(function(pixel) {
								var data = elements[pixel.element];
								var tl = data.tempLow;
								var th = data.tempHigh;
								var noTL = (typeof(tl) == "undefined");
								var noTH = (typeof(th) == "undefined");
								if(noTL && noTH) {
									pixel.temp = airTemp ?? 20
								} else if(noTL && !noTH) {
									if(th < airTemp) {
										pixel.temp = th - 10;
									} else {
										pixel.temp = airTemp
									}
								} else if(!noTL && noTH) {
									if(tl > airTemp) {
										if(tl == Infinity) {
											pixel.temp = airTemp
										} else {
											pixel.temp = tl + 10
										}
									} else {
										pixel.temp = airTemp
									}
								};
								return
							});
							break;
					};
					if(pixelUnder) { deletePixel(pixelUnder.x,pixelUnder.y) };
					delete pixel.charge;
					pixel.chargeCD = 4;
					return true;
				}
			},
			conduct: 1,
			category: "machines",
			hardness: 0.6
		};

	//KETCUP ##

		elements.ketcup = {
			color: "#ab2513",
			behavior: behaviors.LIQUID,
			reactions: {
				"rust": { elem2:"iron", chance:0.01 },
				"oxidized_copper": { elem2:"copper", chance:0.01 },
			},
			viscosity: 50000,
			tempHigh: 260,
			stateHigh: ["vinegar","steam","salt","sugar"],
			category:"liquids",
			state: "liquid",
			density: 1235,
			stain: 0.05,
			isFood: true
		};

		elements.ruphire = {
			color: ["#7C319B", "#BC4F80", "#692287", "#B13B77", "#772A94"],
			tempHigh: 2040,
			behavior: behaviors.POWDER,
			category: "powders",
			state: "solid",
			density: 3980,
			hardness: 0.9,
		};

		standaloneBrokenFormMaker("ruphire","shard",true,"powders","auto","auto","molten_ruphire",["alumina","alumina","alumina","alumina","alumina","alumina","alumina","alumina","alumina","iron_scrap","titanium_scrap","chromium_scrap","chromium_scrap"]).hidden = true;

		elements.molten_ruby ??= {};
		elements.molten_ruby.reactions ??= {};
		elements.molten_sapphire ??= {};
		elements.molten_sapphire.reactions ??= {};
		elements.molten_ruby.reactions.molten_sapphire = {
			elem1: "molten_ruphire",
			elem2: "molten_ruphire"
		}; //they don't make garnet :'(
		elements.molten_sapphire.reactions.molten_ruby = elements.molten_ruby.reactions.molten_sapphire;

	//REPLACER TOOL ##

		replaceFrom = "rock";
		replaceTo = "sand";

		document.addEventListener("keydown", function(e) { //replace prompt listener
			// r = replaceElementPrompt()
			if (e.keyCode == 222) {
				e.preventDefault();
				replaceElementPrompt();
			}
		});

		function replaceElementPrompt() {
			var fromElement = prompt("Enter the element you want to change");
			// replace spaces with underscores
			fromElement = fromElement.replace(/ /g, "_");
			fromElementS = mostSimilarElement(fromElement);
			if (fromElementS === null || fromElementS === undefined || fromElementS === "") {
				alert("Element \"" + fromElement + "\" not found! Defaulting to rock.");
				fromElementS = "rock";
			};

			var toElement = prompt("Enter what you want to replace \"" + fromElementS + "\" with");
			// replace spaces with underscores
			toElement = toElement.replace(/ /g, "_");
			toElementS = mostSimilarElement(toElement);
			if (toElementS === null || toElementS === undefined || toElementS === "") {
				alert("Element \"" + toElement + "\" not found! Defaulting to sand.");
				toElementS = "sand";
			};
			replaceFrom = fromElementS;
			replaceTo = toElementS;
			updateReplaceDescriptions();	
		}

		function updateReplaceDescriptions() {
			elements.replace.desc = "Changes pixels of a specified type to another specified type.<br/>Currently replacing \"" + replaceFrom + "\" with \"" + replaceTo + "\".<br/><span onclick=replaceElementPrompt() style=\"color: #ff00ff;\";>Press [\"] or click here</span> to open the replace prompt.";
			elements.alt_replace.desc = "Changes pixels of a specified type to another specified type, but keeping their non-element-based properties.<br/>Currently replacing \"" + replaceFrom + "\" with \"" + replaceTo + "\".<br/><span onclick=replaceElementPrompt() style=\"color: #ff00ff;\";>Press [\"] or click here</span> to open the replace prompt.";
			elements.alt_alt_replace.desc = "Changes pixels of a specified type to another specified type, but keeping their non-element-based properties except for color.<br/>Currently replacing \"" + replaceFrom + "\" with \"" + replaceTo + "\".<br/><span onclick=replaceElementPrompt() style=\"color: #ff00ff;\";>Press [\"] or click here</span> to open the replace prompt.";
		};

		elements.replace = {
			color: ["#ff0000", "#ff0000", "#ff0000", "#7f00ff", "#0000ff", "#0000ff", "#0000ff"],
			tool: function(pixel) {
				if(pixel.element === replaceFrom) {
					changePixel(pixel,replaceTo,true);
				};
			},
			category: "tools",
			desc: "Changes pixels of a specified type to another specified type.<br/>Currently replacing \"" + replaceFrom + "\" with \"" + replaceTo + "\".<br/><span onclick=replaceElementPrompt() style=\"color: #ff00ff;\";>Press [\"] or click here</span> to open the replace prompt.",
		};

		elements.alt_replace = {
			color: ["#ffff00", "#ffff00", "#ffff00", "#cf7f4f", "#ff00ff", "#ff00ff", "#ff00ff"],
			tool: function(pixel) {
				if(pixel.element === replaceFrom) {
					pixel.element = replaceTo;
				};
			},
			category: "tools",
			desc: "Changes pixels of a specified type to another specified type, but keeping their non-element-based properties.<br/>Currently replacing \"" + replaceFrom + "\" with \"" + replaceTo + "\".<br/><span onclick=replaceElementPrompt() style=\"color: #ff00ff;\";>Press [\"] or click here</span> to open the replace prompt.",
			hidden: true,
		};

		elements.alt_alt_replace = {
			color: ["#00ff00", "#00ff00", "#00ff00", "#cfcf00", "#ff0000", "#ff0000", "#ff0000"],
			tool: function(pixel) {
				if(pixel.element === replaceFrom) {
					pixel.element = replaceTo;
					pixel.color = pixelColorPick(pixel);
				};
			},
			category: "tools",
			desc: "Changes pixels of a specified type to another specified type, but keeping their non-element-based properties except for color.<br/>Currently replacing \"" + replaceFrom + "\" with \"" + replaceTo + "\".<br/><span onclick=replaceElementPrompt() style=\"color: #ff00ff;\";>Press [\"] or click here</span> to open the replace prompt.",
			hidden: true,
		};

	//OLD PROP AND NUMBER CHANGER TOOLS ##

		propProperty = "element";
		propValue = "sand";
		propType = "string";
		numberAdjusterProperty = "temp";
		numberAdjusterValue = 1;
		numberAdjusterMode = "add";
		numberAdjusterVerb = "adding";
		numberAdjusterPreposition = "to";
		numberAdjusterReverseOrder = false;

		function rgbStringToUnvalidatedObject(string) {
			string = string.split(",");
			var red = parseFloat(string[0].substring(4));
			var green = parseFloat(string[1]);
			var blue = parseFloat(string[2].slice(0,-1));
			return {r: red, g: green, b: blue};
		};
		function hslStringToUnvalidatedObject(string) {
			string = string.split(",");
			var hue = parseFloat(string[0].substring(4));
			var saturation = parseFloat(string[1].slice(0,-1));
			var lightness = parseFloat(string[2].slice(0,-2));
			return {h: hue, s: saturation, l: lightness};
		};


		document.addEventListener("keydown", function(e) { //prop prompt listener
			// , = propPrompt()
			if (e.keyCode == 188) {
				//e.preventDefault();
				shiftDown ? numberAdjusterPrompt() : propPrompt();
			};
		});

		function propPrompt() {
			propProperty = prompt("Enter the property you want to set");

			propValue = prompt("Enter the value you want to set to");

			//special check: element
			if(propProperty === "element") {
				//empty string
				if(propValue === "") {
					alert("No element was specified!");
					return false;
				};
				// replace spaces with underscores
				propValue = propValue.replace(/ /g, "_");
				var propValueS = mostSimilarElement(propValue);
				if (propValueS === null || propValueS === undefined) {
					alert("Element \"" + value + "\" not found! Defaulting to sand.");
					propValue = "sand";
				} else {
					propValue = propValueS;
				};
			};

			//special check: color
			if(propProperty === "color") {
				//empty string
				if(propValue === "") {
					alert("No color was specified!");
					return false;
				};
				var splitValue = propValue.split(",");
				if(!propValue.startsWith("rgb(")) { //if not RGB
					if(propValue.startsWith("hsl(")) { //if HSL
						if(!(splitValue[1].endsWith('%')) || !(splitValue[2].endsWith('%)'))) { //if missing percent symbols
							alert(colorInvalidError);
							return false;
						};
					} else { //if not RGB and not HSL
						alert(colorInvalidError);
						return false;
					};
				};
				if(propValue.split(",").length !== 3) { //if too short or long
					alert(colorInvalidError);
					return false;
				}
				if(propValue.startsWith("rgb(")) { //if RGB
					var checkedColorObject = rgbStringToUnvalidatedObject(propValue); //RGB NaN checking
					if(isNaN(checkedColorObject.r) || isNaN(checkedColorObject.g) || isNaN(checkedColorObject.b)) {
						//console.log(checkedColorObject);
						alert("One or more color values are invalid!");
						return false;
					};
				} else if(propValue.startsWith("hsl(")) { //if HSL
					var checkedColorObject = hslStringToUnvalidatedObject(propValue); //HSL NaN checking
					if(isNaN(checkedColorObject.h) || isNaN(checkedColorObject.s) || isNaN(checkedColorObject.l)) {
						//console.log(checkedColorObject);
						alert("One or more color values are invalid!");
						return false;
					};
				} else { //if neither
					alert(colorInvalidError);
					return false;
				};
			};

			//special check: x
			if(propProperty === "x") {
				//empty string
				if(!propValue.isInteger) {
					alert("X values must be integers!");
				};
			};

			if(propProperty == null) { return };

			if(defaultNumberTypeValues.includes(propProperty.toLowerCase())) {
				propType = "number";
			} else if(defaultBooleanTypeValues.includes(propProperty.toLowerCase())) {
				propType = "boolean";
			} else if(defaultStringTypeValues.includes(propProperty.toLowerCase())) {
				propType = "string";
			} else if(defaultArrayTypeValues.includes(propProperty.toLowerCase())) {
				propType = "array";
			} else {
				propType = prompt("Enter the type of the value");
				if(stringSynonyms.includes(propType)) {
					propType = "string";
				} else if(numberSynonyms.includes(propType)) {
					propType = "number"; //Infinity (case-sensitively) is a *number*.
				} else if(booleanSynonyms.includes(propType)) {
					propType = "boolean";
				} else if(objectSynonyms.includes(propType)) {
					propType = "object"; //null (case-sensitively) is an *object*.
				} else if(arraySynonyms.includes(propType)) {
					propType = "array"; //offset coords use arrays a lot
				};
			};

			//Conversion
			if(propType === "number") {
				propValue = parseFloat(propValue);
				if(isNaN(propValue)) {
					alert("Value is not a number!");
					return false;
				};
			} else if(propType === "boolean") {
				if(synonymsOfTrue.includes(propValue.toLowerCase())) {
					propValue = true;
				} else if(synonymsOfFalse.includes(propValue.toLowerCase())) {
					propValue = false;
				} else {
					alert("Unrecognized boolean value: " + propValue + ".");
					return false;
				};
			} else if(propType === "object") {
				try {
					propValue = JSON.parse(propValue);
				} catch (error) {
					alert("JSON is invalid! Note that it requires quotes around keys as well as those curly {} parentheses.");
					return false;
				};
			} else if(propType === "array") {
				array = propValue.split(",");
				for(i = 0; i < array.length; i++) {
					if(array[i].startsWith("s")) { //String
						array[i] = array[i].substring(1);
					} else if(array[i].startsWith("n")) { //Number
						array[i] = array[i].substring(1);
						if(isNaN(parseFloat(array[i]))) {
							alert(array[i] + " is not a number!");
							return false;
						};
						array[i] = parseFloat(array[i]);
					} else if(array[i].startsWith("o")) { //Object
						array[i] = array[i].substring(1);
						try {
							array[i] = JSON.parse(array[i]);
						} catch (error) {
							alert(array[i] + " is not valid JSON!");
							return false;
						};
					} else if(array[i].startsWith("b")) { //Boolean
						array[i] = array[i].substring(1);
						if(synonymsOfTrue.includes(array[i].toLowerCase())) {
							array[i] = true;
						} else if(synonymsOfFalse.includes(array[i].toLowerCase())) {
							array[i] = false;
						} else {
							alert("Unrecognized boolean value: " + array[i] + ".");
							return false;
						};
					} else {
						alert(array[i] + ' must start with "s" for a string, "n" for a number, "o" for an object, or "b" for a boolean.');
						return false;
					};
				};
				propValue = array;
			} else if(propType !== "string") {
				alert("Unrecognized or unsupported type!");
				return false;
			};
			updatePropDescription();
			currentElement = "old_prop";
		};

		elements.old_prop = {
			color: "#ff7f00",
			tool: function(pixel) {
				if(propProperty === "element") {
					pixel[propProperty] = propValue;
					pixel.temp = (elements[propValue].temp || pixel.temp);
				} else {
					pixel[propProperty] = propValue;
				};
				pixelTempCheck(pixel);
			},
			category: "tools",
			desc: `Sets properties of pixels.<br/>Currently setting ${propProperty} to ${propValue} (${propType}).<br/><span onclick=propPrompt() style=\"color: #ff00ff;\";>Press [,] or click here</span> to open the property tool prompt.`,
		};

		function updatePropDescription() {
			elements.old_prop.desc = `Sets properties of pixels.<br/>Currently setting ${propProperty} to ${propValue} (${propType}).<br/><span onclick=propPrompt() style=\"color: #ff00ff;\";>Press [,] or click here</span> to open the property tool prompt.`;
		};

		function numberAdjusterPrompt() {
			var oldProperty = numberAdjusterProperty;
			if(oldProperty === null) {
				oldProperty = "temp";
			};
			numberAdjusterProperty = prompt("Enter the property you want to change");
			if(numberAdjusterProperty === null) {
				numberAdjusterProperty = oldProperty;
				return false;
			};
			numberAdjusterValue = prompt("Enter the value you want to use");
			numberAdjusterMode = prompt("Enter the operation you want to use");

			//property check
			if(numberAdjusterProperty === "") {
				alert("No property was specified! Defaulting to temp.");
				numberAdjusterProperty = "temp";
			};

			//value check
			if(isNaN(parseFloat(numberAdjusterValue))) {
				if(numberAdjusterValue === "" || numberAdjusterValue === null) {
					//console.log("Null value path");
					alert("No value was specified! Defaulting to 1");
					numberAdjusterValue = 1;
					//console.log(numberAdjusterValue);
				} else {
					//console.log("NaN value path");
					alert("Invalid value! The value must be a number (defaulting to 1)");
					numberAdjusterValue = 1;
					//console.log(numberAdjusterValue);
				};
			};
			numberAdjusterValue = parseFloat(numberAdjusterValue);
			//console.log("Value: " + numberAdjusterValue);

			//mode check

			if(numberAdjusterMode === null) {
				alert("No operation was specified! Defaulting to add.");
				numberAdjusterMode = "add";
			};

			numberAdjusterMode = numberAdjusterMode.toLowerCase();

			var opNames = ["+", "add", "addition", "plus", "increase", "increment", "-", "subtract", "subtraction", "minus", "take away", "takeaway", "decrease", "decrement", "*", "x", "×", "multiply", "multiplication", "times", "by", "/", "÷", "divide", "division", "divided by", "%", "mod", "modulo", "modulus", "modulo by", "=", "set", "equals", "assign", "assignment", ">", ">=", "min", "minimum", "<", "<=", "max", "maximum", "^", "**", "exp", "exponent", "exponentiate", "raise", "raise to", "raised to"];

			switch(numberAdjusterMode) {
				case "+":
				case "add":
				case "addition":
				case "plus":
				case "increase":
				case "increment":
					numberAdjusterVerb = "adding";
					numberAdjusterPreposition = "to";
					numberAdjusterReverseOrder = false;
					break;
				case "-":
				case "subtract":
				case "subtraction":
				case "minus":
				case "take away":
				case "takeaway":
				case "decrease":
				case "decrement":
					numberAdjusterVerb = "subtracting";
					numberAdjusterPreposition = "from";
					numberAdjusterReverseOrder = false;
					break;
				case "*":
				case "x":
				case "×":
				case "multiply":
				case "multiplication":
				case "times":
				case "by":
					numberAdjusterVerb = "multiplying";
					numberAdjusterPreposition = "by";
					numberAdjusterReverseOrder = true;
					break;
				case "/":
				case "÷":
				case "divide":
				case "division":
				case "divided by":
					numberAdjusterVerb = "dividing";
					numberAdjusterPreposition = "by";
					numberAdjusterReverseOrder = true;
					break;
				case "%":
				case "mod":
				case "modulo":
				case "modulus":
				case "modulo by":
					numberAdjusterVerb = "reducing";
					numberAdjusterPreposition = "modulo";
					numberAdjusterReverseOrder = true;
					break;
				case "=":
				case "set":
				case "equals":
				case "assign":
				case "assignment":
					numberAdjusterVerb = "setting";
					numberAdjusterPreposition = "to";
					numberAdjusterReverseOrder = true;
					break;
				case ">": //lower-bounds the color
				case ">=":
				case "min":
				case "minimum":
					numberAdjusterVerb = "lower-bounding";
					numberAdjusterPreposition = "to";
					numberAdjusterReverseOrder = true;
					break;
				case "<":
				case "<=":
				case "max": //upper-bounds the color
				case "maximum":
					numberAdjusterVerb = "limiting";
					numberAdjusterPreposition = "to";
					numberAdjusterReverseOrder = true;
					break;
				case "^":
				case "**":
				case "exp":
				case "exponent":
				case "exponentiate":
				case "raise":
				case "raise to":
				case "raised to":
					numberAdjusterVerb = "raising";
					numberAdjusterPreposition = "to";
					numberAdjusterReverseOrder = true;
					break;
				default:
					alert(`Invalid operation (defaulting to "add")!`);
					numberAdjusterMode = "add";
					numberAdjusterVerb = "adding";
					numberAdjusterPreposition = "to";
					numberAdjusterReverseOrder = false;
					break;
			};
			updateNumberAdjusterDescription();
			currentElement = "number_adjuster";
		};

		elements.number_adjuster = {
			color: "#7fff00",
			tool: function(pixel) {
				if(typeof(pixel[numberAdjusterProperty]) === "undefined") {
					pixel[numberAdjusterProperty] = 0;
				};
				if(typeof(pixel[numberAdjusterProperty]) === "number") {
					switch(numberAdjusterMode.toLowerCase()) {
						case "+":
						case "add":
						case "addition":
						case "plus":
						case "increase":
						case "increment":
							pixel[numberAdjusterProperty] += numberAdjusterValue;
							break;
						case "-":
						case "subtract":
						case "subtraction":
						case "minus":
						case "take away":
						case "takeaway":
						case "decrease":
						case "decrement":
							pixel[numberAdjusterProperty] -= numberAdjusterValue;
							break;
						case "*":
						case "x":
						case "×":
						case "multiply":
						case "multiplication":
						case "times":
						case "by":
							pixel[numberAdjusterProperty] *= numberAdjusterValue;
							break;
						case "/":
						case "÷":
						case "divide":
						case "division":
						case "divided by":
							pixel[numberAdjusterProperty] /= numberAdjusterValue;
							break;
						case "%":
						case "mod":
						case "modulo":
						case "modulus":
						case "modulo by":
							pixel[numberAdjusterProperty] %= numberAdjusterValue;
							break;
						case "=":
						case "set":
						case "equals":
						case "assign":
						case "assignment":
							pixel[numberAdjusterProperty] = numberAdjusterValue;
							break;
						case ">": //lower-bounds the color
						case ">=":
						case "min":
						case "minimum":
							pixel[numberAdjusterProperty] = Math.max(numberAdjusterValue,pixel[numberAdjusterProperty]);
							break;
						case "<":
						case "<=":
						case "max": //upper-bounds the color
						case "maximum":
							pixel[numberAdjusterProperty] = Math.min(numberAdjusterValue,pixel[numberAdjusterProperty]);
							break;
						case "^":
						case "**":
						case "exp":
						case "exponent":
						case "exponentiate":
						case "raise":
						case "raise to":
						case "raised to":
							pixel[numberAdjusterProperty] = pixel[numberAdjusterProperty] ** numberAdjusterValue;
							break;
						default:
							pixel[numberAdjusterProperty] += numberAdjusterValue;
					};
					pixelTempCheck(pixel);
				};
			},
			category: "tools",
			desc: `Changes properties of pixels.<br/>Currently ${numberAdjusterVerb} ${numberAdjusterValue} ${numberAdjusterPreposition} ${numberAdjusterProperty}.<br/><span onclick=numberAdjusterPrompt() style=\"color: #ff00ff;\";>Press [Shift+,] or click here</span> to open the adjuster tool prompt.`,
		};

		function updateNumberAdjusterDescription() {
			elements.number_adjuster.desc = numberAdjusterReverseOrder ? `Changes numeric properties of pixels.<br/>Currently ${numberAdjusterVerb} ${numberAdjusterProperty} ${numberAdjusterPreposition} ${numberAdjusterValue}.<br/><span onclick=numberAdjusterPrompt() style=\"color: #ff00ff;\";>Press [Shift+,] or click here</span> to open the adjuster tool prompt.` : `Changes numeric properties of pixels.<br/>Currently ${numberAdjusterVerb} ${numberAdjusterValue} ${numberAdjusterPreposition} ${numberAdjusterProperty}.<br/><span onclick=numberAdjusterPrompt() style=\"color: #ff00ff;\";>Press [Shift+,] or click here</span> to open the adjuster tool prompt.`;
		};

	//TOOL BEHAVIORS ##

		mooreDonutCoords = [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]]; //Moore neighborhood (includes corners) minus center, as opposed to the von Neumann neighborhood which is the + shape.

		elements.lookup.tick = function(pixel) {
			//console.log(`%%% Tick ${pixelTicks} %%%`);
			//console.log(`Storing coordinates`);
			var pX = pixel.x;
			var pY = pixel.y;
			//console.log(`Position (${pX},${pY})`);
			//console.log(`Iterating`);
			var neighborArray = [];
			for(i = 0; i < mooreDonutCoords.length; i++) {
				//console.log(`i: ${i}`);
				//console.log(`Initialized array`);
				var coord = mooreDonutCoords[i];
				//console.log(`Offset pair: ${coord}`);
				var oX = coord[0];
				var oY = coord[1];
				//console.log(`Stored offset pair`);
				var nX = pX+oX;
				var nY = pY+oY;
				//console.log(`Final coordinates: (${nX},${nY}) (index ${i})`);
				if(isEmpty(nX,nY,true)) {
					//console.log(`Skipping empty pixel (${nX},${nY})`);
					continue;
				} else {
					//console.log(`Found pixel at (${nX},${nY})`);
					var newPixel = pixelMap[nX][nY];
					//console.log(`Pixel stored`);
					var newElement = newPixel.element;
					//console.log(`Element is ${newElement}, running exclusion check;`);
					if(newElement !== pixel.element) { //exclude self
						//console.log(`Element is different, storing in array;`);
						neighborArray.push(newElement); //build array of valid neighbors
					};
					//console.log("Finding iteration done");
				};
				//console.log("End of for block (not loop)");
			};
			//console.log(`Loop done: ${neighborArray}`);
			if(neighborArray.length > 0) {
				var changeToElement = neighborArray[Math.floor(Math.random() * neighborArray.length)];
				changePixel(pixel,changeToElement);
			};
		};

		elements.paint.tick = function(pixel) {
			pixel.color = _rgbHexCatcher(currentColor);
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) { //exclude self
						newPixel.color = pixel.color; //change color of other pixel
					};
				};
			};
		};

		elements.unpaint.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) { //slow for self, see below
						newPixel.color = pixelColorPick(newPixel); //change color of other pixel
					} else {
						if(pixelTicks % 10 === 0) { //every 10 ticks because unpaint shouldn't be paintable, but it shouldn't be a seizure machine either
							newPixel.color = pixelColorPick(newPixel);
						};
					};
				};
			};
		};

		elements.sandreplacer.behavior = [
			"CH:sand|CH:sand|CH:sand",
			"CH:sand|XX|CH:sand",
			"CH:sand|CH:sand|CH:sand"
		];

		elements.delete_all_of_element.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					//console.log(`stored adjacent element ${newElement} at (${nX},${nY})`);
					if(newElement !== pixel.element) { //exclude self
						var coordCircle = circleCoords(pX,pY,7);
						//console.log(`got coordinate circle centered at (${pX},${pY}) with ${coordCircle.length} ${coordCircle.length === 1 ? "pixel" : "pixels"}`);
						for(j = 0; j < coordCircle.length; j++) {
							//console.log(`itting through circle, index ${j}`);
							var coordCircleCoord = coordCircle[j];
							var cNX = coordCircleCoord.x;
							var cNY = coordCircleCoord.y;
							//console.log(`circle coords stored (${cNX},${cNY})`);
							if(isEmpty(cNX,cNY,true)) {
								continue;
								//console.log("skipped empty pixel");
							} else {
								var circleNewPixel = pixelMap[cNX][cNY];
								var circleNewElement = circleNewPixel.element;
								//console.log(`found pixel of element ${circleNewElement}`);
								if(circleNewElement === newElement && circleNewElement !== pixel.element) { //exclude self, match the stored element from before
									deletePixel(cNX,cNY);
								};
							};
						};
					};
				};
			};
		};

		elements.room_temp.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.temp = 20; //to room temp
					pixelTempCheck(pixel);
				};
			}
		};

		elements.uncharge.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					if(newPixel.charge) { //remove charge properties
						delete pixel.charge;
					};
					if(newPixel.chargeCD) {
						delete pixel.chargeCD;
					}
				}
			}
		};

		elements.unburn.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement === "fire") { //fire to smoke
						changePixel(newPixel,"smoke");
					};
					if(newPixel.burning) { //remove burning properties
						delete pixel.burning;
					};
					if(newPixel.burnStart) {
						delete pixel.burnStart;
					}
				}
			}
		};

		elements.smash.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if (elements[newElement].breakInto) {
						var breakInto = elements[pixel.element].breakInto; //edited vanilla code
						// if breakInto is an array, pick one
						if (Array.isArray(breakInto)) {
							breakInto = breakInto[Math.floor(Math.random() * breakInto.length)];
						}
						changePixel(pixel,breakInto);
					}
				}
			}
		};

		elements.cook.behavior = [
			"HT:0.5|HT:0.5|HT:0.5",
			"HT:0.5|HT:0.5|HT:0.5",
			"HT:0.5|HT:0.5|HT:0.5",
		];

		elements.ultraheat.behavior = [
			"HT:350|HT:350|HT:350",
			"HT:350|HT:350|HT:350",
			"HT:350|HT:350|HT:350",
		];

		elements.ultracool.behavior = [
			"CO:350|CO:350|CO:350",
			"CO:350|CO:350|CO:350",
			"CO:350|CO:350|CO:350",
		];

		elements.incinerate.behavior = [
			"HT:10000|HT:10000|HT:10000",
			"HT:10000|HT:10000|HT:10000",
			"HT:10000|HT:10000|HT:10000",
		];

		elements.nan_temp.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.temp = NaN;
				};
			};
		};

		elements.inf_temp.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.temp = Infinity;
				};
			};
		};

		elements.superheat.behavior = [
			"HT:10|HT:10|HT:10",
			"HT:10|HT:10|HT:10",
			"HT:10|HT:10|HT:10",
		];

		elements.supercool.behavior = [
			"CO:10|CO:10|CO:10",
			"CO:10|CO:10|CO:10",
			"CO:10|CO:10|CO:10",
		];

		elements.hyperheat.behavior = [
			"HT:50|HT:50|HT:50",
			"HT:50|HT:50|HT:50",
			"HT:50|HT:50|HT:50",
		];

		elements.hypercool.behavior = [
			"CO:50|CO:50|CO:50",
			"CO:50|CO:50|CO:50",
			"CO:50|CO:50|CO:50",
		];

		elements.absolutezero.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.temp = settings.abszero ?? -273.15;
				};
			};
		};

		elements.antigrav.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.r = 2;
				};
			};
		};

		elements.normalgrav.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.r = 0;
				};
			};
		};

		elements.leftgrav.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.r = 3;
				};
			};
		};

		elements.rightgrav.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.r = 1;
				};
			};
		};

		elements.burn.burnTime = Infinity;
		elements.burn.burn = 100;
		elements.burn.burnInto = "burn";
		elements.burn.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement === "smoke") { //smoke to fire
						changePixel(newPixel,"smoke");
					};
					newPixel.burning = true;
				};
			};
		};

		elements.cursed_shock.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					newPixel.charge = 2;
					if(newPixel.chargeCD) {
						delete newPixel.chargeCD;
					};
				};
			};
		};

		elements.offset_fourth_y.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) { //self-exclude
						tryMove(newPixel,nX,nY+0.25)
					};
				};
			};
		};

		elements.offset_half_y.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) { //self-exclude
						tryMove(newPixel,nX,nY+0.5)
					};
				};
			};
		};

		elements.offset_three_fourth_y.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) { //self-exclude
						tryMove(newPixel,nX,nY+0.75)
					};
				};
			};
		};

		elements.find_toggle.behavior = behaviors.WALL;
		elements.find_toggle.tick = function(pixel) {
			pixel.color = "rgb(255," + marasi(pixelTicks / 10) + ",0)";
		};

		elements.replace.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement === replaceFrom) {
						changePixel(newPixel,replaceTo);
					};
				};
			};
		};

		elements.alt_replace.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement === replaceFrom) {
						newPixel.element = replaceTo;
					};
				};
			};
		};

		elements.alt_alt_replace.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement === replaceFrom) {
						newPixel.element = replaceTo;
						newPixel.color = pixelColorPick(newPixel);
					};
				};
			};
		};

		elements.change.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) {
						changePixel(newPixel,changeTo);
					};
				};
			};
		};

		elements.alt_change.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) {
						newPixel.element = changeTo;
					};
				};
			};
		};

		elements.alt_alt_change.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(newElement !== pixel.element) {
						newPixel.element = changeTo;
						newPixel.color = pixelColorPick(newPixel);
					};
				};
			};
		};

		elements.prop.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					var newElement = newPixel.element;
					if(propProperty === "element") {
						if(newElement !== pixel.element) { //self exclude if element is the change
							newPixel[propProperty] = propValue;
							newPixel.temp = (elements[propValue].temp || newPixel.temp);
						};
					} else {
						newPixel[propProperty] = propValue;
					};
				};
			};
		};

		elements.number_adjuster.tick = function(pixel) {
			var pX = pixel.x;
			var pY = pixel.y;
			for(i = 0; i < mooreDonutCoords.length; i++) {
				var coord = mooreDonutCoords[i];
				var oX = coord[0];
				var oY = coord[1];
				var nX = pX+oX;
				var nY = pY+oY;
				if(isEmpty(nX,nY,true)) {
					continue;
				} else {
					var newPixel = pixelMap[nX][nY];
					if(numberAdjusterProperty !== "element") {			
						//console.log(numberAdjusterValue);
						if(numberAdjusterMode === "set") {
							newPixel[numberAdjusterProperty] = numberAdjusterValue;
						} else if(numberAdjusterMode === "add") {
							if(typeof(newPixel[numberAdjusterProperty]) === "undefined") {
								newPixel[numberAdjusterProperty] = 0;
							};
							newPixel[numberAdjusterProperty] += numberAdjusterValue;
						};
						pixelTempCheck(newPixel);
					};
				};
			};
		};

	//SWITCHES ##

		elements.switch_off = {
			name: "switch (off)",
			color: "#7F3333",
			behavior: behaviors.WALL,
			noConduct: ["switch_on_control","switch_off_control"],
			category: "machines",
		};

		elements.switch_on = {
			name: "switch (on)",
			color: "#33CC33",
			behavior: behaviors.WALL,
			conduct: 1,
			noConduct: ["switch_on_control","switch_off_control"],
			category: "machines",
		};

		elements.switch_off_control = {
			color: "#FF3333",
			behavior: behaviors.WALL,
			behaviorOn: [
				"XX|CH:switch_on>switch_off|XX",
				"CH:switch_on>switch_off|XX|CH:switch_on>switch_off",
				"XX|CH:switch_on>switch_off|XX"
			],
			conduct: 1,
			noConduct: ["switch_on","switch_off"],
			category: "machines",
		};

		elements.switch_on_control = {
			color: "#33FF33",
			behavior: behaviors.WALL,
			behaviorOn: [
				"XX|CH:switch_off>switch_on|XX",
				"CH:switch_off>switch_on|XX|CH:switch_off>switch_on",
				"XX|CH:switch_off>switch_on|XX"
			],
			conduct: 1,
			noConduct: ["switch_on","switch_off"],
			category: "machines",
		};

	//MORE HEATERS AND COOLERS (mostly) ##

		elements.super_heater = {
			color: "#ff0000",
			tick: function(pixel) {
				for (let i = -4; i < 5; i++) {
					for (let j = -4; j < 5; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp += 15
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
		},

		elements.super_cooler = {
			color: "#0000ff",
			tick: function(pixel) {
				for (let i = -4; i < 5; i++) {
					for (let j = -4; j < 5; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp < -258 ? pixelMap[pixel.x+j][pixel.y+i].temp = -273 : pixelMap[pixel.x+j][pixel.y+i].temp -= 15
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
		},

		elements.super_warmer = {
			color: "#00ff00",
			tick: function(pixel) {
				for (let i = -4; i < 5; i++) {
					for (let j = -4; j < 5; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp = 20
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
		},

		elements.super_heater_2 = {
			color: "#ff2200",
			tick: function(pixel) {
				for (let i = -9; i < 10; i++) {
					for (let j = -9; j < 10; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp += 25
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
		},

		elements.super_cooler_2 = {
			color: "#0022ff",
			tick: function(pixel) {
				for (let i = -9; i < 10; i++) {
					for (let j = -9; j < 10; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp < -248 ? pixelMap[pixel.x+j][pixel.y+i].temp = -273 : pixelMap[pixel.x+j][pixel.y+i].temp -= 25
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
		},

		elements.super_warmer_2 = {
			color: "#22ff22",
			tick: function(pixel) {
				for (let i = -9; i < 10; i++) {
					for (let j = -9; j < 10; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp = 20
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
		},

		elements.global_heater = {
			color: "#ff6666",
			tick: function(pixel) {
				currentPixels.forEach(function(newPixel) {newPixel.temp++; pixelTempCheck(newPixel)})
			},
			category:"machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
		},

		elements.global_cooler = {
			color: "#6666ff",
			tick: function(pixel) {
				currentPixels.forEach(function(newPixel) {newPixel.temp = Math.max(-273.15,newPixel.temp - 1); pixelTempCheck(newPixel)})
			},
			category:"machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
		},

		elements.global_warmer = {
			color: "#66ff66",
			tick: function(pixel) {
				currentPixels.forEach(function(newPixel) {newPixel.temp = 20; pixelTempCheck(newPixel)})
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
		},

		elements.adjustable_global_heater = {
			color: "#66ff66",
			tick: function(pixel) {
				var thisPixel = pixel;
				currentPixels.forEach(function(newPixel) {if(newPixel.element !== thisPixel.element) {newPixel.temp += thisPixel.temp; pixelTempCheck(newPixel)}})
			},
			category: "machines",
			insulate: true,
			state: "solid",
			hidden: true,
			excludeRandom: true,
			temp: 1
		},

		elements.super_heater_3 = {
			color: "#ff7f00",
			uwu: 0,
			tick: function(pixel) {
				tempInc = 50
				pixel.uwu = 0
				range = 10
				for (let i = -8; i < 9; i++) {
					for (let j = -8; j < 9; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.uwu = 0
				} else {
					tempInc += (pixel.uwu*15)
					range += Math.floor((Math.sqrt(pixel.uwu+1))**1.2)
				}
				for (let i = (-1*range); i < (range + 1); i++) {
					for (let j = (-1*range); j < (range + 1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp += tempInc
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		},

		elements.super_cooler_3 = {
			color: "#007fff",
			uwu: 0,
			tick: function(pixel) {
				tempDec = 50
				pixel.uwu = 0
				range = 10
				for (let i = -8; i < 9; i++) {
					for (let j = -8; j < 9; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.uwu = 0
				} else {
					tempDec += (pixel.uwu*15)
					range += Math.floor((Math.sqrt(pixel.uwu+1))**1.2)
				}
				for (let i = (-1*range); i < (range + 1); i++) {
					for (let j = (-1*range); j < (range + 1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							(pixelMap[pixel.x+j][pixel.y+i].temp < (-273 + tempDec)) ? pixelMap[pixel.x+j][pixel.y+i].temp = -273 : pixelMap[pixel.x+j][pixel.y+i].temp -= tempDec
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		},

		elements.super_warmer_3 = {
			color: "#7fff7f",
			uwu: 0,
			tick: function(pixel) {
				pixel.uwu = 0
				range = 10
				for (let i = -8; i < 9; i++) {
					for (let j = -8; j < 9; j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							if (pixelMap[pixel.x+j][pixel.y+i].element == pixel.element) {
								pixel.uwu++
							}
						}
					}
				}
				pixel.uwu -= 1
				if(pixel.uwu == undefined || pixel.uwu == null || isNaN(pixel.uwu)) {
					pixel.uwu = 0
				} else {
					range += Math.floor((Math.sqrt(pixel.uwu+1))**1.2)
				}
				for (let i = (-1*range); i < (range + 1); i++) {
					for (let j = (-1*range); j < (range + 1); j++) {
						if (!isEmpty(pixel.x+j,pixel.y+i) && !outOfBounds(pixel.x+j,pixel.y+i)) {
							pixelMap[pixel.x+j][pixel.y+i].temp = 20
						}
					}
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		},

		elements.tc = { //temperature checker
			name: "Temperature Checker",
			color: ["#000000","#000000"],
			tick: function(pixel) {
				if(pixel.temp < -255) {
					pixel.color = "rgb(0,0,255)"
				} else if(pixel.temp >= -255 && pixel.temp < 0) {
					pixel.color = "rgb(0,0," + Math.abs(pixel.temp) + ")"
				} else if(pixel.temp <= 255) {
					pixel.color = "rgb(" + pixel.temp % 256 + ",0,0)"
				} else if(pixel.temp <= 65535) {
					pixel.color = "rgb(255," + Math.floor(pixel.temp / 256) + ",0)"
				} else if(pixel.temp <= 16777215) {
					pixel.color = "rgb(255,255," + Math.floor(pixel.temp / 65536) + ")"
				} else {
					pixel.color = "rgb(255,255,255)"
				}
			},
			category:"machines",
			insulate: true,
			state: "solid",
		},

		/**
		*  color-temperature.js
		*
		*  Neil Bartlett
		*  neilbartlett.com
		*  2015-01-22
		*
		*  Copyright [2015] [Neil Bartlett] *
		*
		* Color Temperature is the color due to black body radiation at a given
		* temperature. The temperature is given in Kelvin. The concept is widely used
		* in photography and in tools such as f.lux.
		*
		* The function here converts a given color temperature into a near equivalent
		* in the RGB colorspace. The function is based on a curve fit on standard sparse
		* set of Kelvin to RGB mappings.
		*
		* Two conversions are presented here. The one colorTempertature2RGBUSingTH
		* is a JS version of the algorithm developed by Tanner Helland. The second is a
		* slightly more accurate conversion based on a refitting of the original data
		* using different curve fit functions. The performance cost of the two
		* approaches is very similar and in general the second algorithm is preferred.
		*
		* NOTE The approximations used are suitable for photo-mainpulation and other
		* non-critical uses. They are not suitable for medical or other high accuracy
		* use cases.
		*
		* Accuracy is best between 1000K and 40000K.
		*
		* See http://github.com/neilbartlett/color-temperature for further details.
		*
		**/

		//[Code licensed under the MIT License]

		//[Tanner Helland version omitted]

		/**
		 * A more accurate version algorithm based on a different curve fit to the
		 * original RGB to Kelvin data.
		  * Input: color temperature in degrees Kelvin
		  * Output: json object of red, green and blue components of the Kelvin temperature
		 */
		 colorTemperature2rgb = function(kelvin) {

		  var temperature = kelvin / 100.0;
		  var red, green, blue;

		  if (temperature < 66.0) {
			red = 255;
		  } else {
			// a + b x + c Log[x] /.
			// {a -> 351.97690566805693`,
			// b -> 0.114206453784165`,
			// c -> -40.25366309332127
			//x -> (kelvin/100) - 55}
			red = temperature - 55.0;
			red = 351.97690566805693+ 0.114206453784165 * red - 40.25366309332127 * Math.log(red);
			if (red < 0) red = 0;
			if (red > 255) red = 255;
		  }

		  /* Calculate green */

		  if (temperature < 66.0) {

			// a + b x + c Log[x] /.
			// {a -> -155.25485562709179`,
			// b -> -0.44596950469579133`,
			// c -> 104.49216199393888`,
			// x -> (kelvin/100) - 2}
			green = temperature - 2;
			green = -155.25485562709179 - 0.44596950469579133 * green + 104.49216199393888 * Math.log(green);
			if (green < 0) green = 0;
			if (isNaN(green)) green = 0;
			if (green > 255) green = 255;

		  } else {

			// a + b x + c Log[x] /.
			// {a -> 325.4494125711974`,
			// b -> 0.07943456536662342`,
			// c -> -28.0852963507957`,
			// x -> (kelvin/100) - 50}
			green = temperature - 50.0;
			green = 325.4494125711974 + 0.07943456536662342 * green - 28.0852963507957 * Math.log(green);
			if (green < 0) green = 0;
			if (green > 255) green = 255;

		  }

		  /* Calculate blue */

		  if (temperature >= 66.0) {
			blue = 255;
		  } else {

			if (temperature <= 20.0) {
			  blue = 0;
			} else {

			  // a + b x + c Log[x] /.
			  // {a -> -254.76935184120902`,
			  // b -> 0.8274096064007395`,
			  // c -> 115.67994401066147`,
			  // x -> kelvin/100 - 10}
			  blue = temperature - 10;
			  blue = -254.76935184120902 + 0.8274096064007395 * blue + 115.67994401066147 * Math.log(blue);
			  if (blue < 0) blue = 0;
			  if (blue > 255) blue = 255;
			}
		  }

		  //return {red: Math.round(red), blue: Math.round(blue), green: Math.round(green)};
		  return "rgb("+Math.round(red)+","+Math.round(green)+","+Math.round(blue)+")"
		}

		//[reverse conversion omitted]

		elements.color_temp_test = {
			color: "#111111",
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(!pixel.oldColor) {
					pixel.oldColor = pixel.color
				}
				if(!pixel.lerpValue) {
					pixel.lerpValue = 0
				}
				if(!pixel.lerpAR) {
					pixel.lerpAR = 0
				}
				if(!pixel.lerpAG) {
					pixel.lerpAG = 0
				}
				if(!pixel.lerpAB) {
					pixel.lerpAB = 0
				}
				if(!pixel.lerpBR) {
					pixel.lerpBR = 0
				}
				if(!pixel.lerpBG) {
					pixel.lerpBG = 0
				}
				if(!pixel.lerpBB) {
					pixel.lerpBB = 0
				}
				if(!pixel.lerpedR) {
					pixel.lerpedR = 0
				}
				if(!pixel.lerpedG) {
					pixel.lerpedG = 0
				}
				if(!pixel.lerpedB) {
					pixel.lerpedB = 0
				}
				if(!pixel.lerpedColor) {
					pixel.lerpedColor = ""
				}
				if(pixel.temp < 525) {
					pixel.color = pixel.oldColor
				}
				if(pixel.temp >= 525 && pixel.temp < 1582) {
					pixel.lerpValue = (pixel.temp-524)/(1581-524)
					pixel.lerpAR = pixel.oldColor.split(",")[0].slice(4)
					pixel.lerpAG = pixel.oldColor.split(",")[1]
					pixel.lerpAB = pixel.oldColor.split(",")[2].slice(0,-1)
					pixel.lerpBR = colorTemperature2rgb(pixel.temp + 273.15).split(",")[0].slice(4)
					pixel.lerpBG = colorTemperature2rgb(pixel.temp + 273.15).split(",")[1]
					pixel.lerpBB = colorTemperature2rgb(pixel.temp + 273.15).split(",")[2].slice(0,-1)
					pixel.lerpedR = pixel.lerpBR*pixel.lerpValue + pixel.lerpAR*(1-pixel.lerpValue)
					pixel.lerpedG = pixel.lerpBG*pixel.lerpValue + pixel.lerpAG*(1-pixel.lerpValue)
					pixel.lerpedB = pixel.lerpBB*pixel.lerpValue + pixel.lerpAB*(1-pixel.lerpValue)
					pixel.lerpedColor = "rgb(" + pixel.lerpedR + "," + pixel.lerpedG + "," + pixel.lerpedB + ")"
					pixel.color = pixel.lerpedColor
				}
				if(pixel.temp >= 1582) {
					pixel.color = colorTemperature2rgb(pixel.temp + 273.15)
				}
				doHeat(pixel);
			},
			category: "special",
			state: "solid",
			density: 1500,
			temp: 20,
		}

	//TEETH AND PASTE ##

		elements.tooth = {
			color: "#d9d9d9",
			behavior: behaviors.SUPPORT,
			reactions: {
				"sugar": { "elem1": "decayed_tooth", "elem2": null, "chance": 0.003 },
				"plaque": { "elem1": "decayed_tooth", "elem2": null, "chance": 0.002 },
				"acid": { "elem1": "decayed_tooth", "elem2": null }
			},
			category:"life",
			tempHigh: 1000, //https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5887641/
			stateHigh: ["steam","salt","meat","hydroxyapatite"],
			state: "solid",
			density: 2000, //(bs) inspired by https://ncbi.nlm.nih.gov/pmc/articles/PMC5176275/
			hardness: 0.5,
			breakInto: ["meat","hydroxyapatite"],
		},

		elements.plaque = {
			color: "#faf6dc",
			behavior: [
				"XX|ST AND CR:plague%0.01 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01|XX",
				"ST AND CR:plague%0.01 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01|CH:tartar%0.001|ST AND CR:plague%0.01 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01",
				"M2|M1 AND ST AND CR:plague%0.01 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01|M2",
			],
			reactions: {
				"acid": { "elem1": null, "elem2": null, "chance": 0.01 }
			},
			category:"life",
			tempHigh: 100,
			stateHigh: ["steam","plague"],
			state: "solid",
			density: 5.4, //https://physics.aps.org/articles/v5/s140#:~:text=They%20then%20use%20tabulated%20values,%2D12%20gram)%20per%20cell. 
						  //https://en.wikipedia.org/wiki/Calculus_(dental)#:~:text=Cell%20density%20within%20dental%20plaque,estimated%20200%2C000%2C000%20cells%20per%20milligram.
			hidden: true,
		},

		elements.tartar = {
			color: ["#e8d595", "#cfb27e", "#f0e989"],
			behavior: [
				"XX|ST AND CR:plague%0.02 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01|XX",
				"ST AND CR:plague%0.02 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01|CH:tartar%0.01|ST AND CR:plague%0.02 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01",
				"XX|M1 AND ST AND CR:plague%0.02 AND CR:acid%0.01 AND CR:infection%0.003 AND CH:tooth>decayed_tooth%0.01|XX",
			],
			reactions: {
				"acid": { "elem1": null, "elem2": null, "chance": 0.01 }
			},
			category:"other",
			tempHigh: elements.calcium.tempHigh,
			stateHigh: ["steam","plague","calcium"],
			state: "solid",
			density: 1900,
			hardness: elements.tooth.hardness - 0.05,
			breakInto: ["calcium","calcium","calcium","calcium","rotten_meat","rotten_meat","plague"],
			hidden: true,
		},

		elements.decayed_tooth = {
			color: ["#aba89d","#85837b","#7a7972","#b8b5a5","#6b6a63"],
			behavior: [
				"XX|XX|XX",
				"SP%99.5|DL%0.04|SP%99.5",
				"XX|M1|XX",
			],
			reactions: {
				"acid": { "elem1": null, "elem2": null, "chance": 0.7 }
			},
			tempHigh: 1000,
			stateHigh: ["steam","salt","meat","hydroxyapatite"],
			state: "solid",
			category: "other",
			density: 1900,
			hardness: 0.3,
			breakInto: ["rotten_meat","hydroxyapatite"],
			hidden: true,
		},

		elements.hydroxyapatite = {
			color: ["#edecda", "#f5f5f5", "#e8e8e8"],
			behavior: behaviors.POWDER,
			state: "solid",
			category: "solids",
			density: 3180,
			tempHigh: 1670,
			/* it decomposes but not into anything worth adding
			https://www.sciencedirect.com/science/article/abs/pii/S0142961299000769 */
			category: "powders",
		},

		elements.toothpaste = {
			color: ["#f8f8f8", "#6699ff", "#f8f8f8", "#ff5555"],
			behavior: [
				"XX|SW:plaque%5|XX",
				"SW:plaque%5|XX|SW:plaque%5",
				"M2|SW:plaque%5|M2",
			],
			reactions: {
				"plaque": {"elem1":["foam","toothpaste","toothpaste"], "elem2":"foam", "chance":0.7},
				"decayed_tooth": {"elem1":"tooth", "elem2":"foam", "chance":0.5}
			},
			state: "solid",
			category: "other",
			density: 1330,
			tempHigh: 250, //bs
			stateHigh: ["toothpaste","toothpaste","toothpaste","toothpaste","toothpaste","toothpaste","toothpaste","toothpaste","toothpaste","toothpaste","foam","foam","fire","smoke","ash"],
			burn: 5,
			burnInto: ["fire","smoke","smoke","ash","ash","toothpaste"],
			viscosity: 20000,
			/* it decomposes but not into anything worth adding
			https://www.sciencedirect.com/science/article/abs/pii/S0142961299000769 */
			category: "powders",
		}

	runAfterLoad(function() {
		foodArray = Object.keys(elements).filter(function(e) {
			return elements[e].category == "food";
		});
		if(!elements.tooth.reactions) {
			elements.tooth.reactions = {}
		};
		for(i = 0; i < foodArray.length; i++) {
			elements.tooth.reactions[foodArray[i]] = { "elem1": ["tooth","tooth","tooth","tooth","tooth","tooth","tooth","tooth","decayed_tooth"], "elem2": "plaque", "chance": 0.001 }
		};
		elements.acid.ignore.push("tooth");
		elements.acid.ignore.push("decayed_tooth");
		elements.acid.ignore.push("plaque");
		elements.acid.ignore.push("tartar");
		eLists.IMPURITY.push("plaque");
		eLists.IMPURITY.push("tartar");
		eLists.IMPURITY.push("decayed_tooth");
		//regenerate behaviors of elements that use eLists.IMPURITY {
			elements.pure_water.behavior = [
				"DL:"+eLists.IMPURITY+"|DL:"+eLists.IMPURITY+"|DL:"+eLists.IMPURITY+"",
				"DL:"+eLists.IMPURITY+" AND M2|XX|DL:"+eLists.IMPURITY+" AND M2",
				"DL:"+eLists.IMPURITY+" AND M1|DL:"+eLists.IMPURITY+" AND M1|DL:"+eLists.IMPURITY+" AND M1",
			];
			elements.pure_steam.behavior = [
				"M2 AND DL:"+eLists.IMPURITY+"|M1 AND DL:"+eLists.IMPURITY+"|M2 AND DL:"+eLists.IMPURITY+"",
				"M1 AND DL:"+eLists.IMPURITY+"|XX|M1 AND DL:"+eLists.IMPURITY+"",
				"M2 AND DL:"+eLists.IMPURITY+"|M1 AND DL:"+eLists.IMPURITY+"|M2 AND DL:"+eLists.IMPURITY+"",
			];
		//}
		//concoction support (it's all mistakes) {
			elements.concoction.reactions.tooth = { "elem1": "mistake", "elem2": null };
			elements.concoction.reactions.decayed_tooth = { "elem1": "mistake", "elem2": null };
			elements.concoction.reactions.toothpaste = { "elem1": "mistake", "elem2": null };
			elements.concoction.reactions.plaque = { "elem1": "mistake", "elem2": null };
			elements.concoction.reactions.tartar = { "elem1": "mistake", "elem2": null };
		//}
	});

	//EXPERIMENTAL STRUCTURE SPAWNERS ##

		arrayLoaderVoids = ["air", "null", null];
		buildingOneSegmentDoor = ["concrete","wood_plank","concrete","wood_plank","concrete"];
		buildingOneSegmentWindows = ["concrete","glass_pane","concrete","glass_pane","concrete"];
		buildingOneSegmentConcrete = ["concrete","concrete","concrete","concrete","concrete"];

		buildingTwoSegments = [
			["concrete","concrete","concrete","concrete","concrete"],
			["concrete","concrete","concrete","concrete","concrete"],
			["brick","wood_plank","brick"],
			["glass_pane","wood_plank","glass_pane"],
			["brick","brick","brick"],
			["wood","wood_plank","wood_plank","wood_plank","wood"],
			["wood_plank","wood_plank","wood_plank"],
			["wood_plank"]
		];

		oldRoom= [["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "glass",  "glass",  "glass",  "glass",  "glass",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"],
				  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "glass",  "glass",  "glass",  "glass",  "glass",  "brick",  "battery","brick",  "brick",  "brick",  "brick",  "brick"],
				  ["glass",  "glass",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "light","light_bulb","air",	"air",	"air",	"glass",  "glass"],
				  ["glass",  "glass",  "light",  "light",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"light",  "air",	"air",	"air",	"glass",  "glass"],
				  ["glass",  "glass",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "brass"],
				  ["glass",  "glass",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["brick",  "brick",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["brick",  "brick",  "iron",   "straw",  "straw",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["brick",  "brick",  "iron",   "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "iron",   "air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["brick",  "brick",  "iron",   "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "iron",   "air",	"air",	"light",  "air",	"air",	"air",	"wood",   "brass"],
				  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"],
				  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"]]

		altRoom= [["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"],
				  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "battery","brick",  "brick",  "brick",  "brick",  "brick"],
				  ["glass",  "glass",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "light","light_bulb","air",	"air",	"air",	"glass",  "glass"],
				  ["glass",  "glass",  "light",  "light",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"light",  "air",	"air",	"air",	"glass",  "glass"],
				  ["brass",  "wood",   "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "brass"],
				  ["wood",   "wood",   "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["wood",   "wood",   "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["wood",   "wood",   "air",	"air",	"iron",   "straw",  "straw",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["wood",   "wood",   "air",	"air",	"iron",   "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "iron",   "air",	"air",	"air",	"air",	"wood",   "wood" ],
				  ["brass",  "wood",   "air",	"air",	"iron",   "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "iron",   "light",  "air",	"air",	"air",	"wood",   "brass"],
				  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"],
				  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick", "battery", "brick",  "brick",  "brick",  "brick",  "brick"]]

		/*function r0to255() {
			return Math.floor(Math.random() * 256);
		};*/

		canSupportWithEdge = function(x,y) {
			if(outOfBounds(x,y)) { //count edges
				return true;
			} else {
				if(!isEmpty(x,y,true)) { //if there is a pixel
					if(elements[pixelMap[x][y].element].state === "solid") {
						return true;
					} else {
						return false;
					};
				};
			};
		};

		function loadPixelRowFromArray(pixelArray,centerX,centerY,evenLengthBiasedLeft=true,doOverwrite=true) {
			var arrayLength = pixelArray.length;
			var leftmostOffset = (evenLengthBiasedLeft ? Math.floor(0 - ((arrayLength - 1) / 2)) : Math.ceil(0 - ((arrayLength - 1) / 2))) //floor and ceil have no effect on the integer values produced by odd lengths
			var forEnd = 0 - leftmostOffset;
			//var randomColor = `rgb(${r0to255()},${r0to255()},${r0to255()})`;
			for(i = 0; i < arrayLength; i++) {
				var newElement = pixelArray[i];
				var x = (centerX + leftmostOffset) + i;
				var y = centerY;
				if(outOfBounds(x,y)) {
					continue;
				};
				if(newElement === "null" || newElement === null) { //do nothing if element is null
					continue;
				};
				//console.log([x,y]);
				if(!isEmpty(x,y,true)) {
					if(doOverwrite) {
						deletePixel(x,y);
						if(newElement !== "air") { //if the new element is "air", don't create a pixel after deleting
							createPixel(newElement,x,y);
						};
						continue;
						//pixelMap[x][y].color = randomColor;
					} else {;
						if(newElement === "air") { //delete on "air" even if doOverwrite is false
							deletePixel(x,y);
						} else {
							continue;
						};
					};
				};
				if(!arrayLoaderVoids.includes(newElement)) { //don't create anything if the element is a special void
					createPixel(newElement,x,y);
				}
				//pixelMap[x][y].color = randomColor;
			};
		};

		function loadPixelRowFromArrayWithColorRowArray(pixelArray,colorArray,centerX,centerY,evenLengthBiasedLeft=true,doOverwrite=true,doColorOffset=false) {
			var arrayLength = pixelArray.length;
			var leftmostOffset = (evenLengthBiasedLeft ? Math.floor(0 - ((arrayLength - 1) / 2)) : Math.ceil(0 - ((arrayLength - 1) / 2))) //floor and ceil have no effect on the integer values produced by odd lengths
			var forEnd = 0 - leftmostOffset;
			//var randomColor = `rgb(${r0to255()},${r0to255()},${r0to255()})`;
			for(i = 0; i < arrayLength; i++) {
				var newElement = pixelArray[i];
				var newColor = colorArray[i];
				//console.log(newColor);
				if(doColorOffset && !["null",null].includes(newColor)) {
					newColor = convertHslObjects(normalizeColorToHslObject(newColor),"rgbjson");
					var colorOffset = Math.floor(Math.random() * (Math.random() > 0.5 ? -1 : 1) * Math.random() * 15);
					for(colorlet in newColor) {
						newColor[colorlet] += colorOffset;
					};
					newColor = convertColorFormats(newColor,"rgb");
				};
				var x = (centerX + leftmostOffset) + i;
				var y = centerY;
				if(outOfBounds(x,y)) {
					continue;
				};
				if(newElement === "null" || newElement === null) { //do nothing if element is null
					continue;
				};
				//console.log([x,y]);
				if(!isEmpty(x,y,true)) {
					if(doOverwrite) {
						deletePixel(x,y);
						if(newElement !== "air") { //if the new element is "air", don't create a pixel after deleting
							createPixel(newElement,x,y);
							pixelMap[x][y].color = newColor;
						};
						continue;
						//pixelMap[x][y].color = randomColor;
					} else {;
						if(newElement === "air") { //delete on "air" even if doOverwrite is false
							deletePixel(x,y);
						} else {
							continue;
						};
					};
				};
				if(!arrayLoaderVoids.includes(newElement)) { //don't create anything if the element is a special void
					createPixel(newElement,x,y);
					pixelMap[x][y].color = newColor;
				}
				//pixelMap[x][y].color = randomColor;
			};
		};

		delete elements.rad_glass.stateHigh;

		elements.glass.hardness = 0.25,
		elements.rad_glass.hardness = 0.25,

		//Prereq elements
		elements.crumbling_concrete = {
			color: "#ababab",
			tick: function(pixel) {
				var px = pixel.x;
				var py = pixel.y;

				if (pixel.start === pixelTicks) {return}

				var supportCondition1 = (canSupportWithEdge(px-1,py-1) && canSupportWithEdge(px+1,py-1))	// V shape
				var supportCondition2 = (canSupportWithEdge(px-1,py) && canSupportWithEdge(px+1,py)) 		// - shape
				var supportCondition3 = (canSupportWithEdge(px-1,py+1) && canSupportWithEdge(px+1,py+1))	// Λ shape
				var supportCondition4 = (canSupportWithEdge(px-1,py+1) && canSupportWithEdge(px+1,py-1))	// / shape
				var supportCondition5 = (canSupportWithEdge(px-1,py-1) && canSupportWithEdge(px+1,py+1))	// \ shape
				var supportCondition6 = (canSupportWithEdge(px-1,py-1) && canSupportWithEdge(px+1,py))		// '- shape
				var supportCondition7 = (canSupportWithEdge(px-1,py+1) && canSupportWithEdge(px+1,py))		// ,- shape
				var supportCondition8 = (canSupportWithEdge(px+1,py-1) && canSupportWithEdge(px-1,py))		// -' shape
				var supportCondition9 = (canSupportWithEdge(px+1,py+1) && canSupportWithEdge(px-1,py))		// -, shape
				var supportCondition10 = (canSupportWithEdge(px,py+1) && canSupportWithEdge(px,py-1))		// | shape
				var supports = (supportCondition1 || supportCondition2 || supportCondition3 || supportCondition4 || supportCondition5 || supportCondition6 || supportCondition7 || supportCondition8 || supportCondition9 || supportCondition10);

				if(!supports) {
					behaviors.POWDER(pixel);
				};

				elements.concrete.tick

				doDefaults(pixel);
			},
			tempHigh: 1500,
			stateHigh: "magma",
			category: "powders",
			state: "solid",
			density: 2400,
			hardness: 0.5,
			breakInto: "dust",
		};

		elements.attach_powder_silk = {
			color: ["#ebebeb", "#e6d9d1"],
			properties: {
				"attached": false,
				"attachOffsets": [null, null]
			},
			tick: function(pixel) {
				if (pixel.start === pixelTicks) {return}
				if(pixel.attached) {
					if(pixel.attachOffsets === null) {
						pixel.attached = false;
					} else if(pixel.attachOffsets.includes(null)) {
						pixel.attached = false;
					} else {
						var attachCoords = [pixel.x + pixel.attachOffsets[0], pixel.y + pixel.attachOffsets[1]];
						if(isEmpty(attachCoords[0],attachCoords[1],false)) { //consider OOB full
							pixel.attached = false;
						};
					};
				} else {
					behaviors.POWDER(pixel);
				};
				doDefaults(pixel);
			},
			burnInto: "ash",
			burn:75,
			burnTime:25,
			category: "solids",
			state: "solid",
			density: 1000,
			hidden: true
		};

		elements.glass_pane = {
			color: ["#5e807d","#679e99"],
			behavior: behaviors.SUPPORT,
			reactions: {
				"radiation": { "elem1":"rad_glass_pane", "chance":0.33 }
			},
			tempHigh: 1500,
			stateHigh: "molten_glass",
			hardness: 0.2,
			category: "solids",
			state: "solid",
			density: 2500,
			breakInto: "glass_shard",
			hidden: true,
		};

		elements.rad_glass_pane = {
			color: ["#648c64","#6aad83"],
			behavior: [
				"XX|CR:radiation%0.075|XX",
				"SP AND CR:radiation%0.075|XX|SP AND CR:radiation%0.075",
				"XX|M1 AND CR:radiation%0.075|XX",
			],
			tempHigh: 1500,
			hardness: 0.2,
			stateHigh: "molten_rad_glass",
			category: "solids",
			state: "solid",
			density: 2500,
			breakInto: "rad_glass_shard",
			hidden: true,
		};

		elements.wood.hardness = 0.2;

		elements.wood_plank = {
			color: "#ab6c3f",
			behavior: behaviors.SUPPORT,
			tempHigh: 400,
			stateHigh: ["ember","charcoal","fire","fire","fire"],
			category: "solids",
			burn: 5,
			burnTime: 300,
			burnInto: ["ember","charcoal","fire"],
			state: "solid",
			hardness: 0.2,
			breakInto: "sawdust",
			cutInto: ["wood_plank","wood_plank","wood_plank","wood_plank","wood_plank","wood_plank","sawdust"]
		};

		elements.hanging_concrete = {
			color: "#ababab",
			behavior: [
				"XX|SP|XX",
				"XX|XX|XX",
				"M2|M1|M2" //crumbling from the top down is acceptable
			],
			tempHigh: 1500,
			stateHigh: "magma",
			category: "powders",
			state: "solid",
			density: 2400,
			hardness: 0.5,
			breakInto: "dust",
			hidden: true,
		};

		elements.support_copper = {
			color: ["#A95232","#BE4322","#C76035"],
			behavior: behaviors.SUPPORT,
			reactions: {
				"water": { "elem1":"oxidized_copper", chance:0.0025 },
				"salt_water": { "elem1":"oxidized_copper", chance:0.005 },
				"dirty_water": { "elem1":"oxidized_copper", chance:0.04 },
				"sugar_water": { "elem1":"oxidized_copper", chance:0.0035 },
				"seltzer": { "elem1":"oxidized_copper", chance:0.006 }
			},
			category: "solids",
			tempHigh: 1085,
			stateHigh: "molten_copper",
			density: 8960,
			conduct: 0.95,
			hardness: 0.3,
			hidden: true,
		};

		elements.paper.behavior = behaviors.SUPPORT;

		elements.support_glass = JSON.parse(JSON.stringify(elements.glass));
		elements.support_glass.stateHigh = "molten_glass";
		elements.support_glass.behavior = behaviors.SUPPORT;

		elements.support_bulb = {
			color: "#a8a897",
			behavior: behaviors.SUPPORTPOWDER,
			behaviorOn: [
				"XX|CR:light|XX",
				"CR:light AND SP|XX|CR:light AND SP",
				"M2|CR:light AND M1|M2"
			],
			colorOn: "#ebebc3",
			category: "machines",
			tempHigh: 1500,
			stateHigh: ["molten_glass","molten_glass","molten_copper"],
			conduct: 1,
			breakInto: "glass_shard",
			hidden: true,
		};

		elements.hanging_bulb = {
			color: "#a8a897",
			behavior: [
				"XX|SP|XX",
				"XX|XX|XX",
				"M2|M1|M2"
			],
			behaviorOn: [
				"XX|SP|XX",
				"CR:light|XX|CR:light",
				"M2|CR:light AND M1|M2"
			],
			colorOn: "#ebebc3",
			category: "machines",
			tempHigh: 1500,
			stateHigh: ["molten_glass","molten_glass","molten_copper"],
			conduct: 1,
			breakInto: "glass_shard",
			hidden: true,
		};

		elements.support_plastic = {
			color: "#c5dede",
			behavior: behaviors.SUPPORT,
			tempHigh: 250,
			stateHigh: "molten_plastic",
			burn: 10,
			burnTime: 200,
			burnInto: ["dioxin","smoke","dioxin","smoke","stench"],
			category: "solids",
			state: "solid",
			density: 1052,
			hidden: true,
		};

		newPowder("calcium_sulfate","#d1cec7",2960,1460).reactions = {
			water: { elem1: ["gypsum","calcium_sulfate"], elem2: null }
		};

		newPowder("gypsum",["#e6e5e3","#d9dbdb"],2320,1460).tick = function(pixel) {
			//thermal split
			if(pixel.temp > 100) {
				var emptySlots = getEmptyMooreNeighbors(pixel);
				if(emptySlots.length > 1) {
					shuffleArray(emptySlots);
					emptySlots = emptySlots.slice(0,2);
					for(var i = 0; i < emptySlots.length; i++) {
						var coords = emptySlots[i];
						createPixelReturn("steam",...coords).temp = pixel.temp
					};
					changePixel(pixel,"calcium_sulfate",false);
					return
				}
			}
		};
		
		elements.paper.reactions ??= {};
		elements.paper.reactions.gypsum = { elem1: ["paper","paper","paper","paper","paper","paper",null], elem2: "drywall" };
		
		elements.molten_gypsum = {
			tick: function(pixel) {
				//thermal split
				var emptySlots = getEmptyMooreNeighbors(pixel);
				if(emptySlots.length > 1) {
					shuffleArray(emptySlots);
					emptySlots = emptySlots.slice(0,2);
					for(var i = 0; i < emptySlots.length; i++) {
						var coords = emptySlots[i];
						createPixelReturn("steam",...coords).temp = pixel.temp
					};
					changePixel(pixel,"molten_calcium_sulfate",false);
					return
				}
			}
		};
		
		elements.drywall = {
			color: "#dedcd9",
			behavior: behaviors.SUPPORT,
			tick: function(pixel) {
				if(pixel.burning && pixel.temp < 80) {
					delete pixel.burning;
					delete pixel.burnStart
				};
				pixel.isWet ??= Math.random() < 0.085;
				var chance = Math.max(0,scale(pixel.temp,59.9999,100,0,0.05));
				if(pixel.isWet && Math.random() < chance) {
					var emptySlots = getEmptyMooreNeighbors(pixel);
					if(emptySlots.length > 0) {
						var randomCoords = randomChoice(emptySlots);
						if(isEmpty(...randomCoords)) {
							createPixel(getStateAtTemp("water",pixel.temp),...randomCoords);
							changePixel(pixel,"gypsum");
							delete pixel.isWet;
							return
						}
					}
				}
			},
			burn: 1,
			burnTime: 100,
			burnInto: ["gypsum","gypsum","gypsum","gypsum","gypsum","gypsum","gypsum","gypsum","gypsum","gypsum","steam","ash"],
			category: "solids",
			state: "solid",
			density: 609
		};

		elements.steel.movable = false;

		elements.support_steel = {
			color: elements.steel.color,
			behavior: behaviors.SUPPORT,
			tempHigh: elements.steel.tempHigh,
			stateHigh: "molten_steel",
			category: "solids",
			density: elements.steel.density,
			conduct: elements.steel.conduct,
			hardness: elements.steel.hardness,
		};

		elements.support_aluminum = {
			color: elements.aluminum.color,
			behavior: behaviors.SUPPORT,
			tempHigh: elements.aluminum.tempHigh,
			stateHigh: "molten_aluminum",
			category: "solids",
			density: elements.aluminum.density,
			conduct: elements.aluminum.conduct,
			hardness: elements.aluminum.hardness,
		};
		
		elements.support_copper = {
			color: elements.copper.color,
			behavior: behaviors.SUPPORT,
			tempHigh: elements.copper.tempHigh,
			stateHigh: "molten_copper",
			category: "solids",
			density: elements.copper.density,
			conduct: elements.copper.conduct,
			hardness: elements.copper.hardness,
		};
		
		runAfterAutogen(function() {
			for(var name in elements) {
				var rxns = elements[name].reactions;
				if(!rxns) { continue };
				if(typeof(rxns) == "object" && typeof(rxns["steel"]) === "object") {
					rxns.support_steel = rxns.steel
				};
				if(typeof(rxns) == "object" && typeof(rxns["aluminum"]) === "object") {
					rxns.support_aluminum = rxns.aluminum
				}
				if(typeof(rxns) == "object" && typeof(rxns["copper"]) === "object") {
					rxns.support_copper = rxns.copper
				}
			};
			elements.support_steel.reactions = elements.steel.reactions;
			elements.support_aluminum.reactions = elements.aluminum.reactions;
			elements.support_copper.reactions = elements.copper.reactions;
		});

		var newAcidIgnores = ["glass_pane", "rad_glass_pane", "rad_glass_shard", "hanging_plastic"];
		for(i = 0; i < newAcidIgnores.length; i++) {
			elements.acid.ignore.push(newAcidIgnores[i]);
			elements.acid_gas.ignore.push(newAcidIgnores[i]);
		};

		elements.rad_glass.breakInto = "rad_glass_shard";

		if(!elements.glass_shard.reactions) {
			elements.glass_shard.reactions = {};
		};
		elements.glass_shard.reactions.radiation = { "elem1":"rad_glass_shard", "chance":0.33 };

		if(!elements.molten_glass.reactions) {
			elements.molten_glass.reactions = {};
		};
		elements.molten_glass.reactions.radiation = { "elem1":"molten_rad_glass", "chance":0.33 };

		elements.rad_glass_shard = {
			color: ["#648c64","#6aad83","#6a9171"],
			behavior: [
				"XX|CR:radiation%0.075|XX",
				"CR:radiation%0.075|XX|CR:radiation%0.075",
				"M2|M1 AND CR:radiation%0.075|M2",
			],
			tempHigh: 1500,
			stateHigh: "molten_rad_glass",
			category: "powders",
			state: "solid",
			density: 2500,
		};

		elements.molten_rad_glass = {
			behavior: [
				"XX|CR:radiation%0.15 AND CR:fire%2.5|XX",
				"M2 AND CR:radiation%0.15|XX|M2 AND CR:radiation%0.15",
				"M1|M1 AND CR:radiation%0.15|M1",
			],
		};

		elements.attach_concrete = {
			color: "#ababab",
			properties: {
				"attached": false,
				"attachOffsets": [null, null]
			},
			tick: function(pixel) {
				if (pixel.start === pixelTicks) {return}
				if(pixel.attached) {
					if(pixel.attachOffsets === null) {
						pixel.attached = false;
					} else if(pixel.attachOffsets.includes(null)) {
						pixel.attached = false;
					} else {
						var attachCoords = [pixel.x + pixel.attachOffsets[0], pixel.y + pixel.attachOffsets[1]];
						if(isEmpty(attachCoords[0],attachCoords[1],false)) { //consider OOB full
							pixel.attached = false;
						};
					};
				} else { //Support behavior if not attached
					if(!isEmpty(pixel.x-1,pixel.y,true) || !isEmpty(pixel.x+1,pixel.y,true)) {
						tryMove(pixel,pixel.x,pixel.y+1);
					};
				};
				doDefaults(pixel);
			},
			tempHigh: 1500,
			stateHigh: "magma",
			category: "powders",
			state: "solid",
			density: 2400,
			hardness: 0.5,
			breakInto: "dust",
		};

		elements.steel_plate_ledge = {
			color: "#F2F2F2",
			tick: function(pixel) {
				if(pixel.attached) {
					if(pixel.attachOffsets === null) {
						pixel.attached = false;
					} else if(pixel.attachOffsets.includes(null)) {
						pixel.attached = false;
					} else {
						var attachCoords = [pixel.x + pixel.attachOffsets[0], pixel.y + pixel.attachOffsets[1]];
						if(isEmpty(attachCoords[0],attachCoords[1],false)) { //consider OOB full
							pixel.attached = false;
						};
					};
				} else { //Move if not attached
					tryMove(pixel,pixel.x,pixel.y+1);
				};
				doDefaults(pixel);
			},
			properties: {
				"attached": false,
				"attachOffsets": [null, null]
			},
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			category: "solids",
			density: 785,
			conduct: 0.32,
			hardness: 0.7,
			breakInto: "metal_scrap",
		};

		//Seeds
		elements.building_1_seed = {
			tick: function(pixel) {
				for(cx = -4; cx <= 4; cx++) {
					for(cy = -4; cy <= 4; cy++) {
						if(cx === 0 && cy === 0) {
							continue;
						};
						var finalCoords = [pixel.x+cx,pixel.y+cy];
						if(isEmpty(...finalCoords,true)) {
							continue;
						} else {
							var otherPixel = pixelMap[finalCoords[0]][finalCoords[1]];
							if(otherPixel.element === pixel.element) {
								deletePixel(...finalCoords);
							};
						};
					};
				};
				if(!isEmpty(pixel.x,pixel.y-1,true)) {
					swapPixels(pixel,pixelMap[pixel.x][pixel.y-1]);
					return;
				};
				if(!tryMove(pixel,pixel.x,pixel.y+1)) {
					var randomHeight = 13 + Math.floor(Math.random() * (8 + 1)) //min 12, variance 8
					var currentHeight = pixel.y + 2;
					var endHeight = pixel.y - randomHeight;
					//bottom 2 rows of concrete, 2 door layers and another concrete (the three of those counting against the final height)
					loadPixelRowFromArray(buildingOneSegmentConcrete,pixel.x,currentHeight,true,false);
					currentHeight--;
					loadPixelRowFromArray(buildingOneSegmentConcrete,pixel.x,currentHeight,true,false);
					currentHeight--;
					loadPixelRowFromArray(buildingOneSegmentDoor,pixel.x,currentHeight,true,true);
					currentHeight--;
					loadPixelRowFromArray(buildingOneSegmentDoor,pixel.x,currentHeight,true,true);
					currentHeight--;
					loadPixelRowFromArray(buildingOneSegmentConcrete,pixel.x,currentHeight,true,true);
					currentHeight--;
					//start looped alternating rows
					while(currentHeight > endHeight) {
						//console.log(currentHeight)
						if(outOfBounds(pixel.x,pixel.y)) {
							break;
						};
						loadPixelRowFromArray(buildingOneSegmentWindows,pixel.x,currentHeight,true,true);
						currentHeight--;
						loadPixelRowFromArray(buildingOneSegmentConcrete,pixel.x,currentHeight,true,true);
						currentHeight--;
					};
				};
			},
			excludeRandom: true,
			desc: "Creates a miniature building made of concrete and glass.",
			cooldown: 6,
			state: "solid",
			hardness: 1,
			category: "structures",
			color: ["#adadad", "#70b8ba", "#adadad", "#70b8ba", "#adadad"],
		};

		elements.building_2_seed = {
			tick: function(pixel) {
				for(cx = -4; cx <= 4; cx++) {
					for(cy = -4; cy <= 4; cy++) {
						if(cx === 0 && cy === 0) {
							continue;
						};
						var finalCoords = [pixel.x+cx,pixel.y+cy];
						if(isEmpty(...finalCoords,true)) {
							continue;
						} else {
							var otherPixel = pixelMap[finalCoords[0]][finalCoords[1]];
							if(otherPixel.element === pixel.element) {
								deletePixel(...finalCoords);
							};
						};
					};
				};
				if(!isEmpty(pixel.x,pixel.y-1,true)) {
					swapPixels(pixel,pixelMap[pixel.x][pixel.y-1]);
					return;
				};
				if(!tryMove(pixel,pixel.x,pixel.y+1)) {
					var currentHeight = pixel.y + 2;
					for(q = 0; q < buildingTwoSegments.length; q++) {
						if(q >= buildingTwoSegments.length) {
							break;
						};
						loadPixelRowFromArray(buildingTwoSegments[q],pixel.x,currentHeight--,true,(q > 1));
					};
				};
			},
			excludeRandom: true,
			desc: "Creates a miniature house.",
			cooldown: 6,
			state: "solid",
			hardness: 1,
			category: "structures",
			color: ["#f05d43", "#f05d43", "#b06f33"],
		};

		elements.room_seed = {
			color: "#ffffff",
			tick: function(pixel) {
				if(!tryMove(pixel,pixel.x,pixel.y+1)) {
					var currentHeight = pixel.y;
					for(q = oldRoom.length - 1; q > -1; q--) {
						loadPixelRowFromArray(oldRoom[q],pixel.x,currentHeight--,true,true);
					};
				};
			},
			desc: "Creates a large room.",
			excludeRandom: true,
			cooldown: 10,
			state: "solid",
			hardness: 1,
			category: "structures",
		};

		elements.altered_room_seed = {
			color: "#ffffff",
			tick: function(pixel) {
				if(!tryMove(pixel,pixel.x,pixel.y+1)) {
					var currentHeight = pixel.y;
					for(q = altRoom.length - 1; q > -1; q--) {
						loadPixelRowFromArray(altRoom[q],pixel.x,currentHeight--,true,true);
					};
				};
			},
			desc: "Creates a variant form of the large room used in the old nested structure test.",
			excludeRandom: true,
			cooldown: 10,
			state: "solid",
			hardness: 1,
			category: "structures",
		};

		elements.altroom_compat = {
			name: "Altered Room (Old)",
			hidden: true,
			color: "#ffffff",
			desc: "An old version of the variant room, kept for compatibility because I don't know how to rework the structure test.",
			tick: function(pixel) {
			pixel.arr=[["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"],
					  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "battery","brick",  "brick",  "brick",  "brick",  "brick"],
					  ["glass",  "glass",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "light","light_bulb","air",	"air",	"air",	"glass",  "glass"],
					  ["glass",  "glass",  "light",  "light",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"light",  "air",	"air",	"air",	"glass",  "glass"],
					  ["brass",  "wood",   "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "brass"],
					  ["wood",   "wood",   "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
					  ["wood",   "wood",   "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
					  ["wood",   "wood",   "air",	"air",	"iron",   "straw",  "straw",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"wood",   "wood" ],
					  ["wood",   "wood",   "air",	"air",	"iron",   "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "iron",   "air",	"air",	"air",	"air",	"wood",   "wood" ],
					  ["brass",  "wood",   "air",	"air",	"iron",   "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "straw",  "iron",   "light",  "air",	"air",	"air",	"wood",   "brass"],
					  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick"],
					  ["brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick",  "brick", "battery", "brick",  "brick",  "brick",  "brick",  "brick"]]

				aa = (0 - (Math.floor(pixel.arr[0].length / 2)))
				na = Math.abs(aa)
				if(pixel.arr[0].length % 2 == 1) {
					bb = ((Math.floor(pixel.arr[0].length / 2)) + 1)
				} else if(pixel.arr[0].length % 2 == 0) {
					bb = (Math.floor(pixel.arr[0].length / 2))
				}

				cc = (0 - (Math.floor(pixel.arr.length / 2)))
				nc = Math.abs(cc)
				if(pixel.arr.length % 2 == 1) {
					dd = ((Math.floor(pixel.arr.length / 2)) + 1)
				} else if(pixel.arr.length % 2 == 0) {
					dd = (Math.floor(pixel.arr.length / 2))
				}
				for (let j = cc; j < dd; j++) {
					for (let i = aa; i < bb; i++) {
						if(!isEmpty(pixel.x+i,pixel.y+j) && !outOfBounds(pixel.x+i,pixel.y+j)) {
							if(pixel.arr[j+nc][i+na] !=  "air" || pixel.arr[j+nc][i+na] == "air") {
								deletePixel(pixel.x+i,pixel.y+j)
							}
						}
						if(pixel.arr[j+nc][i+na]) {
							if(isEmpty(pixel.x+i,pixel.y+j) && pixel.arr[j+nc][i+na] !=  "air" && pixel.arr[j+nc][i+na] != "air" && !outOfBounds(pixel.x+i,pixel.y+j)) {
								createPixel(pixel.arr[j+nc][i+na],pixel.x+i,pixel.y+j)
							}
						}
					}
				}
			},
			category:"structures",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		},

		elements.nested_structure_test = {
			name: "Nested Structure Test (Old)",
			color: "#ffffff",
			cooldown: 13,
			desc: "An old test of structure spawners in structure spawners. Creates several rooms stacked on top of each other.",
			tick: function(pixel) {
				pixel.arr=[["altroom_compat",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   ["altroom_compat",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   ["altroom_compat",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   ["altroom_compat",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   ["altroom_compat",  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	  "air"  ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ],
						   [  "air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",	"air",   "brick",	"brick" ]]

				aa = (0 - (Math.floor(pixel.arr[0].length / 2)))
				na = Math.abs(aa)
				if(pixel.arr[0].length % 2 == 1) {
					bb = ((Math.floor(pixel.arr[0].length / 2)) + 1)
				} else if(pixel.arr[0].length % 2 == 0) {
					bb = (Math.floor(pixel.arr[0].length / 2))
				}

				cc = (0 - (Math.floor(pixel.arr.length / 2)))
				nc = Math.abs(cc)
				if(pixel.arr.length % 2 == 1) {
					dd = ((Math.floor(pixel.arr.length / 2)) + 1)
				} else if(pixel.arr.length % 2 == 0) {
					dd = (Math.floor(pixel.arr.length / 2))
				}
				for (let j = cc; j < dd; j++) {
					for (let i = aa; i < bb; i++) {
						if(!isEmpty(pixel.x+i,pixel.y+j) && !outOfBounds(pixel.x+i,pixel.y+j)) {
							if(pixel.arr[j+nc][i+na] !=  "air" || pixel.arr[j+nc][i+na] == "air") {
								deletePixel(pixel.x+i,pixel.y+j)
							}
						}
						if(pixel.arr[j+nc][i+na]) {
							if(isEmpty(pixel.x+i,pixel.y+j) && pixel.arr[j+nc][i+na] !=  "air" && pixel.arr[j+nc][i+na] != "air" && !outOfBounds(pixel.x+i,pixel.y+j)) {
								createPixel(pixel.arr[j+nc][i+na],pixel.x+i,pixel.y+j)
							}
						}
					}
				}
			},
			category:"structures",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		};

		function _toggleDesertBuildings() {
			var layer = worldgentypes.desert.layers[0];
			if(layer[1] !== "building_1_seed") { //if the first layer isn't a building layer, add one
				worldgentypes.desert.layers.unshift([0.95,"building_1_seed",0.01]);
			} else if(layer[1] === "building_1_seed") { //if the first layer is a building layer, remove it
				worldgentypes.desert.layers.shift();
			};
		};

	//OLD WORLDGEN TEST ##

		elements.worldgen_test = {
			color: ["#787674", "#787674", "#787674", "#8c5923", "#8c5923", "#54c942", "#f7f0b0", "#5280eb"],
			tick: function(pixel) {
				for (var i = 1; i < width; i++) {

					for (var j = 1; j < height; j++) {

						if (isEmpty(i,j)) {

							if(j >= Math.floor(6*height/7) && j < Math.floor(7*height/7)) {

								if(Math.random() < 0.95) {

									createPixel("rock",i,j)


								}

							}

							if(j >= Math.floor(5*height/7) && j < Math.floor(6*height/7)) {

								if(Math.random() < 0.95) {

									if(Math.random() < 1/2) {

										if(i >= Math.floor(11*width/14) && i < Math.floor(14*width/14)) {

											createPixel("sand",i,j)

										} else {

											createPixel("dirt",i,j)

										}

									} else {

										createPixel("rock",i,j)

									}

								}

							}

							if(j >= Math.floor(4*height/7) && j < Math.floor(5*height/7)) {

								if(Math.random() < 0.95) {

									if(i >= Math.floor(11*width/14) && i < Math.floor(14*width/14)) {

										createPixel("sand",i,j)

									} else {

										createPixel("dirt",i,j)

									}

								}

							}

							if(j >= Math.floor(15*height/28) && j < Math.floor(4*height/7)) {

								if(Math.random() < 0.95) {

									if(i < Math.floor(11*width/14)) {

										createPixel("grass",i,j)

									}

								}

							}

						}

					}

				}

				explodeAt(Math.floor(51*width/56),Math.floor(9*height/14),Math.floor(1.8*height/7),fire="water")

				deletePixel(pixel.x,pixel.y)

			},
			category: "machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		}

		elements.nether_gen_test = {
			color: ["#751318","#694a20","#f0771a"],
			tick: function(pixel) {
				for (var i = 1; i < width; i++) {

					for (var j = 1; j < height; j++) {

						if (isEmpty(i,j)) {

							if(j >= Math.floor(5*height/7) && j < Math.floor(7*height/7)) {

								if(Math.random() < 0.95) {

									if(Math.random() < 2/3) {

										if(i >= Math.floor(11*width/14) && i < Math.floor(14*width/14)) {

											createPixel("soul_sand",i,j)

										} else {

											createPixel("netherrack",i,j)

										}

									} else {

										createPixel("gravel",i,j)

									}

								}

							}

							if(j >= Math.floor(4*height/7) && j < Math.floor(5*height/7)) {

								if(Math.random() < 0.95) {

									if(i >= Math.floor(11*width/14) && i < Math.floor(14*width/14)) {

										createPixel("soul_sand",i,j)

									} else {

										createPixel("netherrack",i,j)

									}

								}

							}

						}

					}

				}

				explodeAt(Math.floor(51*width/56),Math.floor(10*height/14),Math.floor(1.8*height/7),fire="magma")

				if(!isEmpty(Math.floor(51*width/56),Math.floor(10*height/14))) {
					pixelMap[Math.floor(51*width/56)][Math.floor(10*height/14)].temp += 10**(3*(Math.floor(Math.log10(Math.sqrt((height**2)+(width**2))))))
				}

				deletePixel(pixel.x,pixel.y)

			},
			category: "machines",
			insulate: true,
			state: "solid",
			excludeRandom: true,
		}

	//NOTE BLOCKS ##

		audioContext = new AudioContext()

		//Derived from marcgg's music.js

		oscillatorDefaults = {
			frequency: 440,
			type: "sine",
			endType: "none",
			length: 1,
			volume: 1,
			delay: 0,
		};

		audioObject = {};

		function oscillator(name="test",parameterObject=oscillatorDefaults){ //creates oscillator with gain node, has specifiable frequency and type, fades out over 1 second (hard-coded)
			var defaultKeys = Object.keys(oscillatorDefaults); //readability variable

			for(i = 0; i < defaultKeys.length; i++) {
				var key = defaultKeys[i]; //the indexed keyname
				if(typeof(parameterObject[key]) === "undefined") {
					parameterObject[key] = oscillatorDefaults[key];
				};
			};

			var oscillatorNodeName = `${name}Oscillator`;
			var gainNodeName = `${name}Gain`;

			audioObject[oscillatorNodeName] = audioContext.createOscillator()
			audioObject[gainNodeName] = audioContext.createGain()
			audioObject[gainNodeName].gain.value = parameterObject.volume;
			audioObject[oscillatorNodeName].type = parameterObject.type
			audioObject[oscillatorNodeName].connect(audioObject[gainNodeName])
			audioObject[oscillatorNodeName].frequency.value = parameterObject.frequency
			audioObject[gainNodeName].connect(audioContext.destination)
			audioObject[oscillatorNodeName].start(audioContext.currentTime + parameterObject.delay)

			//stopping handler
			if(parameterObject.endType === "exponential") { //starts fading immediately
				audioObject[gainNodeName].gain.exponentialRampToValueAtTime(
					0.00001, audioContext.currentTime + parameterObject.length
				);
			} else if(parameterObject.endType === "linear") { //starts fading immediately
				audioObject[gainNodeName].gain.linearRampToValueAtTime(
					0.00001, audioContext.currentTime + parameterObject.length
				);	
			} else { //waits to stop
				audioObject[oscillatorNodeName].stop(audioContext.currentTime + parameterObject.delay + parameterObject.length);
			};
		};

		elements.note_block = {
			color: "#ee33ee",
			behavior: behaviors.WALL,
			state: "solid",
			category: "machines",
			density: 1200,
			hardness: 0.2,
			breakInto: ["plastic","metal_scrap","metal_scrap","metal_scrap"],
			conduct: 1,
			properties: {
				frequency: 440,
				type: "sine",
				endType: "none",
				length: 1,
				volume: 1,
				delay: 0,
				debounce: 0,
				debounceLength: tps
			},
			tick: function(pixel) {
				var pixelSoundName = `x${pixel.x}y${pixel.y}`; //Generate unique-enough name

				var pixelPropertyObject = { //Load sound properties from pixel as object;
					frequency: pixel.frequency,
					type: pixel.type,
					endType: pixel.endType,
					length: pixel.length,
					volume: pixel.volume,
					delay: pixel.delay,
				};

				//console.log(pixelPropertyObject);

				if(pixel.debounce < 1) {
					//console.log(`${pixel.debounce} not debounced, play`);
					if(pixel.charge) {
						oscillator(pixelSoundName,pixelPropertyObject);
						delete pixel.charge;
						pixel.debounce = pixel.debounceLength;
					};
				} else if(pixel.debounce > 0) {
					//console.log(`${pixel.debounce} debounced, don't play`);
					pixel.debounce--;
				};
			},
		};

		runAfterLoad(function() {
			elements.note_block.movable = false;
		});

		if(runAfterAutogen) {
			runAfterAutogen(function() {
				elements.note_block.movable = false;
			});
		};

	//STATE-SPECIFIC VOIDS ##

		//Deletion code mostly by R74n

		elements.drain = {
			color: "#888888",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "liquid") {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A drain that removes any liquid.",
			hardness: 0.8,
			insulate: true,

		};

		elements.vent = {
			color: "#e6e6e6",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "gas") {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A vent that removes any gas. <span style=\"color: #eeaaaa;\">Remarkably sussy.<span>",
			hardness: 0.8,
			insulate: true,
		}; elements.wall.movable = false; chuteExcludedElements = ["wall","drain","vent","chute","hole_of_miscellanea","drent","drute","vute","drolent","drolute","volute","void"]; elements.acid.ignore.push("drain"); elements.acid_gas.ignore.push("vent");

		elements.chute = {
			color: "#636363",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "solid" && elements[newPixel.element].movable === true && !chuteExcludedElements.includes(newPixel.element)) {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A chute that removes any powder.",
			movable: false,
			hardness: 0.8,
			insulate: true,

		}; mainStateArray = ["solid","liquid","gas"];

		elements.hole_of_miscellanea = {
			color: "#69606b",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].movable && !mainStateArray.includes(elements[newPixel.element].state)) {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A mysterious hole that removes the other states of matter.",
			movable: false,
			insulate: true,
		};

		elements.drent = {
			color: "#B7B7B7",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "liquid" || elements[newPixel.element].state === "gas") {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A combined drain and vent that removes any liquid or gas. <span style=\"color: #ffcccc;\">Slightly sussy.<span>",
			hardness: 0.8,
			insulate: true,

		};

		elements.drute = {
			color: "#767676",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "liquid") {
							deletePixel(coord[0],coord[1]);
						} else if (elements[newPixel.element].state === "solid" && elements[newPixel.element].movable === true && !chuteExcludedElements.includes(newPixel.element)) {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A combined drain and chute that removes any liquid or powder.",
			hardness: 0.8,
			insulate: true,

		};

		elements.vute = {
			color: "#9d8aa1",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "gas") {
							deletePixel(coord[0],coord[1]);
						} else if (elements[newPixel.element].state === "solid" && elements[newPixel.element].movable === true && !chuteExcludedElements.includes(newPixel.element)) {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A vent that removes any gas. <span style=\"color: #eeaaaa;\">Somewhat sussy.<span>",
			hardness: 0.8,
			insulate: true,
		};

		elements.drolent = {
			color: "#b8afba",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state !== "solid") {
							deletePixel(coord[0],coord[1]);
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A combined drain, hole, and vent removes anything but powders. <span style=\"color: #ffd7d7;\">Slightly sussy.<span>",
			hardness: 0.8,
			insulate: true,

		};

		elements.drolute = {
			color: "#786c7a",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "solid" && elements[newPixel.element].movable === true && !chuteExcludedElements.includes(newPixel.element)) {
							deletePixel(coord[0],coord[1]);
						} else {
							if (elements[newPixel.element].state !== "solid" && elements[newPixel.element].state !== "gas") {
								deletePixel(coord[0],coord[1]);
							}
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A combined drain, hole, and chute removes anything but gases.",
			hardness: 0.8,
			insulate: true,

		};

		elements.volute = {
			color: "#b8afba",
			behavior: behaviors.WALL,
			tick: function(pixel) {
				var coordsToCheck = [
					[pixel.x-1,pixel.y],
					[pixel.x+1,pixel.y],
					[pixel.x,pixel.y-1],
					[pixel.x,pixel.y+1],
				];
				for (var i = 0; i < coordsToCheck.length; i++) {
					var coord = coordsToCheck[i];
					if (!isEmpty(coord[0],coord[1],true)) {
						var newPixel = pixelMap[coord[0]][coord[1]];
						if (elements[newPixel.element].state === "solid" && elements[newPixel.element].movable === true && !chuteExcludedElements.includes(newPixel.element)) {
							deletePixel(coord[0],coord[1]);
						} else {
							if (elements[newPixel.element].state !== "solid" && elements[newPixel.element].state !== "liquid") {
								deletePixel(coord[0],coord[1]);
							}
						}
					}
				}
			},
			category: "special",
			tempHigh: 1455.5,
			stateHigh: "molten_steel",
			state: "solid",
			density: 2000,
			breakInto: ["metal_scrap"],
			desc: "A combined vent, hole, and chute removes anything but liquids.",
			hardness: 0.8,
			insulate: true,

		};

		//The all-combination is called void.

		elements.drain.breakInto = ["steel_scrap"];
		elements.vent.breakInto = ["steel_scrap"];
		elements.chute.breakInto = ["steel_scrap"];
		elements.hole_of_miscellanea.breakInto = ["steel_scrap"];

		elements.drain.breakInto.push("magic");
		elements.vent.breakInto.push("magic");
		elements.chute.breakInto.push("magic");
		elements.hole_of_miscellanea.breakInto.push("magic");

	//THERMAL CONDUITS ##

		//Base element, uninitialized IO
		elements.thermal_conduit = {
			hidden: true,
			category: "thermal conduits",
			hardness: 1,
			state: "solid",
			density: 3000,
			temp: (settings.abszero ?? -273.15),
			behavior: behaviors.WALL,
			color: "#cf9f7f",
			insulate: true,
			properties: {
				inputs: [],
				outputs: [],
				rate: 2
			},
			tick: function(pixel) {
				if(pixel.inputs.length == 0 && pixel.outputs.length == 0) {
					return;
				};
				//Iterate through inputs
				for(var i = 0; i < pixel.inputs.length; i++) {
					//Readability variable for offset pair
					var coordPair = pixel.inputs[i];
					//Coord RVs
					var newX = pixel.x + coordPair[0];
					var newY = pixel.y + coordPair[1];
					//Skip empties
					if(isEmpty(newX,newY,true)) {
						continue;
					} else {
						//New pixel RV
						var newPixel = pixelMap[newX][newY];
						//More sugar
						var newPixelTempKelvin = newPixel.temp - (settings.abszero ?? -273.15);
						//Skip pixels at or below absolute zero
						if(newPixelTempKelvin <= 0) {
							continue;
						};
						//If temp withdrawal would put pixel below absolute zero
						if(newPixelTempKelvin <= pixel.rate) {
							//Special "draining" logic
							pixel.temp += newPixelTempKelvin;
							newPixel.temp = (settings.abszero ?? -273.15);
						} else {
							//If not, just move the temperature
							pixel.temp += pixel.rate;
							newPixel.temp -= pixel.rate;
						};
						pixelTempCheck(pixel);
						pixelTempCheck(newPixel);
					};
				};

				if(pixelTempKelvin <= 0 || pixel.outputs.length == 0) {
					return false;
				};

				//Iterate through outputs
				var availableOutputs = [];
				//Adjust effective output count;
				for(var i = 0; i < pixel.outputs.length; i++) {
					var coordPair = pixel.outputs[i];
					var newX = pixel.x + coordPair[0];
					var newY = pixel.y + coordPair[1];
					if(!isEmpty(newX,newY,true)) {
						availableOutputs.push([newX,newY]);
					};
				};

				var pixelTempKelvin = pixel.temp - (settings.abszero ?? -273.15);
				var isDraining = (pixelTempKelvin <= pixel.rate);
				var effectiveRate = (isDraining ? pixelTempKelvin : pixel.rate) / availableOutputs.length;
				//Actual distribution
				for(var i = 0; i < availableOutputs.length; i++) {
					var coordPair = availableOutputs[i];
					var newPixel = pixelMap[coordPair[0]][coordPair[1]];
					newPixel.temp += effectiveRate;
				};
				if(availableOutputs.length > 0) { isDraining ? pixel.temp = (settings.abszero ?? -273.15) : pixel.temp -= pixel.rate };
			},
		};

		function defineConduitElement(nameDescriber,inputOffsetNestedArray,outputOffsetNestedArray) {
			//There is no validation here. Please don't do anything stupid.
			//Pretty please don't run after script loading, i don't feel like adding that code in.
			var autoName = `thermal_${nameDescriber}_conduit`;
			//console.log(inputOffsetNestedArray);
			elements[autoName] = {
				category: "thermal conduits",
				properties: {
					inputs: inputOffsetNestedArray,
					outputs: outputOffsetNestedArray,
					rate: 2,
				},
				hardness: 1,
				state: "solid",
				density: 3000,
				temp: (settings.abszero ?? -273.15),
				behavior: behaviors.WALL,
				color: "#cf9f7f",
				insulate: true,
				tick: function(pixel) {
					pixel.element = "thermal_conduit"; //manual change to preserve properties
				},
			};
			return autoName;
		}

		autoConduitTable = {
			//up_to_up: useless,
			up_to_down: {ins: [[0,-1]], outs: [[0,1]]},
			up_to_left: {ins: [[0,-1]], outs: [[-1,0]]},
			up_to_right: {ins: [[0,-1]], outs: [[1,0]]},

			down_to_up: {ins: [[0,1]], outs: [[0,-1]]},
			//down_to_down: useless,
			down_to_left: {ins: [[0,1]], outs: [[-1,0]]},
			down_to_right: {ins: [[0,1]], outs: [[1,0]]},

			left_to_up: {ins: [[-1,0]], outs: [[0,-1]]},
			left_to_down: {ins: [[-1,0]], outs: [[0,1]]},
			//left_to_left: useless,
			left_to_right: {ins: [[-1,0]], outs: [[1,0]]},

			right_to_up: {ins: [[1,0]], outs: [[0,-1]]},
			right_to_down: {ins: [[1,0]], outs: [[0,1]]},
			right_to_left: {ins: [[1,0]], outs: [[-1,0]]},
			//right_to_right: useless,

			left_and_right_to_down: {ins: [[-1,0],[1,0]], outs: [[0,1]]},
			up_and_down_to_left: {ins: [[0,-1],[0,1]], outs: [[-1,0]]},
			left_and_right_to_up: {ins: [[-1,0],[1,0]], outs: [[0,-1]]},
			up_and_down_to_right: {ins: [[0,-1],[0,1]], outs: [[1,0]]},

			down_to_left_and_right: {outs: [[-1,0],[1,0]], ins: [[0,1]]},
			left_to_up_and_down: {outs: [[0,-1],[0,1]], ins: [[-1,0]]},
			up_to_left_and_right: {outs: [[-1,0],[1,0]], ins: [[0,-1]]},
			right_to_up_and_down: {outs: [[0,-1],[0,1]], ins: [[1,0]]},

			up_down_and_left_to_right: {ins: [[0,-1],[0,1],[-1,0]], outs: [[1,0]]},
			up_left_and_right_to_down: {ins: [[0,-1],[-1,0],[1,0]], outs: [[0,1]]},
			up_down_and_right_to_left: {ins: [[0,-1],[0,1],[1,0]], outs: [[-1,0]]},
			down_left_and_right_to_up: {ins: [[0,1],[-1,0],[1,0]], outs: [[0,-1]]},

			right_to_up_down_and_left: {outs: [[0,-1],[0,1],[-1,0]], ins: [[1,0]]},
			down_to_up_left_and_right: {outs: [[0,-1],[-1,0],[1,0]], ins: [[0,1]]},
			left_to_up_down_and_right: {outs: [[0,-1],[0,1],[1,0]], ins: [[-1,0]]},
			up_to_down_left_and_right: {outs: [[0,1],[-1,0],[1,0]], ins: [[0,-1]]},

			left_and_down_to_right: {ins: [[-1,0],[0,1]], outs: [[1,0]]},
			right_and_down_to_left: {ins: [[1,0],[0,1]], outs: [[-1,0]]},
			left_and_up_to_right: {ins: [[-1,0],[0,-1]], outs: [[1,0]]},
			right_and_up_to_left: {ins: [[1,0],[0,-1]], outs: [[-1,0]]},

			right_to_down_and_left: {ins: [[1,0]], outs: [[-1,0],[0,1]]},
			right_to_up_and_left: {ins: [[1,0]], outs: [[-1,0],[0,-1]]},
			left_to_down_and_right: {ins: [[-1,0]], outs: [[1,0],[0,1]]},
			left_to_up_and_right: {ins: [[1,0]], outs: [[1,0],[0,-1]]},
		};

		for(direction in autoConduitTable) {
			defineConduitElement(direction,autoConduitTable[direction].ins,autoConduitTable[direction].outs);
		};

	//WIRELESS TRANSMISSION ##

		//https://stackoverflow.com/a/60922255
		elements.wifi = {
			color: "#bfff7f",
			properties: {
				_channel: 0,
				_correspondingWifi: null
			},
			hardness: 0.8,
			breakInto: ["plastic","steel","copper"],
			conduct: 1,
			insulate: true,
			tick: function(pixel) {
				pixel._channel = Math.floor(pixel.temp / 100);

				var colorBase = (pixel._channel + 3);
				if(colorBase < 0 || colorBase > 124) {
					pixel.color == "rgb(212,185,222)";
				} else {
					colorBase = colorBase.toString(5).padStart(3,"0").split("").map(x => parseInt(x) * 64);
					pixel.color = `rgb(${colorBase.join(",")})`
				};

				pixel._correspondingWifi = currentPixels.filter(function(pixelToCheck) {
					return (
						pixelToCheck !== pixel && //should work if this pixel is the same as the other one by reference
						["wifi","receiver"].includes(pixelToCheck.element) &&
						pixelToCheck._channel == pixelChannel
					);
				},pixelChannel=pixel._channel).map(pixel => [pixel.x,pixel.y]);

				if(pixel.charge) {
					for(var i in pixel._correspondingWifi) {
						i = parseInt(i);
						var wifiCoords = pixel._correspondingWifi[i];
						var newPixel = pixelMap[wifiCoords[0]]?.[wifiCoords[1]];
						if(newPixel) {
							if(!newPixel.chargeCD) {
								for(var j in adjacentCoords) {
									j = parseInt(j);
									var pixelAdjacentToWifi = pixelMap[newPixel.x+adjacentCoords[j][0]]?.[newPixel.y+adjacentCoords[j][1]];
									if(pixelAdjacentToWifi) { pixelAdjacentToWifi.charge = 1 };
								};
							}
						}
					};
					if(pixel._correspondingWifi.length > 0) {
						delete pixel.charge;
						pixel.chargeCD = 5
					}
				}

				if(typeof(pixel.chargeCD) !== "undefined") {
					pixel.chargeCD--;
					if(pixel.chargeCD <= 0) { delete pixel.chargeCD };
				};
			},
			category: "machines",
			state: "solid",
		};

		elements.transmitter = {
			color: "#00ff7f",
			properties: {
				_channel: 0,
				_correspondingWifi: null
			},
			hardness: 0.8,
			breakInto: ["plastic","steel","copper"],
			conduct: 1,
			insulate: true,
			tick: function(pixel) {
				pixel._channel = Math.floor(pixel.temp / 100);

				var colorBase = (pixel._channel + 3);
				if(colorBase < 0 || colorBase > 124) {
					pixel.color == "rgb(212,185,222)";
				} else {
					colorBase = colorBase.toString(5).padStart(3,"0").split("").map(x => parseInt(x) * 64);
					pixel.color = `rgb(${colorBase.join(",")})`
				};

				pixel._correspondingWifi = currentPixels.filter(function(pixelToCheck) {
					return (
						pixelToCheck !== pixel && //should work if this pixel is the same as the other one by reference
						["wifi","receiver","support_receiver"].includes(pixelToCheck.element) &&
						pixelToCheck._channel == pixelChannel
					);
				},pixelChannel=pixel._channel).map(pixel => [pixel.x,pixel.y]);

				if(pixel.charge) {
					for(var i in pixel._correspondingWifi) {
						i = parseInt(i);
						var wifiCoords = pixel._correspondingWifi[i];
						var newPixel = pixelMap[wifiCoords[0]]?.[wifiCoords[1]];
						if(newPixel) {
							if(!newPixel.chargeCD) {
								for(var j in adjacentCoords) {
									j = parseInt(j);
									var pixelAdjacentToWifi = pixelMap[newPixel.x+adjacentCoords[j][0]]?.[newPixel.y+adjacentCoords[j][1]];
									if(pixelAdjacentToWifi && elements[pixelAdjacentToWifi.element].conduct) { pixelAdjacentToWifi.charge = 1 };
								};
							}
						}
					};
					if(pixel._correspondingWifi.length > 0) {
						delete pixel.charge;
						pixel.chargeCD = 5
					}
				}

				if(typeof(pixel.chargeCD) !== "undefined") {
					pixel.chargeCD--;
					if(pixel.chargeCD <= 0) { delete pixel.chargeCD };
				};
			},
			category: "machines",
			state: "solid",
		}

		elements.receiver = {
			color: "#bfff00",
			properties: {
				_channel: 0
			},
			hardness: 0.8,
			breakInto: ["plastic","steel","copper"],
			conduct: 1,
			insulate: true,
			tick: function(pixel) {
				pixel._channel = Math.floor(pixel.temp / 100);

				var colorBase = (pixel._channel + 3);
				if(colorBase < 0 || colorBase > 124) {
					pixel.color = "rgb(212,185,222)";
				} else {
					colorBase = colorBase.toString(5).padStart(3,"0").split("").map(x => parseInt(x) * 64);
					pixel.color = `rgb(${colorBase.join(",")})`
				};

				if(typeof(pixel.chargeCD) !== "undefined") {
					pixel.chargeCD = Math.min(pixel.chargeCD,5);
					pixel.chargeCD--;
					if(pixel.chargeCD <= 0) { delete pixel.chargeCD };
				};
				if(pixel.charge) {
					pixel.charge -= 0.25;
					if(pixel.charge <= 0) { delete pixel.charge };
				};
			},
			category: "machines",
			state: "solid",
		};

		elements.support_receiver = {
			color: "#bfff00",
			behavior: behaviors.SUPPORT,
			properties: {
				_channel: 0
			},
			hardness: 0.8,
			breakInto: ["plastic","steel","copper"],
			conduct: 1,
			insulate: true,
			tick: function(pixel) {
				pixel._channel = Math.floor(pixel.temp / 100);

				var colorBase = (pixel._channel + 3);
				if(colorBase < 0 || colorBase > 124) {
					pixel.color = "rgb(212,185,222)";
				} else {
					colorBase = colorBase.toString(5).padStart(3,"0").split("").map(x => parseInt(x) * 64);
					pixel.color = `rgb(${colorBase.join(",")})`
				};

				if(typeof(pixel.chargeCD) !== "undefined") {
					pixel.chargeCD = Math.min(pixel.chargeCD,5);
					pixel.chargeCD--;
					if(pixel.chargeCD <= 0) { delete pixel.chargeCD };
				};
				if(pixel.charge) {
					pixel.charge -= 0.25;
					if(pixel.charge <= 0) { delete pixel.charge };
				};
			},
			category: "machines",
			state: "solid",
		};
		
	// SPINEL'S INJECTOR ##

		var injectorPoisonCategories = ["life","auto creepers","shit","cum","food","fantastic creatures","fey","auto_fey"];
		var injectorPoisonBlacklist = ["injector_poison","dead_matter","poisoned_dirt"];
		var injectorPoisonWhitelist = ["blood","skin","hair","poop","blood_ice","wood","wood_plank","sawdust","straw","paper","birthpool","dried_poop","gloomfly","meat_monster","rotten_ravager","bone_beast","withery","withery_plant","banana","apple","rotten_apple","apioform_player","apioform_bee","apioform","apiodiagoform","sugar_cactus","sugar_cactus_seed","flowering_sugar_cactus","tree_branch","sap","silk","red_velvet","silk_velvet","ketchup", "enchanted_ketchup", "frozen_ketchup", "poisoned_ketchup", "frozen_poisoned_ketchup", "ketchup_spout", "ketchup_cloud", "poisoned_ketchup_cloud", "ketchup_snow", "ketchup_snow_cloud", "poisoned_ketchup_snow", "poisoned_ketchup_snow_cloud", "ketchup_gas", "poisoned_ketchup_gas", "ketchup_powder", "poisoned_ketchup_powder", "eketchup_spout", "ketchup_metal", "antiketchup", "dirty_ketchup", "ketchup_gold", "molten_ketchup_metal", "ketchup_fairy", "ketchup_metal_scrap", "ketchup_gold_scrap", "molten_ketchup_gold", "mycelium","vaccine","antibody","infection","sap","caramel","molasses","melted_chocolate","soda","mustard","fry_sauce","tomato_sauce","sugary_tomato_sauce","bio_ooze","zombie_blood","feather","tooth","decayed_tooth","plaque","tartar","bacteria","replacer_bacteria","pop_rocks"];
		var injectorPoisonSubstitutions = {
			"dirt": "poisoned_dirt",
			"dry_dirt": "poisoned_dirt",
			"crimsoil": "poisoned_dirt",
			"rainbow_dirt": "poisoned_dirt",
			"sand": "poisoned_dirt",
			"wet_sand": "poisoned_dirt",
			"mud": "poisoned_dirt",
			"sandy_water": "injector_poison",
			"water": "injector_poison",
		};

		var blue = "rgb(0,0,255)";
		var cyan = "rgb(0,255,255)";
		var green = "rgb(0,255,0)";
		var yellow = "rgb(255,255,0)";
		var orange = "rgb(255,127,0)";
		var red = "rgb(255,0,0)";
		var gray = "rgb(127,127,127)";

		function debugPoisonColor(pixel) {
			pixel.poison ??= 0;
			if(pixel.poison > 5) {
				pixel.color = blue;
			} else if(pixel.poison > 1.5) {
				pixel.color = cyan;
			} else if(pixel.poison > 1) {
				pixel.color = green;
			} else if(pixel.poison > 0.5) {
				pixel.color = yellow;
			} else if(pixel.poison > 0.05) {
				pixel.color = orange;
			} else if(pixel.poison > 0) {
				pixel.color = red;
			} else {
				pixel.color = gray;
			};
		};

		function spreadInjectorPoison(pixel) {
			var convertedPixels = [];
			for(i = 0; i < adjacentCoords.length; i++) { //iterate through neighbor spots
				if(!isEmpty(pixel.x+adjacentCoords[i][0],pixel.y+adjacentCoords[i][1],true)) { //check for adjacentCoords
					var newPixel = pixelMap[pixel.x+adjacentCoords[i][0]][pixel.y+adjacentCoords[i][1]]
					var isInjectorPoisonFairy = (elements[newPixel.element].category == "auto_fey" && newPixel.element.includes("life_eater_"))
					//console.log(newPixel.element,isInjectorPoisonFairy);
					if(
						(injectorPoisonCategories.includes(elements[newPixel.element].category) || injectorPoisonWhitelist.includes(newPixel.element) || Object.keys(injectorPoisonSubstitutions).includes(newPixel.element)) && 
						!injectorPoisonBlacklist.includes(newPixel.element) &&
						!isInjectorPoisonFairy //exclude fairies which produce life eater
					) {
						if(Object.keys(injectorPoisonSubstitutions).includes(newPixel.element)) {
							if(["water","sandy_water"].includes(newPixel.element) && Math.random() < 0.8) {
								continue;
							};
							var data = injectorPoisonSubstitutions[newPixel.element];
							while(data instanceof Array) {
								data = data[Math.floor(Math.random() * data.length)];
							};
							if(data === null) {
								if(newPixel) { deletePixel(newPixel.x,newPixel.y) };
							} else {
								changePixel(newPixel,data);
								convertedPixels.push(newPixel);
							};
						} else {
							changePixel(newPixel,"dead_matter");
							convertedPixels.push(newPixel);
						};
					};
				};
			};
			return convertedPixels.length == 0 ? false : convertedPixels;
		};

		elements.injector_poison = {
			properties: {
				didWeakColorChange: 0,
				poison: 15
			},
			stain: 0.2,
			color: ["#f70a98", "#ff308d"],
			behavior: behaviors.LIQUID,
			tick: function(pixel) {
				//console.log(pixel.poison);
				pixel.didWeakColorChange ??= 0;
				if(isNaN(pixel.poison)) {
					pixel.poison = 15;
					return;
				};
				var convertedPixels = spreadInjectorPoison(pixel);
				if((convertedPixels.length ?? 0) !== 0) {
					for(i = 0; i < convertedPixels.length; i++) {
						convertedPixels[i].poison ??= Math.max(convertedPixels[i].element == "injector_poison" ? 0.9 : 0,pixel.poison - 1);
					};
					pixel.poison--;

					if(pixel.poison <= -1) {
						deletePixel(pixel.x,pixel.y);
						return;
					};
				};
				if(pixel.poison < 5 && pixel.didWeakColorChange == 0) {
					pixel.color = changeSaturation(changeLuminance(pixel.color,0.7 ** (1/3),"multiply","hsljson"),0.7 ** (1/3),"multiply","rgb");
					pixel.didWeakColorChange = 1;
				};
				if(pixel.poison < 1 && pixel.didWeakColorChange == 1) {
					pixel.color = changeSaturation(changeLuminance(pixel.color,0.7 ** (1/3),"multiply","hsljson"),0.7 ** (1/3),"multiply","rgb");
					pixel.didWeakColorChange = 2;
				};
				if(pixel.poison < 0.1 && pixel.didWeakColorChange == 2) {
					pixel.color = changeSaturation(changeLuminance(pixel.color,0.7 ** (1/3),"multiply","hsljson"),0.7 ** (1/3),"multiply","rgb");
					pixel.didWeakColorChange = 3;
				};

				if(pixel.poison >= 0.1 && pixel.didWeakColorChange == 3) {
					pixel.color = changeSaturation(changeLuminance(pixel.color,1/(0.7 ** (1/3)),"multiply","hsljson"),1/(0.7 ** (1/3)),"multiply","rgb");
					pixel.didWeakColorChange = 2;
				};
				if(pixel.poison >= 1 && pixel.didWeakColorChange == 2) {
					pixel.color = changeSaturation(changeLuminance(pixel.color,1/(0.7 ** (1/3)),"multiply","hsljson"),1/(0.7 ** (1/3)),"multiply","rgb");
					pixel.didWeakColorChange = 1;
				};
				if(pixel.poison >= 5 && pixel.didWeakColorChange == 1) {
					pixel.color = changeSaturation(changeLuminance(pixel.color,1/(0.7 ** (1/3)),"multiply","hsljson"),1/(0.7 ** (1/3)),"multiply","rgb");
					pixel.didWeakColorChange = 0;
				};

				if(pixel.poison <= 0 && Math.random() < 0.1) {
					deletePixel(pixel.x,pixel.y);
					return;
				};

				if(isNaN(pixel.poison)) {
					pixel.poison = 15;
				};
				for(z = 0; z < 3; z++) {
					spreadingPropertyReturn(pixel,"poison",injectorPoisonBlacklist).forEach(x => spreadingPropertyReturn(x,"poison",injectorPoisonBlacklist));
				};

				//debugPoisonColor(pixel);
			},
			category: "weapons",
			state: "liquid",
			density: 1000,
			excludeRandom: true,
			/*tempHigh: 300,
			stateHigh: null,*/
			forceAutoGen: true
		};

		elements.dead_matter = {
			color: ["#6b5869", "#99508c", "#b53c8b"],
			behavior: behaviors.POWDER,
			tick: function(pixel) {
				if(isNaN(pixel.poison)) {
					pixel.poison = 10;
				};
				if(pixel.poison > 0.05) {
					var convertedPixels = spreadInjectorPoison(pixel);
					if((convertedPixels.length ?? 0) !== 0) {
						if(Math.random() < 0.2) {
							var randomConverted = randomChoice(convertedPixels);
							swapNumericPropertyValues(pixel,randomConverted,"poison",injectorPoisonBlacklist);
						};
						for(i = 0; i < convertedPixels.length; i++) {
							convertedPixels[i].poison ??= Math.max(0,pixel.poison - 1);
						};
						pixel.poison--;
					};
				};

				for(z = 0; z < 3; z++) {
					spreadingPropertyReturn(pixel,"poison",injectorPoisonBlacklist).forEach(x => spreadingPropertyReturn(x,"poison",injectorPoisonBlacklist));
				};

				if(pixel.poison < 0) {
					pixel.poison = 0
				};

				if(pixel.temp > 350 || (pixel.poison <= 1 && Math.random() < 0.04)) {
					if(Math.random() < pixel.poison) {
						changePixelReturn(pixel,"injector_poison").poison = pixel.poison * 0.85;
						return;
					} else {
						deletePixel(pixel.x,pixel.y);
						return;
					};
				};

				//debugPoisonColor(pixel);
			},
			category: "life",
			state: "solid",
			density: 1050,
			excludeRandom: true,
		};

		elements.poisoned_dirt = {
			behavior: behaviors.POWDER,
			color: ["#665e66","#735370","#805265"],
			tick: function(pixel) {
				if(isNaN(pixel.poison)) {
					pixel.poison = 10;
				};
				if(pixel.poison > 0.05) {
					var convertedPixels = spreadInjectorPoison(pixel);
					if((convertedPixels.length ?? 0) !== 0) {
						if(Math.random() < 0.2) {
							var randomConverted = randomChoice(convertedPixels);
							swapNumericPropertyValues(pixel,randomConverted,"poison",injectorPoisonBlacklist);
						};
						for(i = 0; i < convertedPixels.length; i++) {
							convertedPixels[i].poison ??= Math.max(0,pixel.poison - 1);
						};
						pixel.poison--;
					};
				};

				for(z = 0; z < 3; z++) {
					spreadingPropertyReturn(pixel,"poison",injectorPoisonBlacklist).forEach(x => spreadingPropertyReturn(x,"poison",injectorPoisonBlacklist));
				};

				if(pixel.poison < 0) {
					pixel.poison = 0
				};

				//debugPoisonColor(pixel);
			},
			category: "life",
			state: "solid",
			density: 1220,
			excludeRandom: true,
		};


		//Injector data and decoding
		var injectorCrystalPinks = {
			"0": "rgb(239,109,189)",
			"1": "rgb(253,185,241)",
			"2": "rgb(253,207,234)",
			"3": "rgb(253,161,225)",
			"4": "rgb(253,156,206)",
			"5": "rgb(249,139,249)",
			"6": "rgb(235,95,190)",
			"7": "rgb(221,26,132)",
			"8": "rgb(246,68,183)",
			"9": "rgb(184,24,95)",
			"~": "rgb(221,26,132)",
			"!": "rgb(230,64,191)",
			"@": "rgb(136,12,60)",
			"#": "rgb(221,26,132)"
		}

		var poisonPink = "rgb(253,54,196)";

		var injectorMetalColors = {
			"A": "rgb(82,95,136)",
			"B": "rgb(35,57,94)",
			"C": "rgb(60,79,120)",
			"D": "rgb(74,100,148)", 
			"E": "rgb(44,64,101)",
			"F": "rgb(242,45,166)" //pink lines
		};

		var vanishingMetalColors = {
			"X": "rgb(60,79,120)",
			"Y": "rgb(242,45,166)" //pink lines
		};

		var glassColor = "rgb(168,206,217)"; //G

		var red = "rgb(255,0,0)"; //for unknown item

		injectorData = [
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : :0:1:2:2:2:2:2:2:2:2:3: : : : : : : ",
			" : : : : :0:0:1:2:2:2:2:2:2:2:2:2:2:4:4: : : : : ",
			" : : : : :5:5:6:6:6:6:6:6:6:7:7:7:7:8:8: : : : : ",
			" : : : : :5:5:6:6:6:6:6:6:7:7:7:7:7:8:8: : : : : ",
			" : : : : : :5:5:6:6:6:6:7:7:7:7:7:8:8: : : : : : ",
			" : : : : : :5:5:6:6:6:7:7:7:7:7:7:8:8: : : : : : ",
			" : : : : : :5:5:6:6:7:7:7:7:7:7:7:8:8: : : : : : ",
			" : : : : : :5:5:6:7:7:7:7:7:7:7:7:8:8: : : : : : ",
			" : : : : : : :5:5:7:7:7:7:7:7:7:8:8: : : : : : : ",
			" : : : : : : :5:5:7:7:7:7:7:7:7:8:8: : : : : : : ",
			" : : : : : : :9:9:9:~:!:!:!:@:#:#:#: : : : : : : ",
			" : : : : : : : :B:9:B:~:!:@:B:#:B: : : : : : : : ",
			" : : : : : : :A:A:A:A:A:A:A:A:A:A:A: : : : : : : ",
			" : : : : : : : :B:B:B:B:B:B:B:B:B: : : : : : : : ",
			" : : : : : : :A:A:A:A:A:A:A:A:A:A:A: : : : : : : ",
			" : : : : : : :G:G:G:G:G:G:G:G:G:G:G: : : : : : : ",
			" : : : : : : :G:$:$:$:$:$:$:$:$:$:G: : : : : : : ",
			" : : : : : : :G:$:$:$:$:$:$:$:$:$:G: : : : : : : ",
			" : : : : : : :G:$:$:$:$:$:$:$:$:$:G: : : : : : : ",
			" : : : : : : :G:G:$:$:$:$:$:$:$:G:G: : : : : : : ",
			" : : : : : : : :G:$:$:$:$:$:$:$:G: : : : : : : : ",
			" : : : : : : : :G:$:$:$:$:$:$:$:G: : : : : : : : ",
			" : : : : : : : :G:$:$:$:$:$:$:$:G: : : : : : : : ",
			" : : : : : : : :G:$:$:$:$:$:$:$:G: : : : : : : : ",
			" : : : : : : :G:G:$:$:$:$:$:$:$:G:G: : : : : : : ",
			" : : : : : : :G:$:$:$:$:$:$:$:$:$:G: : : : : : : ",
			" : : : : : :G:G:$:$:$:$:$:$:$:$:$:G:G: : : : : : ",
			" : : : : : :G:$:$:$:$:$:$:$:$:$:$:$:G: : : : : : ",
			" : : : : : :G:$:$:$:$:$:$:$:$:$:$:$:G: : : : : : ",
			" : : : : : :G:$:$:$:$:$:$:$:$:$:$:$:G: : : : : : ",
			" : : :C: :G:G:G:G:G:G:G:X:G:G:G:G:G:G:G: :C: : : ",
			" : :C:C: :C:C:C:C:C:C:C:X:C:C:C:C:C:C:C: :C:C: : ",
			" :C:C:C: : :D:C:C:C:C:C:X:C:C:C:C:C:D: : :C:C:C: ",
			" :C:C:E:C: :C:D: :F:C:C:Y:C:C:F: :D:C: :C:E:C:C: ", //H = seed location
			"C:C:C: :C:C:C: : :C:C:F:X:C:F:C: : :C:C:C: :C:C:C",
			"E:C:C: : :C: : : : :F:C:X:F:C: : : : :C: : :C:C:E",
			"C:E:E: : : : : : : :C:C:Y:C:C: : : : : : : :E:E:C",
			"C:C:C: : : : : : : : :F:X:C: : : : : : : : :C:C:C",
			" :C:C:C: : : : : : : :C:X:F: : : : : : : :C:C:C: ",
			" :C:C:C: : : : : : : : :Y: : : : : : : : :C:C:C: ",
			" : :C:C: : : : : : : : :X: : : : : : : : :C:C: : ",
			" : : :C: : : : : : : : : : : : : : : : : :C: : : "
		];

		injectorColorData = [
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : ",
			" : : : : : : : : : : : : : : : : : : : : : : : : "
		];

		injectorData = injectorData.map(x => x.split(":"));
		injectorColorData = injectorColorData.map(x => x.split(":"));

		function injectorDataDecoder(character) {
			var element, color;
			if(injectorCrystalPinks[character]) {
				element = "glass";
				color = injectorCrystalPinks[character];
			} else if(injectorMetalColors[character]) {
				element = "steel";
				color = injectorMetalColors[character];
			} else if(vanishingMetalColors[character]) {
				element = "vanishing_steel";
				color = vanishingMetalColors[character];
			} else {
				switch(character) {
					case "$":
						element = "injector_poison";
						color = poisonPink;
						break;
					case "G":
						element = "glass";
						color = glassColor;
						break;
					case " ":
						element = "null";
						color = "null";
						break;
					default:
						element = "wall";
						color = red;
				};
			};
			return [element,color];
		};

		for(x = 0; x < injectorData.length; x++) {
			for(y = 0; y < injectorData[x].length; y++) {
				var decodedData = injectorDataDecoder(injectorData[x][y]);
				injectorData[x][y] = decodedData[0];
				injectorColorData[x][y] = decodedData[1];
			};
		};

		function spawnInjectorAt(x,y) {
			//var tries = 0;
			for(q = 0; q < injectorData.length; q++) {
				//console.log("q is: " + q);
				//tries++;
				//if(tries > 100) {
					//break;
				//};
				loadPixelRowFromArrayWithColorRowArray(injectorData[q],injectorColorData[q],x,y+q,true,true)
			};	
		};

		elements.injector_seed = {
			tick: function(pixel) {
				if(!tryMove(pixel,pixel.x,pixel.y+1)) {
					spawnInjectorAt(pixel.x,pixel.y - 34)
				};
			},
			excludeRandom: true,
			desc: '<span style="color: #ff007f">Spawns the Injector.</span>',
			cooldown: 8,
			state: "solid",
			hardness: 1,
			category: "structures",
			color: ["#586075", "#ff2b84"],
		};

	//CSS CHANGES ##

		var style2 = document.createElement('style');
		style2.type = 'text/css';
		style2.id = 'categoryStylesheet';
		//there are so many categories now that scrolling is cumbersome
		style2.innerHTML = '#categoryControls, #category-tools { white-space: normal !important; }'
		document.getElementsByTagName('head')[0].appendChild(style2);

		runAfterButtons(function() {
			//try to make the category buttons look nicer
			var cc = document.getElementById("categoryControls");
			var cccw = parseFloat(window.getComputedStyle(cc).width.match(/[\d\.]+/));
			cc.style.width = (cccw * 2).toString() + "px";
			newDiv = document.createElement("div");
			//put controls in div (for scrolling) by creating the div, placing the div after the controls, and moving the controls into the div
			cc.after(newDiv);
			newDiv.appendChild(cc);
			newDiv.style.width = cccw.toString() + "px";
			newDiv.style["overflow-x"] = "scroll";
			newDiv.style["scrollbar-color"] = "rgba(255, 255, 255, 0.25) rgba(255, 255, 255, 0.1)";
			newDiv.style["scrollbar-width"] = "thin";
			catButtons = document.getElementsByClassName("categoryButton");
			for(var i = 0; i < catButtons.length; i++) {
				var button = catButtons[i];
				var bcs = window.getComputedStyle(button);
				var oldWidth = parseFloat(bcs.width.match(/[\d\.]+/));
				var oldHeight = bcs.height;
				var oldFontSize = bcs["font-size"];
				var oldFontSizeNumber = parseFloat(oldFontSize.match(/[\d\.]+/));
				var oldFontSizeUnit = oldFontSize.match(/[A-Za-z%]+$/);
				if(oldWidth < 100) {
					button.style.width = "100px"
				} else if(oldWidth <= 115) {
					var newFontSize = ((oldFontSizeNumber * (7/8)).toString() + oldFontSizeUnit);
					button.style["font-size"] = newFontSize;
					button.style.width = "100px"
				} else if(oldWidth <= 130) {
					var newFontSize = ((oldFontSizeNumber * (3/4)).toString() + oldFontSizeUnit);
					button.style["font-size"] = newFontSize;
					button.style.width = "100px"
				} else if(oldWidth < 200) {
					button.style.width = "200px"
				} else if(oldWidth < 215) {
					var newFontSize = ((oldFontSizeNumber * (7/8)).toString() + oldFontSizeUnit);
					button.style["font-size"] = newFontSize;
					button.style.width = "200px"
				} else if(oldWidth < 250) {
					var newFontSize = ((oldFontSizeNumber * (3/4)).toString() + oldFontSizeUnit);
					button.style["font-size"] = newFontSize;
					button.style.width = "200px"
				} else if(oldWidth < 400) {
					button.style.width = "400px"
				};
				button.style.height = oldHeight;
			}
		});

	//DEBUG: PLACE ALL ELEMENTS ##

		function placeAll(limit=null,clear=true) {
			if(clear) { bareClear() };
			var elementArray = Object.keys(elements);
			if(typeof(limit) === "number") {
				elementArray = elementArray.slice(0,limit)
			};
			paused = true;
			counterLol = 0;
			loop1:
			for(i = 1; i < height; i++) {
				loop2:
				for(j = 1; j < width; j++) {
					if(isEmpty(j,i,false)) {
					  if(!outOfBounds(j,i)) {
						if(!elementArray[counterLol]) {
							break loop1;
						};
						createPixel(elementArray[counterLol],j,i);
						counterLol++;
					  } else { break loop1; };
					} else if(!isEmpty(j,i,true)) {
					  if(!outOfBounds(j,i)) {
						deletePixel(j,i)
						createPixel(elementArray[counterLol],j,i);
						counterLol++;
					  } else { break loop1; };
					}
				};
			};
			paused = true;
		};

	//BUFFED EVENTS ##

		if(urlParams.get("insaneRandomEvents") !== null) {
			if(typeof(width) === "undefined") {
				width = 20;
			}
			if(typeof(height) === "undefined") {
				height = 10;
			}

			runAfterAutogen(function() {
				//Regenerate randomChoices
				var randomChoices = Object.keys(elements).filter(function(e) {
					return elements[e].excludeRandom != true && elements[e].category != "tools" && !elements[e].tool;
				});

				//Set all event choices to randomChoices
				randomEventChoices = {
					"falling_pixel": randomChoices.filter( function(elem) { return commonMovableCriteria(elem) } ),
					"element_circle": randomChoices,
					"explosion": randomChoices,
				}

				//Buff event functions
				randomEvents.old_falling_pixel = randomEvents.falling_pixel;
					//transform falling_pixel into a rain of pixels
				randomEvents.falling_pixel = function() {
					var element = randomEventChoices.falling_pixel[Math.floor(Math.random()*randomEventChoices.falling_pixel.length)];
					for(i = 1; i < width; i++) {
						for(j = 0; j < Math.round(height * 0.35); j++) {
							if(Math.random() < 0.1 && isEmpty(i,j)) {
								createPixel(element,i,j)
							};
						};
					};
				};
				randomEvents.element_circle = function() {
					// random x between 1 and width-1
					var x = Math.floor(Math.random()*(width-1))+1;
					// random y between 1 and height-1
					var y = Math.floor(Math.random()*(height-1))+1;
					// random radius between 1 and 9
					var radius = Math.floor(Math.random()*19)+1;
					// random element from randomEventChoices.element_circle
					var element = randomEventChoices.element_circle[Math.floor(Math.random()*randomEventChoices.element_circle.length)];
					var coords = circleCoords(x,y,radius);
					for (var i = 0; i < coords.length; i++) {
						var coord = coords[i];
						if (isEmpty(coord.x,coord.y)) {
							createPixel(element,coord.x,coord.y);
						}
					}
				};
				randomEvents.explosion = function() {
					// similar but do explodeAt(x,y,radius,element)
					var x = Math.floor(Math.random()*(width-1))+1;
					var y = Math.floor(Math.random()*(height-1))+1;
					var radius = Math.floor(Math.random()*19)+1;
					var element = randomEventChoices.explosion[Math.floor(Math.random()*randomEventChoices.explosion.length)];
					explodeAt(x,y,radius,element);
				};
				randomEvents.temperature = function() {
					// set the temperature in a random circle to a random value
					var x = Math.floor(Math.random()*(width-1))+1;
					var y = Math.floor(Math.random()*(height-1))+1;
					var radius = Math.floor(Math.random()*19)+1;
					var temp = Math.floor(Math.random()*400)-273;
					var coords = circleCoords(x,y,radius);
					for (var i = 0; i < coords.length; i++) {
						var coord = coords[i];
						if (!outOfBounds(coord.x,coord.y) && !isEmpty(coord.x,coord.y)) {
							pixelMap[coord.x][coord.y].temp = temp;
						}
					}
				};

				randomEvents.paint = function() {
					// set the color of a random circle to a random color
					var x = Math.floor(Math.random()*(width-1))+1;
					var y = Math.floor(Math.random()*(height-1))+1;
					var randomR = Math.floor(Math.random() * 256);
					var randomG = Math.floor(Math.random() * 256);
					var randomB = Math.floor(Math.random() * 256);
					var radius = Math.floor(Math.random()*19)+1;
					var rColor = "rgb(" + randomR + "," + randomG + "," + randomB + ")";
					var coords = circleCoords(x,y,radius);
					for (var i = 0; i < coords.length; i++) {
						var coord = coords[i];
						if (!outOfBounds(coord.x,coord.y) && !isEmpty(coord.x,coord.y)) {
							pixelMap[coord.x][coord.y].color = rColor;
						};
					};
				};

					//Buff mob events
				if(typeof(maximumCreeperTries) !== "undefined") {
					minimumCreeperTries = 10;
					maximumCreeperTries = 30;
				};
				if(typeof(maximumZombieTries) !== "undefined") {
					minimumZombieTries = 10;
					maximumZombieTries = 30;
				};
				if(typeof(maximumSkeletonTries) !== "undefined") {
					minimumSkeletonTries = 10;
					maximumSkeletonTries = 30;
				};

				//New event option
				var eventOptions = document.querySelectorAll('span[setting="events"]')[0].children[0];
				var newEventOption = document.createElement("option");
				newEventOption.setAttribute("value","1");
				newEventOption.textContent = "Every tick (why)";
				eventOptions.appendChild(newEventOption);
			});
		};

	//EXPERIMENTAL STRICTLY DIRECTIONAL WIRE ##

		//The CMYK is symbolic
		elements.start_test = {
			color: "#dddddd",
			category: "test",
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			conduct: 1,
			tick: function(pixel) {
				if(pixel.charge) {
					for(i = 0; i < adjacentCoords.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x + adjacentCoords[i][0];
						var nY = pixel.y + adjacentCoords[i][1];

						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
					};
				};
			},
		};

		elements.end_test = {
			color: "#888888",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				if(pixel.value === 1) {
					for(i = 0; i < adjacentCoords.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x + adjacentCoords[i][0];
						var nY = pixel.y + adjacentCoords[i][1];

						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							if(newInfo.conduct) {
								//console.log(`cond ${nX} ${nY}`)
								if(!newPixel.chargeCD) {
									//console.log(`noCD ${nX} ${nY}`)
									if(Math.random() < newInfo.conduct) {
										//console.log(`rolled ${nX} ${nY}`)
										if(isNaN(newPixel.charge) || newPixel.charge <= 1) {
											//console.log(`dead ${nX} ${nY}`)
											newPixel.charge = 1
										}/* else {
											console.log(`maybe if you had stanned loona ${nX} ${nY}`)
										}*/;
									};
								};
							};
						};
					};
					pixel.value = 0;
				};
			},
		};

		elements.right_test = {
			color: "#dddd22",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffset = [1, 0];
				var pX = pixel.x;
				var pY = pixel.y;
				var nX = pixel.x+newPixelCoordOffset[0];
				var nY = pixel.y+newPixelCoordOffset[1];

				if(pixel.value === 1) {
					if(!isEmpty(nX,nY,true)) {
						var newPixel = pixelMap[nX][nY];
						var newInfo = elements[newPixel.element];
						var newCategory = newInfo.category;
						if(newCategory == elements[pixel.element].category) {
							newPixel.value = 1;
						};
					};
					pixel.value = 0;
				};
			},
		};

		elements.left_test = {
			color: "#dd22dd",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffset = [-1, 0];
				var pX = pixel.x;
				var pY = pixel.y;
				var nX = pixel.x+newPixelCoordOffset[0];
				var nY = pixel.y+newPixelCoordOffset[1];

				if(pixel.value === 1) {
					if(!isEmpty(nX,nY,true)) {
						var newPixel = pixelMap[nX][nY];
						var newInfo = elements[newPixel.element];
						var newCategory = newInfo.category;
						if(newCategory == elements[pixel.element].category) {
							newPixel.value = 1;
						};
					};
					pixel.value = 0;
				};
			},
		};

		elements.down_test = {
			color: "#222222",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffset = [0, 1];
				var pX = pixel.x;
				var pY = pixel.y;
				var nX = pixel.x+newPixelCoordOffset[0];
				var nY = pixel.y+newPixelCoordOffset[1];

				if(pixel.value === 1) {
					if(!isEmpty(nX,nY,true)) {
						var newPixel = pixelMap[nX][nY];
						var newInfo = elements[newPixel.element];
						var newCategory = newInfo.category;
						if(newCategory == elements[pixel.element].category) {
							newPixel.value = 1;
						};
					};
					pixel.value = 0;
				};
			},
		};

		elements.up_test = {
			color: "#22dddd",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffset = [0, -1];
				var pX = pixel.x;
				var pY = pixel.y;
				var nX = pixel.x+newPixelCoordOffset[0];
				var nY = pixel.y+newPixelCoordOffset[1];

				if(pixel.value === 1) {
					if(!isEmpty(nX,nY,true)) {
						var newPixel = pixelMap[nX][nY];
						var newInfo = elements[newPixel.element];
						var newCategory = newInfo.category;
						if(newCategory == elements[pixel.element].category) {
							newPixel.value = 1;
						};
					};
					pixel.value = 0;
				};
			},
		};

		elements.up_left_test = {
			color: "#2222dd",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [-1, 0]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.up_left_test = {
			color: "#2222dd",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [-1, 0]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.up_right_test = {
			color: "#22dd22",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [1, 0]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.up_down_test = {
			color: "#228888",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [0, 1]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};


		elements.left_right_test = {
			color: "#dd2222",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[-1, 0], [1, 0]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.left_down_test = {
			color: "#882288",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[-1, 0], [0, 1]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};


		elements.right_down_test = {
			color: "#888822",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[1, 0], [0, 1]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.up_left_right_test = {
			color: "#454545",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [-1, 0], [1, 0]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.left_right_down_test = {
			color: "#882222",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[-1, 0], [1, 0], [0, 1]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.up_right_down_test = {
			color: "#228822",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [1, 0], [0, 1]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

		elements.up_left_down_test = {
			color: "#222288",
			category: "test",
			properties: {
				value: 0,
				offColor: null,
				onColor: null,
			},
			behavior: behaviors.WALL,
			insulate: true,
			hardness: 1,
			tick: function(pixel) {
				if(pixel.offColor === null) {
					pixel.offColor = _rgbHexCatcher(pixel.color)
				};
				pixel.onColor = lightenColor(pixel.offColor,32,"rgb");
				pixel.color = (pixel.value > 0 ? pixel.onColor : pixel.offColor);

				var newPixelCoordOffsets = [[0, -1], [-1, 0], [0, 1]];

				if(pixel.value === 1) {
					for(i = 0; i < newPixelCoordOffsets.length; i++) {
						var pX = pixel.x;
						var pY = pixel.y;
						var nX = pixel.x+newPixelCoordOffsets[i][0];
						var nY = pixel.y+newPixelCoordOffsets[i][1];
						if(!isEmpty(nX,nY,true)) {
							var newPixel = pixelMap[nX][nY];
							var newInfo = elements[newPixel.element];
							var newCategory = newInfo.category;
							if(newCategory == elements[pixel.element].category) {
								newPixel.value = 1;
							};
						};
						pixel.value = 0;
					};
				};
			},
		};

	//RAY CLONER (like TPT CRAY) ##

		function placeRegularlySpacedPixels(element,startX,startY,xSpacing,ySpacing,overwrite=false,stopAt=null,rayIgnore=[],spawnTemp=null,limit=1000) {
			if(element.includes(",")) { element = element.split(",") };
			var newElement = element;
			if(isNaN(xSpacing) || isNaN(ySpacing)) {
				throw new Error("Missing xSpacing or ySpacing");
			};
			if(xSpacing == 0 && ySpacing == 0) {
				//pixel.color = convertColorFormats("#5F5F5F","rgb");
				return false;
			};
			if(outOfBounds(startX,startY) /*|| (!overwrite && !isEmpty(startX,startY,false))*/) {
				return false;
			};	
			var posX = startX;
			var posY = startY;
			var pixelsPlaced = 0;
			//var tries = 0;
			while(!outOfBounds(posX,posY) /*&& tries < 100*/) {
				//tries++;
				if(newElement instanceof Array) { newElement = newElement[Math.floor(Math.random() * newElement.length)] };
				if(!elements[newElement]) {
					//pixel.color = convertColorFormats("#FF5F5F","rgb");
					console.error(`Nonexistent element ${newElement}`);
					return false;
				};
				if(isEmpty(posX,posY,true)) {
					createPixel(newElement,posX,posY);
					if(spawnTemp !== null) { pixelMap[posX][posY].temp = spawnTemp };
					pixelsPlaced++;
				} else {
					if(outOfBounds(posX,posY)) {
						break;
					} else {
						if(!isEmpty(posX,posY,true)) {
							var otherElement = pixelMap[posX][posY].element;
							//console.log(tries,"tries");
							//console.log("ri",rayIgnore);
							if(rayIgnore) {
								/*console.log(
									rayIgnore instanceof Array,
									otherElement,
									otherElement == rayIgnore || rayIgnore.includes(otherElement)
								);*/
								if(rayIgnore instanceof Array) {
									if(rayIgnore.includes(otherElement)) {
										posX += xSpacing;
										posY += ySpacing;
										//console.log(posX, posY);
										continue;
									};
								} else {
									if(otherElement == rayIgnore) {
										posX += xSpacing;
										posY += ySpacing;
										//console.log(posX,posY);
										continue;
									};
								};
							};
							if(stopAt) {
								if(stopAt instanceof Array) {
									if(stopAt.includes(otherElement)) { break };
								} else {
									if(otherElement == stopAt) { break };
								};
							};
							if(overwrite) {
								changePixel(pixelMap[posX][posY],newElement)
								if(spawnTemp !== null) {
									pixelMap[posX][posY].temp = spawnTemp;
								};
								pixelsPlaced++
							} else { break };
						};
					};
				};
				posX += xSpacing;
				posY += ySpacing;
				if(limit !== null && pixelsPlaced >= limit) {
					return true;
				};
			};
			return true;
		};

		elements.ray_cloner = {
			properties: {
				xSpacing: 0,
				ySpacing: 0,
				overwrite: false,
				stopAt: null,
				rayIgnore: [],
				spawnAtPixelTemp: false,
				maxPixels: 1000,
				/*clone: "plasma",
				xSpacing: 0,
				ySpacing: 1,
				overwrite: false,
				stopAt: null,
				rayIgnoreSelf: true,
				spawnAtPixelTemp: true,
				maxPixels: 1000,*/
			},
			//temp: 100000,
			tick: function(pixel) {
				storeFirstTouchingElement(pixel,"clone",true,true);
				if(pixel.clone && pixel.charge) {
						placeRegularlySpacedPixels(
						pixel.clone,
						pixel.x+pixel.xSpacing,
						pixel.y+pixel.ySpacing,
						pixel.xSpacing,
						pixel.ySpacing,
						pixel.overwrite,
						pixel.stopAt,
						pixel.rayIgnore,
						pixel.spawnAtPixelTemp ? pixel.temp : null,
						pixel.maxPixels
					);
					pixel.charge = 0
				};
			},
			conduct: 1,
			category: "machines",
			state: "solid",
			density: 3000,
			insulate: true,
			hardness: 0.8,
			color: "#FFFF7F",
			desc: `clone: Which element to place. Cannot be an array or comma-separated string, and will be chosen from the first pixel to touch it.<br/>
xSpacing and ySpacing: Horizontal and vertical spacing between x pixels. Note that blocking is only checked at these intervals. An xSpacing of 0 with a ySpacing of 1 would give a straight line going down; xS1 and yS0 would give a line to the right.<br/>
overwrite (default false): Whether to replace pixels the line goes into.<br/>
stopAt (default null): Elements the line to stop at if it hits. Also applies to overwrite, and has no effect if set to null. Can be a string or an array.<br/>
spawnAtPixelTemp (default false): Whether to place new pixels at the same temperature as this pixel.<br/>
maxPixels (default 1000): Maximum amount of pixels/changes (if xSpacing and ySpacing are both less than 2, this corresponds to the pixel length of the line).

<em>prop or old_prop are highly recommended to set pixel properties</em>`,
		};

	//NO GAMMA RAY SPAWNERS IN RANDOM ##

		runAfterLoad(function() {
			randomBlacklist = ["quark_matter", "liquid_neutronium", "molten_neutronium", "neutronium", "neutronium_gas"];
			for(i = 0; i < randomBlacklist.length; i++) {
				var element = randomBlacklist[i];
				elements[element].excludeRandom = true;
			};
		});

	//NO RADIOACTIVE ELEMENTS IN FALLING PIXEL EVENTS ##

		window.addEventListener("load",function(){
			var rads = Object.keys(elements).filter(function(name) {
				return !!((elements[name].behavior ?? "undefined").toString().match(/C[RH]:[A-Za-z0-9_,]*radiation/))
			}).concat(["radiation"]);

			for(var i = 0; i < rads.length; i++) {
				var radElem = rads[i];
				if(randomEventChoices.falling_pixel.includes(radElem)) {
					randomEventChoices.falling_pixel.splice(randomEventChoices.falling_pixel.indexOf(radElem),1)
				}
			};	

			function editDistance(s1, s2) {s1 = s1.toLowerCase();s2 = s2.toLowerCase();var costs = new Array();for (var i = 0; i <= s1.length; i++) {var lastValue = i;for (var j = 0; j <= s2.length; j++) {if (i == 0)costs[j] = j;else {if (j > 0) {var newValue = costs[j - 1];if (s1.charAt(i - 1) != s2.charAt(j - 1))newValue = Math.min(Math.min(newValue, lastValue),costs[j]) + 1;costs[j - 1] = lastValue;lastValue = newValue;}}}if (i > 0)costs[s2.length] = lastValue;}return costs[s2.length];}

			function similarity(s1, s2) {var longer = s1;var shorter = s2;if (s1.length < s2.length) {longer = s2;shorter = s1;}var longerLength = longer.length;if (longerLength == 0) {return 1.0;}return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);}

			function mostSimilarElement(s) {
				delete elements;
				var max = 0;
				var maxElement = "";
				for (var e in elements) {
					var sim = similarity(e,s);
					if (sim > max) {
						max = sim;
						maxElement = e;
					}
					if (elements[e].alias && elements[e].alias === s) {
						max = 0.99;
						maxElement = e;
					}
				}
				if (max < 0.5) { return null }
				return maxElement;
			}
		});

	//SPECIFY CURRENT ELEMENT ON LOAD ##
	
	window.addEventListener("load",function() {
		//console.log(currentElement);
		currentElement = urlParams.get("currentElement") ?? "sand";
		//console.log(currentElement);
		if(!elementExists(currentElement)) {
			//console.log(false);
			currentElement = "sand"
		}// else { console.log(true) };
		//console.log(currentElement);
	});
	
	//MISCELLANEOUS CHANGES ##

	eLists.PIPE = ['pipe', 'destroyable_pipe', 'e_pipe', 'destroyable_e_pipe', 'channel_pipe', 'destroyable_channel_pipe', 'bridge_pipe'];

	elements.pipe_stage_shifter = {
		tool: function(pixel) {
			if(!(eLists.PIPE.includes(pixel.element))) { return false };
			if(typeof(pixel.stage) == "undefined" || !(pixel.stage) || pixel.stage < 2) { return false };
			switch (pixel.stage) {
				case 2: pixel.stage = 3; break;
				case 3: pixel.stage = 4; break;
				case 4: pixel.stage = 2; break;
				default: pixel.stage = (((Math.max(2,Math.floor(pixel.stage)) - 2) % 3) + 2);
			};
			switch (pixel.stage) {
				case 2: newColor = "#003600"; break;
				case 3: newColor = "#360000"; break;
				case 4: newColor = "#000036"; break;
			};
			pixel.color = pixelColorPick(pixel,newColor);
		},
		category: "tools",
		color: ["#003600","#360000","#000036"], 
		density: elements.steel.density,
		hardness: elements.steel.hardness,
		tempHigh: elements.steel.tempHigh,
		stateHigh: "molten_steel",
		state: "solid",
		behavior: behaviors.POWDER
	};

	var notActuallyMovable = ["pipe","e_pipe","steel","vivite"];

	runAfterLoad(function() {
		for(var i = 0; i < notActuallyMovable.length; i++) {
			var name = notActuallyMovable[i];
			Object.defineProperty(elements[name], "movable", {
				value: false,
				writable: false //**** you, you're not changing it to true.
			});		
		}
	});

	elements.unknown = {
		color: "#FFFFFF",
		behavior: behaviors.WALL,
		maxColorOffset: 0
	};
	
	
	runAfterLoad(function() {
		//Emergency bug fix
		elemfillerVar = null;
		elements.element_filler = {
			category: "special",
			color: elements.filler.color,
			state: "solid",
			movable: "false",
			onSelect: function() {
				var answer6 = prompt("Please input the desired element of this filler. It will not work if you do multiple filter types while paused.",(elemfillerVar||undefined));
				if (!answer6) { return }
				elemfillerVar = answer6;
			},
			excludeRandom: true,
			tick: function(pixel){
				if(!(elementExists(elemfillerVar))) {
					deletePixel(pixel.x,pixel.y);
					return
				};
				var neighbors = 0;
				if(!pixel.changeElem){
					pixel.changeElem = elemfillerVar;
				}
				for (var i = 0; i < squareCoords.length; i++) {
					var coord = squareCoords[i];
					var x = pixel.x+coord[0];
					var y = pixel.y+coord[1];
					if (!isEmpty(x,y, true)) {
						neighbors = neighbors + 1;
					} else if (isEmpty(x, y)){
						createPixel("element_filler", x, y)
						pixelMap[x][y].changeElem = pixel.changeElem;
					} else (
						changePixel(pixel, pixel.changeElem)
					)
				}
				if (neighbors >= 8){
					changePixel(pixel, pixel.changeElem)
				}
			}
		}

		if(elementExists("ohio")) {
			elements.ohio.excludeRandom = true
		};
		if(elementExists("rainbow_bomb")) {
			elements.rainbow_bomb.excludeRandom = true
		};
		if(elementExists("fart")) {
			elements.fart.excludeRandom = true
		};
		if(elementExists("dark_energy")) {
			elements.dark_energy.excludeRandom = true
		};
		if(elementExists("rainbow_flash")) {
			elements.rainbow_flash.excludeRandom = true;
			delete elements.rainbow_flash.reactions.fire
		};
	})

	//END ##
} catch (error) {
	alert(`Load failed (try reloading).\nThis is likely a sporadic failure caused by inconsistencies in how mods are loaded, and will likely fix itself in a refresh or two. If it persists, then it's an issue.\nError: ${error.stack}`);
	console.error(error)
};
} else {
	var nonexistentMods = dependencies.filter(function(modPath) { return !(enabledMods.includes(modPath)) });
	nonexistentMods.forEach(function(modPath) {
		enabledMods.splice(enabledMods.indexOf(modName),0,modPath);
		console.log(enabledMods.join(", "))
	});
	localStorage.setItem("enabledMods", JSON.stringify(enabledMods));
	//console.log(enabledMods.join(", "));
	alert(`Missing dependencies: ${nonexistentMods.join(", ")}
These mods have been inserted automatically. Please reload for this to take effect.
!!! Dependencies may randomly fail to load, or fail to run in the wrong order.
!!! This has nothing to do with the needed mods themselves, and it is something I cannot fix.
!!! If the game fails to load, try refreshing (repeating as needed).
If this message itself persists, something is wrong.`)
}
