2025-04-02 20:19:02 -04:00
// 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 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
2025-04-04 10:14:23 -04:00
if (Array.isArray(elements[key].reactions[reaction].elem1)) {
2025-04-02 20:19:02 -04:00
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; }
}
2025-04-04 10:14:23 -04:00
}
Version 1.13.2 - January 11, 2026
+ Edit Mode
~ Moved Special tools to the edit tab
~ Expect more tools in the future
+ Relations - Multiple pixels as a single entity
+ Group and Ungroup tool in edit tab
+ Grouped pixels fall and move as one structure
+ Grouped pixels are ungrouped when broken or changing states
+ Grouped pixels can displace fluids (Experimental)
+ More Shift-Select functionality:
+ Battery can set a frequency to create timers
+ Image tool can set element
~ Fire begins more yellow and gets redder quicker
+ Icing (Hidden) made with Butter and Sugar
~ Liquids and Gases appear less grainy
~ Settings changes
+ Speed option in Settings to replace TPS button
+ Keybind: T to change TPS
~ Renamed E button to Elem button
~ Improved simulation speeds
- Removed World Gen
~ We recommend the PRNGworldgenlib.js mod, a much better world generator
- Removed Cheerful Mode
[Changes]
+ Escape can be pressed to cancel while drawing a line
+ Fillers can be painted and will fight other colors
+ Portals show destination lines when hovered over or selected
+ Portal In can be selected with the Elem button by typing 'Portal'
+ Lightning placed near certain elements will change color
+ Worms can break down Flour, Dough, Adobe, Paper, Confetti, Cellulose, Cheese, and Potato
+ Worms can move through Mulch
+ Radiation will radiate Batter
+ Rad Steam causes Cheese to rot
~ Filters and Gates are Acid-resistant
~ Portals no longer teleport Fuse
~ Ruler measures diagonal lines as a pixel count instead of hypotenuse
~ Unique flame colors for Nickel, Iron, and Mercury
~ Rearranged Special category
~ Opening the Info, Mods, or Settings menus will pause the game
~ Steam: All canvas sizes now stretch to the full screen width
+ Assigned densities to many solids
+ Keybind: Quote (") to toggle Edit Mode
[Bug Fixes]
~ Fixed: Dragging pixels through Filters creates ghost pixels
~ Fixed: Filters can break when transporting to Gates or Pipes
~ Fixed: Pixels are rendered in incorrect position when mixing with Pipe, Filter, or Gate
~ Fixed: Broken modded saves are loaded every bootup
~ Fixed: Precision Dot is misaligned with 2- and 4-wide brushes
~ Fixed: Portals can't transmit electricity if surrounded by pixels
~ Fixed: E-cloner can't be set to Fuse by clicking on it
~ Fixed: Some saves throw TypeError "Cannot set properties of undefined"
~ Fixed: Prop tool always unpauses after dialog
~ Fixed: Z and Backspace keys select Heat by default instead of Sand
~ Fixed: Random can't place Fire, Plasma, Ink, Bless, Rock Wall, Cold Fire, Sun, Pointer, or Art
~ Fixed: Elem dialog doesn't close if button is pressed again
~ Fixed: Wrong font when language set to Italian
~ Fixed: Magnesium melts at a much lower temperature than expected
~ Fixed: Shift-selecting Random and inputting 'random' causes errors
[Technical]
~ Note: Optimizations mean a lot of code was rewritten, please report any issues and be patient
~ Note: Don't pass an 'rgba' color to drawSquare, use the 'alpha' argument instead
+ Pixel relations system
+ Used to group pixels together
+ '_r' pixel property to identify its relation (should be an integer)
+ 'trackPaint' element property (boolean)
+ 'painted' pixel property (color code) given to these elements when painted
+ "Failed to load" message instead of hanging forever
~ 'con' contents only render if element has existing 'canContain' property
~ 'onMouseDown' element event only triggers with left clicks
~ Acid Gas 'ignore' property is now identical to Acid
~ Elements with a set density but no state are set to solid
~ Fuse no longer shows hidden element
~ Save version is now sb7
2026-01-11 13:52:44 -05:00
let xstart = x-1;
let ystart = y-1;
let xsize = pixelSize3;
let ysize = pixelSize3;
if (!isEmpty(x-1, y)) {
xstart ++;
xsize -= pixelSize;
}
if (!isEmpty(x+1, y)) {
xsize -= pixelSize;
}
if (!isEmpty(x, y-1)) {
ystart ++;
ysize -= pixelSize;
}
if (!isEmpty(x, y+1)) {
ysize -= pixelSize;
}
ctx.fillRect(canvasCoord(xstart), canvasCoord(y), xsize, pixelSize);
ctx.fillRect(canvasCoord(x), canvasCoord(ystart), pixelSize, ysize);
if (Math.random() < 0.5) {
let trapped = true;
for (let i = 0; i < adjacentCoords.length; i++) {
const coord = adjacentCoords[i];
const x = pixel.x + coord[0];
const y = pixel.y + coord[1];
if (!(y > height-currentSaveData.border || y < currentSaveData.border || x > width-currentSaveData.border || x < currentSaveData.border) &&
(pixelMap[x][y] === undefined ||
elements[pixelMap[x][y].element].id !== info.id)
) {
trapped = false;
break;
}
}
if (trapped === true) {
pixel.opti = pixelTicks;
return false;
}
}
function worldGen(worldtype) {
var complexity = worldtype.complexity || 20;
var heightVariance = worldtype.heightVariance || 0.5;
var baseHeight = height-(height*(worldtype.baseHeight || 0.5));
var layers = worldtype.layers || {0:"rock"};
var yoffsets = generateTerrainHeights(width,heightVariance,complexity);
// 2D world vertical generator
for (var x = 0; x <= width; x++) {
var yoffset = yoffsets[x];
var worldHeight = baseHeight+yoffset;
for (var y = 0; y <= height; y++) {
// Change element type based on y, from grass > dirt > rock > basalt
if (y > worldHeight) {
// distance from the bottom of worldHeight
var frombottom = worldHeight-(y-worldHeight);
var element = null;
for (var i in layers) {
var layer = layers[i];
if (layer[0] == 0 && yoffset < 0) {
layer[0] = yoffset;
}
if (frombottom > worldHeight*layer[0] && Math.random() < (layer[2] || 1)) {
if (elements[layer[1]]) {
element = layer[1];
break
}
}
}
if (y >= height && (currentSaveData.voidY || currentSaveData.loopY)) {
element = currentSaveData.borderElem ? null : "wall";
}
if ((x >= width || x === 0) && (currentSaveData.voidX || currentSaveData.loopX)) {
element = currentSaveData.borderElem ? null : "wall";
}
if (element) {
createPixel(element,x,y);
if (worldtype.temperature) {
pixelMap[x][y].temp = worldtype.temperature;
}
}
}
}
}
// decor
if (worldtype.decor) {
for (var i = 0; i < worldtype.decor.length; i++) {
var decor = worldtype.decor[i];
var element = decor[0];
var chance = decor[1];
for (var x = 1; x < width; x++) {
var y = decor[2] || 5;
// add or subtract worldtype.decorVariance from y
y += Math.round(Math.random()*(worldtype.decorVariance||2) - (worldtype.decorVariance||2)/2);
if (Math.random() < chance && isEmpty(x,y)) {
createPixel(element,x,y);
if (worldtype.temperature) {
pixelMap[x][y].temp = worldtype.temperature;
}
if (decor[3]) {
pixelMap[x][y].color = pixelColorPick(pixelMap[x][y],decor[3])
}
}
}
}
}
}
// 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
for (var key in worldgentypes) {
document.getElementById("worldgenselect").innerHTML += "<option value='" + key + "'>" + key.replace(/_/g, " ").replace(/\b\w/g, l => l.toUpperCase()) + "</option>";
}
if (settings["worldgen"] && !worldgentypes[settings["worldgen"]]) {
settings["worldgen"] = "off";
}