diff --git a/mod-list.html b/mod-list.html
index 0141dd98..1a600cc9 100644
--- a/mod-list.html
+++ b/mod-list.html
@@ -246,6 +246,7 @@
| conveyance.js | Conveyors, operated with and without electricity | Melecie |
| drill.js | Drills made out of several materials | Suss |
| ExtraMachines.js | Sensors, energy resources, materials, and more | Mecoolnotcool |
+| fans.js | Fans | Cube14yt |
| fine_tuned_cloner.js | Cloner that can spawn at different rates and prevent unwanted cloning | BatteRaquette58 |
| flipflop.js | Toggleable switches [More Info] | Flix |
| fueled_generators.js | Fuel powered generators | guzzo86 |
@@ -356,6 +357,7 @@
| bfdi.js | Several references to Battle for Dream Island | Taterbob |
| citybuilding.js | Seeds that create miniature buildings and other city-related items | SquareScreamYT |
| collab_mod.js | Created by multiple people, adds random things | mrapple, ilikepizza, stefanblox |
+| cubesstuff.js | Some random stuff like disco ball, pyrite, and nordic gold. | Cube14yt |
| doom.js | As seen on TikTok - Select the Doom element to start [WASD to move] | ggod |
| elem3.js | All elements and combinations from Elemental 3 [Very Large] | Sophie |
| explosionsound.js | Sound effects for explosions | nousernamefound |
diff --git a/mods/cubesstuff.js b/mods/cubesstuff.js
new file mode 100644
index 00000000..0421591c
--- /dev/null
+++ b/mods/cubesstuff.js
@@ -0,0 +1,161 @@
+//broken rn dont know how to fix it yet
+/*
+elements.button = {
+ color: "#970000",
+ conduct: 1,
+ charge: 0,
+ category: "machines",
+ onSelect: function () {
+ logMessage("Click the button with no elements equipped to charge the button.")
+ },
+ properties: {
+ clicked: false,
+ clickTime: 1,
+ },
+ onClicked: function (pixel) {
+ pixel.clicked = true
+ pixel.clickTime = 1
+ },
+ tick: function (pixel) {
+ if (pixel.clicked == true && pixel.clickTime > 0) {
+ pixel.charge = 1
+ pixel.clickTime--
+ }
+ else if (pixel.clicked == true && pixel.clickTime <= 0) {
+ pixel.clicked = false
+ pixel.charge = 0
+ }
+ }
+}
+*/
+elements.aerogel = {
+ color: "#79ffff",
+ category: "solids",
+ behavior: behaviors.WALL,
+ state: "solid",
+ tempHigh: 1200,
+ stateHigh: "ash",
+ insulate: true,
+ density: 0.2,
+ hardness: 0.1,
+ breakInto: "dust",
+ onPlace: function (pixel) {
+ if (pixel.alpha === undefined) { pixel.alpha = Math.random() * (0.5 - 0.4) + 0.4 }
+ }
+}
+
+let oldCopperReactions = elements.copper.reactions
+elements.molten_copper.reactions.molten_aluminum = { elem1: "molten_nordic_gold", elem2: null, chance: 0.5 }
+elements.acid.ignore.push("nordic_gold")
+elements.acid.ignore.push("nordic_gold_coin")
+
+elements.nordic_gold = {
+ color: ["#f1db7c", "#e5c34b", "#d2a742", "#b98c31", "#a47320"],
+ tempHigh: 1060,
+ behavior: behaviors.WALL,
+ category: "solids",
+ state: "solid",
+ density: 8800,
+ conduct: 0.90,
+ breakInto: "nordic_gold_coin"
+}
+
+elements.nordic_gold_coin = {
+ color: ["#f1db7c", "#e5c34b", "#d2a742", "#b98c31", "#a47320"],
+ tempHigh: 1060,
+ stateHigh: "molten_nordic_gold",
+ behavior: behaviors.POWDER,
+ category: "powders",
+ state: "solid",
+ density: 8800,
+ conduct: 0.90,
+ alias: "euro_coin",
+ reactions: {
+ "glue": { elem1: "nordic_gold", elem2: null }
+ }
+}
+
+function randomColor() {
+ const letters = "0123456789ABCDEF";
+ let color = "#";
+ for (let i = 0; i < 6; i++) {
+ color += letters[Math.floor(Math.random() * 16)];
+ }
+ return color;
+}
+
+elements.disco_ball = {
+ color: "#ebebc3",
+ buttonColor: ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff"],
+ renderer: renderPresets.LED,
+ behavior: behaviors.WALL,
+ category: "machines",
+ tempHigh: 1500,
+ stateHigh: ["molten_glass", "molten_glass", "molten_copper"],
+ conduct: 1,
+ breakInto: "glass_shard",
+ forceSaveColor: true,
+ 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 (pixel.charge > 0) {
+ pixel.color = randomColor()
+ if (isEmpty(x, y)) {
+ createPixel("light", x, y)
+ let p = getPixel(x, y)
+ if (p !== null && p.element == "light") {
+ p.color = pixel.color
+ }
+ }
+ }
+ else { pixel.color = "#ebebc3" }
+ }
+ }
+}
+
+elements.molten_iron.reactions.sulfur = { elem1: "pyrite", elem2: null, chance: 0.25 }
+elements.molten_iron.reactions.molten_sulfur = { elem1: "pyrite", elem2: null, chance: 0.25 }
+elements.molten_iron.reactions.sulfur_gas = { elem1: "pyrite", elem2: null, chance: 0.25 }
+
+elements.pyrite = {
+ color: ["#d8c25e", "#bbaa49", "#998f3e"],
+ alias: ["fools_gold", "Iron Disulfide"],
+ density: 5000,
+ tempHigh: 1177,
+ stateHigh: ["iron", "molten_sulfur"],
+ grain: 0.4,
+ state: "solid",
+ behavior: behaviors.WALL,
+ category: "solids"
+}
+
+elements.fire_extinguisher_powder = {
+ color: "#ececec",
+ behavior: [
+ "XX|XX|XX",
+ "XX|DL%1|XX",
+ "M2%30|M1%30|M2%30"
+ ],
+ extinguish: true,
+ tick: function (pixel) {
+ for (var i = 0; i < adjacentCoords.length; i++) {
+ var coords = adjacentCoords[i];
+ var x = pixel.x + coords[0];
+ var y = pixel.y + coords[1];
+ if (getPixel(x, y)?.burning === true) {
+ let elem = getPixel(x, y)
+ elem.burning = false
+ }
+ }
+ },
+ tool: function(pixel) {
+ if(pixel.burning === true){
+ delete pixel.burning;
+ delete pixel.burnStart;
+ }
+ },
+ canPlace: true,
+ category: "powders"
+}
\ No newline at end of file
diff --git a/mods/fans.js b/mods/fans.js
new file mode 100644
index 00000000..a3bf07ad
--- /dev/null
+++ b/mods/fans.js
@@ -0,0 +1,211 @@
+function getSelfMovingBehaviorFunctionNames() {
+ return Object.entries(behaviors)
+ .filter(([name, func]) => {
+ if (typeof func !== "function") return false;
+ if (["SEEDRISE"].includes(name)) return false;
+
+ const code = func.toString();
+
+ // Only allow if it's moving its own pixel
+ const selfMove = /movePixel\s*\(\s*pixel\s*,/.test(code) || /tryMove\s*\(\s*pixel\s*,/.test(code)
+ || /pixel\.(x|y)\s*[\+\-]=/.test(code);
+
+ return selfMove;
+ })
+ .map(([name]) => name);
+}
+
+const builtInMovementBehaviors = [
+ "M1", "M2","BO", "SP", "XX|M1", "M1|M2", "M1%", "M2%", "M1%|M2",
+ "M1|M2%", "M1%|M2%", 'XX|M1%', 'M1%|XX', 'XX|M2%', 'M2%|XX'
+];
+
+function behaviorIncludesMovement(behaviorMatrix, movementFunctionNames) {
+ if (!Array.isArray(behaviorMatrix)) return false;
+
+ for (const row of behaviorMatrix) {
+ if (!Array.isArray(row)) continue;
+
+ for (const cell of row) {
+ if (typeof cell !== "string") continue;
+
+ // Handle "AND" logic: multiple behaviors in one cell
+ const andParts = cell.split("AND");
+
+ for (const andPart of andParts) {
+ const parts = andPart.trim().split("|").map(p => p.trim());
+ if (parts.some(p => builtInMovementBehaviors.includes(p) || movementFunctionNames.includes(p))) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+runAfterAutogen(function () {
+ const movementFunctionNames = getSelfMovingBehaviorFunctionNames();
+
+ const movableElements = Object.entries(elements).filter(([name, elem]) => {
+ const behavior = elem.behavior;
+ const tick = elem.tick || elem.tickFunc;
+
+ let movesSelf = false;
+
+ // Check behavior matrix
+ if (Array.isArray(behavior)) {
+ movesSelf = behaviorIncludesMovement(behavior, movementFunctionNames);
+ }
+
+ // Check single string
+ else if (typeof behavior === "string") {
+ const parts = behavior.split("|").map(p => p.trim());
+ movesSelf = parts.some(p => builtInMovementBehaviors.includes(p) || movementFunctionNames.includes(p));
+ }
+
+ // Check function-type behavior
+ else if (typeof behavior === "function") {
+ movesSelf = movementFunctionNames.includes(behavior.name);
+ }
+
+ // Check tick function
+ if (!movesSelf && typeof tick === "function") {
+ const code = tick.toString();
+ if (/movePixel\s*\(\s*pixel\s*,/.test(code) || /tryMove\s*\(\s*pixel\s*,/.test(code) || /pixel\.(x|y)\s*[\+\-]=/.test(code)) {
+ movesSelf = true;
+ }
+ }
+
+ return movesSelf;
+ }).map(([name]) => name);
+
+ console.log("🚶 Self-Moving Elements:", movableElements);
+ window.movableElementsByBehavior = movableElements;
+});
+
+
+
+
+
+
+runAfterAutogen(function () {
+ console.log(behaviors)
+})
+
+// Create a global map to track delay for each position
+if (!window.fanPushDelays) {
+ window.fanPushDelays = new Map();
+}
+
+elements.fan_right = {
+ behavior: behaviors.WALL,
+ color: "#c5c5c5",
+ tick: function (pixel) {
+ const fan_strength = 10;
+ const delay_ticks = 2;
+
+ for (let i = 1; i <= fan_strength; i++) {
+ const x = pixel.x + i;
+ const y = pixel.y;
+
+ // SKIP if position is empty
+ if (isEmpty(x, y)) continue;
+
+ const delem = pixelMap[x]?.[y];
+ if (!delem) continue;
+
+ // Skip non-movable elements
+ if (!window.movableElementsByBehavior.includes(delem.element)) continue;
+
+ // Use position key for delay tracking
+ const key = `${x},${y}`;
+ const currentDelay = window.fanPushDelays.get(key) || 0;
+
+ if (currentDelay >= delay_ticks) {
+ window.fanPushDelays.set(key, 0);
+
+ const newX = x + 1;
+ if (isEmpty(newX, y)) {
+ movePixel(delem, newX, y);
+ }
+ } else {
+ window.fanPushDelays.set(key, currentDelay + 1);
+ }
+ }
+ },
+ category: "machines"
+};
+
+elements.fan_left = {
+ behavior: behaviors.WALL,
+ color: "#c5c5c5",
+ tick: function (pixel) {
+ const fan_strength = 10;
+ const delay_ticks = 2;
+
+ for (let i = 0; i >= -fan_strength; i--) {
+ const x = pixel.x + i;
+ const y = pixel.y;
+
+ // SKIP if position is empty
+ if (isEmpty(x, y)) continue;
+
+ const delem = pixelMap[x]?.[y];
+ if (!delem) continue;
+
+ // Skip non-movable elements
+ if (!window.movableElementsByBehavior.includes(delem.element)) continue;
+
+ // Use position key for delay tracking
+ const key = `${x},${y}`;
+ const currentDelay = window.fanPushDelays.get(key) || 0;
+
+ if (currentDelay >= delay_ticks) {
+ window.fanPushDelays.set(key, 0);
+
+ const newX = x - 1;
+ if (isEmpty(newX, y)) {
+ movePixel(delem, newX, y);
+ }
+ } else {
+ window.fanPushDelays.set(key, currentDelay + 1);
+ }
+ }
+ },
+ category: "machines"
+};
+/*
+elements.fan_up = {
+ behavior: behaviors.WALL,
+ tick: function (pixel) {
+ let fan_strength = 10;
+ let delay_ticks = 0; // delay between pushes per pixel row
+ if (!pixel._fan_delay) pixel._fan_delay = 0;
+
+ if (pixel._fan_delay > 0) {
+ pixel._fan_delay--;
+ return;
+ }
+
+ for (let i = 1; i <= fan_strength; i++) {
+ const tx = pixel.x;
+ const ty = pixel.y - i;
+
+ if (!isEmpty(tx, ty)) {
+ const delem = pixelMap[tx]?.[ty];
+
+ if (!delem || !window.movableElementsByBehavior.includes(delem.element)) break;
+
+ const above = ty - 1;
+ if (isEmpty(tx, above)) {
+ movePixel(delem, tx, above);
+ pixel._fan_delay = delay_ticks;
+ break; // only move one per tick
+ }
+ }
+ }
+ }
+};
+*/