diff --git a/CITATION.cff b/CITATION.cff
index c87d93c7..b6cae29e 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -8,11 +8,35 @@ message: >-
metadata from this file.
type: software
authors:
- - given-names: R74n
- email: contact@r74n.com
- affiliation: R74n
+ - name: R74n
+ email: contact@R74n.com
+ date-start: '2017-05-02'
+ website: 'https://r74n.com/'
+identifiers:
+ - type: doi
+ value: 10.17605/OSF.IO/H7TDY
+ - type: swh
+ value: 'swh:1:rev:5a5813a8f4f418540f1bdb765d293735541bf7fd'
+ - type: url
+ value: 'https://sandboxels.r74n.com/'
+ - type: doi
+ value: 10.5281/zenodo.10044909
+ - type: other
+ value: /g/11spmybz10
+ description: KGMID
+ - type: url
+ value: 'https://purl.org/r74n/sandboxels'
+ description: PURL
+ - type: url
+ value: 'https://w3id.org/r74n/sandboxels'
+ description: W3ID
repository-code: 'https://github.com/R74nCom/sandboxels'
url: 'https://sandboxels.r74n.com'
+abstract: >-
+ Sandboxels is a free falling-sand simulator that can be
+ played in your browser. It features heat simulation,
+ electricity, density, chemical reactions, cooking, and
+ fire spread.
keywords:
- R74n
- Sandboxels
diff --git a/mod-list.html b/mod-list.html
index 4242543b..1312c8fc 100644
--- a/mod-list.html
+++ b/mod-list.html
@@ -115,13 +115,13 @@
| Tools & Settings |
| adjustablepixelsize.js | Allows you to set the pixelSize with a URL parameter | Alice |
+| betaworldgen.js | adds a more advanced world generation to the game | Alex |
| betterModManager.js | Improvements to the Mod Manager | ggod |
| betterSettings.js | Adds additional settings and functionality | ggod |
| betterStats.js | Separate “real” and “set” TPS, meaning you can see what the TPS actually is, instead of only seeing what it’s set to | mollthecoder |
| change.js | Adds a tool that only replaces existing pixels | Alice |
| color_tools.js | Adds tools that manipulate colors | Alice |
| controllable_pixel_test.js | Adds a pixel that can be controlled with the keyboard keys. Read the commit description for more info. [PC ONLY] | Alice |
-| customexplosion.js | Added a custom explosion element and interface for it. check out its source code for how modders can use it. | Alex |
| cpt_alt.js | Adds a more destructive variant of the controllable pixel | Alice |
| debugRework.js | Revamps the Debug tool | Fioushemastor |
| delete_all_of_element.js | Adds a tool that deletes every pixel of the element(s) the user clicks on | Alice |
@@ -261,6 +261,7 @@
| changeTempReactionParameter.js | Adds the changeTemp property to modded reactions | Alice |
| code_library.js | Adds functions and variables common to some other mods | Alice |
| CrashTestDummy.js | Originally a test to see if certain code broke the game, but now just adds a tool that turns things into sand | StellarX20 |
+| customexplosion.js | Added a custom explosion element and interface for it. check out its source code for how modders can use it. | Alex |
| date_test.js | K-pop idol birthday testing stuff | Alice |
| drawPixels_change_test.js | A test of altering drawPixels(). Gives burning pixels a red overlay similar to the yellow overlay for charged pixels | Alice |
| example_mod.js | An example mod for new modders | R74n |
diff --git a/mods/betaworldgen.js b/mods/betaworldgen.js
new file mode 100644
index 00000000..9c25dd1f
--- /dev/null
+++ b/mods/betaworldgen.js
@@ -0,0 +1,128 @@
+//This mod was made by Alex the transfem, https://discord.com/users/778753696804765696 on discord and https://www.tiktok.com/@alextheagenenby?_t=8hoCVI3NRhu&_r=1 on tiktok.
+function randomAlter(num, list){
+ let r = Math.floor(Math.random() * list.length);
+ return (num + list[r]);
+}
+let avgheight = 0;
+let seed = "";
+function getSeed(type = "plains", thickness = 15){
+ seed = "";
+ console.log(thickness)
+ if(thickness == 15){
+ avgheight = Math.floor(Math.random() * (18 - 12 + 1)) + 12;
+ } else{
+ avgheight = thickness;
+ }
+ console.log(avgheight)
+
+ if(type == "plains"){
+ let location = Math.floor(Math.random(0, pixelMap.length) * 100);
+ let i = 0;
+ while (i < pixelMap.length){
+ if (i !== location){
+ seed += `${randomAlter(avgheight, [0, 1, 1, 2, 0, 0])}|`;
+ i += 1;
+ } else if (i == location){
+ let height = `${Math.floor(Math.random(40, pixelMap[i].length) * 10)}`;
+ let prevH = randomAlter(avgheight, [1, 1, 1, 2, 0, 0]);
+ while (height > prevH){
+ prevH = randomAlter(prevH, [0, 1, 1, 2, 0, 0, 0, 1]);
+ seed += `${prevH}|`;
+ }
+
+ i += 1;
+
+ }
+ }
+ return seed;
+ }
+ if(type == "desert"){
+ let i = 0;
+ while (i < pixelMap.length){
+ seed += `${randomAlter(avgheight, [0, 1, 1, 2, 0, 0])}|`;
+ i += 1;
+
+ }
+ }
+ return seed;
+ }
+function spawnElements(seed, list, height2 = 1, condition = [1, 1, 0]){
+ console.log(list);
+ let width = pixelMap.length - 1;
+ let element;
+ let height = pixelMap[1].length - 1;
+ console.log(seed);
+ let seedArray = seed.split("|");
+ console.log(seedArray);
+ seedArray.splice(seedArray.indexOf(""), 1);
+ seedArray.splice(pixelMap.length);
+ console.log(seedArray);
+ let i = 0;
+ while (i < seedArray.length - 1){
+
+ let ii = 0;
+ while (ii < seedArray[i]){
+ if((Math.floor(Math.random() * (condition[0] - condition[1] + 1))) == condition[2]){
+ if (height2 != 1){
+ element = list[Math.floor(Math.random() * list.length)];
+ createPixel(element, (width - 1) - i, (height - (height2 + 1)) - ii);
+ } else{
+ element = list[Math.floor(Math.random() * list.length)];
+ createPixel(element, (width - 1) - i, (height - 1) - ii);
+ }
+ }
+ ii += 1;
+ }
+
+ i += 1;
+ }
+}
+
+function flat(){
+ let iii = 0;
+ let flat = "";
+ while (iii < pixelMap.length){
+ flat += "1|";
+ iii += 1;
+ }
+ return flat;
+}
+function processSeed(seed, type = "plains"){
+
+ console.log(flat());
+ let seedsArray = seed.split(":");
+ console.log(seedsArray);
+ if(type == "plains"){
+ spawnElements(seedsArray[0],["rock","rock","rock","rock","rock","rock","metal_scrap","metal_scrap","metal_scrap","gold_coin","uranium","uranium","diamond","rock","iron","iron","iron","aluminum","aluminum","aluminum","aluminum","copper","copper","copper","zinc","zinc","rock","rock","rock","rock","rock","rock","rock","rock","rock","rock","rock","rock","rock","rock","rock"].sort(() => Math.random() - 0.5));
+ setTimeout(function(){ spawnElements(seedsArray[1], ["dirt"], 24); }, 200);
+ setTimeout(function(){ spawnElements(flat(),["grass","grass","grass","sapling","flower_seed","grass","grass","pinecone","grass","grass","grass","grass","grass","grass","grass","grass","grass","grass"], 40); }, 300);
+ } else if(type == "desert"){
+ spawnElements(seed, ["sand"]);
+ setTimeout(function(){ spawnElements(flat(), ["cactus"], 40, [6, 0, 3]); }, 100);
+ }
+}
+elements.worldGen = {
+ color: "#FFFFFF",
+ behavior: elements.erase.behavior,
+ temp: 2,
+ category: "tools",
+ insulate:true,
+ canPlace: false,
+ desc: "Generate worlds with random seeds or your own seeds.",
+ onSelect: function() {
+ let Seed = prompt("Enter desert or plains random generation! automatically set to plains.");
+ let regex = /[a-z]/;
+ if (regex.test(Seed)){
+ if(Seed.toLowerCase() == "desert"){
+ processSeed(getSeed("desert", 30), "desert");
+ }
+ } else {
+ if (Seed == ""){
+ seed = `${getSeed("plains", 20)}:${getSeed("plains", 8)}`
+ processSeed(seed);
+ } else{
+ processSeed(Seed);
+ }
+ }
+ }
+}
diff --git a/mods/code_library.js b/mods/code_library.js
index 6035ca3a..0afacccf 100644
--- a/mods/code_library.js
+++ b/mods/code_library.js
@@ -1,7 +1,15 @@
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//URL
urlParams = new URLSearchParams(window.location.search);
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Objects
//getKeyByValue code by SO UncleLaz: https://stackoverflow.com/questions/9907419/how-to-get-a-key-in-a-javascript-object-by-its-value
@@ -10,6 +18,10 @@
return Object.keys(object).find(key => object[key] === value);
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//RNG
//Random integer from 0 to n
@@ -77,6 +89,10 @@
return Math.floor(randomFunction() * (max - min + 1)) + min
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Arrays
//Shallow array comparer by SO Tim Down: https://stackoverflow.com/a/10260204
@@ -158,6 +174,10 @@
};
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Checks
//Element exists in the elements object
@@ -260,6 +280,10 @@
return false;
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Math(s)
//Base n logarithm from https://stackoverflow.com/a/3019290
@@ -305,6 +329,10 @@
return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Color
function rgbStringToUnvalidatedObject(string) { //turns rgb() to {r,g,b} with no bounds checking
@@ -1182,7 +1210,11 @@
//console.log(`Hexed to #${f(0)}${f(8)}${f(4)}`)
return `#${f(0)}${f(8)}${f(4)}`;
};
-
+
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Pixels
function exposedToAir(pixel) {
@@ -1540,6 +1572,10 @@
return true;
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//World
function breakCircle(x,y,radius,respectHardness=false,changeTemp=false,defaultBreakIntoDust=false) {
@@ -1639,6 +1675,10 @@
return true;
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Logic
function xor(c1,c2) {
@@ -1651,6 +1691,10 @@
};
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//currentPixels operations
function findInCurrentPixels(x,y) {
@@ -1710,6 +1754,10 @@
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Sugar functions
function newElement(name="element_name",color="#FF00FF",otherProps={}) {
@@ -1722,6 +1770,10 @@
return elements[name];
};
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
//Fixes
//fix -1-caused ghost pixels
@@ -1747,4 +1799,16 @@
}
}
}*/
+ };
+
+libraryLoaded = true;
+this.libraryLoaded = true;
+window.libraryLoaded = true;
+
+runAfterLoad(function() {
+ if(!libraryLoaded) {
+ libraryLoaded = true;
+ this.libraryLoaded = true;
+ window.libraryLoaded = true
}
+})
diff --git a/mods/heatglow.js b/mods/heatglow.js
index d9b6e269..2fb1a58b 100644
--- a/mods/heatglow.js
+++ b/mods/heatglow.js
@@ -1,8 +1,9 @@
const heatfunc = function(pixel){
- if (pixel.ogR == null || pixel.ogG == null || pixel.ogB == null){
+ if (pixel.ogR == null || pixel.ogG == null || pixel.ogB == null || !(pixel.element == pixel.ogElement)){
pixel.ogR = parseInt(pixel.color.slice(4, pixel.color.indexOf(',')), 10)
pixel.ogG = parseInt(pixel.color.slice(pixel.color.indexOf(',') + 1, pixel.color.lastIndexOf(',')), 10)
pixel.ogB = parseInt(pixel.color.slice(pixel.color.lastIndexOf(',') + 1, -1), 10)
+ pixel.ogElement = pixel.element
}else{
pixel.gethigh = (elements[pixel.element].tempHigh)
pixel.halftemp = ((20+pixel.gethigh)/2)
@@ -24,7 +25,7 @@ const heatfunc = function(pixel){
}
};
if (!eLists.metals) { eLists.metals = [] }
- eLists.metals = eLists.metals.concat(["iron", "glass", "copper", "gold", "brass","steel","nickel","zinc","silver","aluminum","bronze","metal_scrap","oxidized_copper","tin","lead"])
+ eLists.metals = eLists.metals.concat(["iron", "glass", "copper", "gold", "brass","steel","nickel","zinc","silver","aluminum","bronze","metal_scrap","oxidized_copper","tin","lead", "rose_gold"])
eLists.metals.forEach(metal => {
const prefunc = elements[metal].tick;
if (!prefunc){
diff --git a/mods/nousersthings.js b/mods/nousersthings.js
index 883ebdb1..4f2d2288 100644
--- a/mods/nousersthings.js
+++ b/mods/nousersthings.js
@@ -1390,4 +1390,111 @@ elements.blackhole_storage = {
},
movable: false,
conduct: 1,
+},
+elements.plutonium = {
+ color: ["#616161", "#4b4949", "#353232", "#211c1c"],
+ behavior: behaviors.STURDYPOWDER,
+ category: "powders",
+ tempHigh: 640,
+ stateHigh: "molten_plutonium",
+ state: "solid",
+ tick: function(pixel){
+ if (Math.random() < 0.0007) {
+ changePixel(pixel, "neutron", false);
+ } else if (Math.random() < 0.0007) {
+ changePixel(pixel, "uranium", false);
+ }
+ },
+ reactions: {
+ "neutron": { elem1:"pn_explosion", tempMin:400, chance:0.1 },
+ "neutron": { temp1: 100, temp2: 100 },
+ },
+ density: 19186,
+}
+elements.molten_plutonium = {
+ color: ["#6b5133", "#743f26", "#7c2727"],
+ behavior: behaviors.LIQUID,
+ category: "states",
+ state: "liquid",
+ tempLow: 620,
+ stateLow: "plutonium",
+ tick: function(pixel){
+ if (Math.random() < 0.0007) {
+ changePixel(pixel, "neutron", false);
+ } else if (Math.random() < 0.0007) {
+ changePixel(pixel, "uranium", false);
+ }
+ },
+ reactions: {
+ "neutron": { elem1:"pn_explosion", tempMin:400, chance:0.1 },
+ },
+ density: 16629,
+},
+elements.neutron.reactions = {
+ "uranium": { temp2:100 },
+ "plutonium": { temp2: 100 }
+},
+elements.pn_explosion = {
+ color: ["#ffb48f","#ffd991","#ffad91"],
+ behavior: [
+ "XX|XX|XX",
+ "XX|EX:80>plasma,plasma,plasma,plasma,radiation,rad_steam,neutron|XX",
+ "XX|XX|XX",
+ ],
+ temp: 100000000,
+ category: "energy",
+ state: "gas",
+ density: 1000,
+ excludeRandom: true,
+ hidden: true,
+ alias: "plutonium nuclear explosion",
+ noMix: true
+},
+elements.smasher = {
+ color: "#606060",
+ behavior: behaviors.WALL,
+ category: "machines",
+ tick: function(pixel){
+ 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)) {
+ var otherPixel = pixelMap[x][y];
+ breakPixel(otherPixel);
+ }
+ }
+ },
+ movable: false,
+},
+elements.mixer = {
+ color: "#F0F0F0",
+ behavior: behaviors.WALL,
+ category: "machines",
+ tick: function(pixel){
+ pixel.mixList = [];
+ 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)) {
+ var otherPixel = pixelMap[x][y];
+ pixel.mixList.push(otherPixel);
+ }
+ }
+ for (var i = 0; i < pixel.mixList.length; i++) {
+ var pixel1 = pixel.mixList[Math.floor(Math.random()*pixel.mixList.length)];
+ var pixel2 = pixel.mixList[Math.floor(Math.random()*pixel.mixList.length)];
+ swapPixels(pixel1,pixel2);
+ pixel.mixList.splice(pixel.mixList.indexOf(pixel1),1);
+ pixel.mixList.splice(pixel.mixList.indexOf(pixel2),1);
+ if (elements[pixel1.element].onMix) {
+ elements[pixel1.element].onMix(pixel1,pixel2);
+ }
+ if (elements[pixel2.element].onMix) {
+ elements[pixel2.element].onMix(pixel2,pixel1);
+ }
+ }
+ },
+ movable: false,
}
diff --git a/mods/precisionDrawing.js b/mods/precisionDrawing.js
new file mode 100644
index 00000000..5dbd5929
--- /dev/null
+++ b/mods/precisionDrawing.js
@@ -0,0 +1,58 @@
+let precisionDrawing = false
+//mod made by feeshmaster
+
+//try out debugRework.js or any other mods made by me!
+document.addEventListener("keydown", (e) => {
+ if (e.key == "w" && precisionDrawing) {
+ mousePos.y -= 1
+ } else if (e.key == "a" && precisionDrawing) {
+ mousePos.x -= 1
+ } else if (e.key == "s" && precisionDrawing) {
+ mousePos.y += 1
+ } else if (e.key == "d" && precisionDrawing) {
+ mousePos.x += 1
+ } else if (event.key === 'X' && event.shiftKey) {
+ console.log("precisionMode deactivated!")
+ precisionDrawing = !precisionDrawing
+ if (!precisionDrawing) {
+ mousePos = {x: "not", y: "updated"}
+ }
+ }
+})
+
+setTimeout(setUpOnStart, 1000)
+function setUpOnStart() {
+
+ getMousePos = (canvas, evt) => {
+ if (precisionDrawing) {
+ return mousePos;
+ }
+ // If evt.touches is defined, use the first touch
+ if (evt.touches) {
+ evt.preventDefault();
+ evt = evt.touches[0];
+ isMobile = true;
+ }
+ var rect = canvas.getBoundingClientRect();
+ return {
+ // Round to nearest pixel
+ x: Math.round((evt.clientX - rect.left)/pixelSize-0.5),
+ y: Math.round((evt.clientY - rect.top)/pixelSize-0.5)
+ };
+ }
+
+let controlsTable = document.getElementById("controlsTable");
+let row1 = document.createElement("tr");
+row1.innerHTML = `Mod controls | `;
+let row2 = document.createElement("tr");
+row2.innerHTML = `start precision | Shift + X or toggle button(not released) | `;
+let row3 = document.createElement("tr");
+row3.innerHTML = `move in precision | W, A, S, D/arrows or buttons(not released) | `;
+
+ // Append the rows
+ controlsTable.appendChild(row1);
+ controlsTable.appendChild(row2);
+ controlsTable.appendChild(row3);
+
+
+}
diff --git a/mods/rays.js b/mods/rays.js
index f862df47..0b039664 100644
--- a/mods/rays.js
+++ b/mods/rays.js
@@ -22,7 +22,7 @@ var runAfterAutogenMod = "mods/runAfterAutogen2.js";
var libraryMod = "mods/code_library.js";
if(enabledMods.includes(runAfterAutogenMod) && enabledMods.includes(libraryMod)) {
-whenAvailable(["runAfterAutogen","libraryLoaded"], function() {
+whenAvailable(["raaLoaded","libraryLoaded"], function() {
runAfterAutogen(function() {
snowAndIceCache = Object.keys(elements).filter(function(name) {
return name.endsWith("snow") || name.endsWith("ice") || name == "rime"
diff --git a/mods/runAfterAutogen2.js b/mods/runAfterAutogen2.js
index 8ea63f87..8d3ea839 100644
--- a/mods/runAfterAutogen2.js
+++ b/mods/runAfterAutogen2.js
@@ -51,4 +51,6 @@ function createButtonsAndCountElements() {
}, 10);
};
-runAfterAutogen(createButtonsAndCountElements);
\ No newline at end of file
+runAfterAutogen(createButtonsAndCountElements);
+
+raaLoaded = true;