From 10f1aa0aad887d299e79b0635d89a0456af755ba Mon Sep 17 00:00:00 2001 From: Cube14yt Date: Fri, 19 Sep 2025 18:24:54 +0800 Subject: [PATCH] Update cubesstuff.js --- mods/cubesstuff.js | 1084 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 974 insertions(+), 110 deletions(-) diff --git a/mods/cubesstuff.js b/mods/cubesstuff.js index baea58e7..891e747e 100644 --- a/mods/cubesstuff.js +++ b/mods/cubesstuff.js @@ -1,11 +1,27 @@ -// TypeScript integration for Sandboxels modding -// Enables function autocomplete & element definition hints -/// -// Get the file here: https://github.com/Cube14yt/sandboxels-types -// Changelog -// Starts at version 3 +/* +Use intellisense for sandboxels modding here: + to show availavle functions and show global variables + https://github.com/Cube14yt/sandboxels-types +*/ + +// Changelog /* +V1 +Solids: Aerogel, Nordic Gold, Nordic Gold coin, Pyrite +Machines: Disco Ball +Extras: Fire Extinguisher Powder + +V2 +Machines: Button, Disco Floor, Faulty Wire, Randomizer, +Tools: Press, Circle +Solids: Cardboard, Obsidian +Powders: Glow Stick, Obsidian Shard +Liquids: Glow Stick Liquid, Lighter fluid, Gasoline +Life: Mold, Moss, +Gases: Lighter Fluid Gas +Food: Pie, Pie Crust + V3 Tools: RGB LED, Dice, Custom Bomb Life: Pineapple Plants (seed, stem, fruit) @@ -33,8 +49,32 @@ Tools: Polish Extras: 2 ways to make an element with no name Special: Black hole Building Materials: Roman concrete/cement -*/ +V5 +Machines: Random Teleporter +Life: Carrot seed and fruit +Minerals: Quartz and Quartz powder +Special: Random Word Generator, Fill all, +Element fill all, Pulsing Color, Element line, White hole + +Changes: + Moved sandstone to land category + +Technical: + New helpers for squareCoords and adjacentCoords + Rainbow color preset +*/ +let globals = { + circleRad: 7, + circleElem: "wood", + red: randomIntInRange(0, 255), + green: randomIntInRange(0, 255), + blue: randomIntInRange(0, 255), + explodeElem: "fire", + adjusted_heater_temp: 100 +} + +const rainbowColor = ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff"] elements.button = { color: "#970000", @@ -129,7 +169,7 @@ function randomColor() { elements.disco_ball = { color: "#ebebc3", - buttonColor: ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff"], + buttonColor: rainbowColor, renderer: renderPresets.LED, behavior: behaviors.WALL, category: "machines", @@ -139,6 +179,9 @@ elements.disco_ball = { breakInto: "glass_shard", forceSaveColor: true, tick: function (pixel) { + if (!pixel.charge) { + pixel.charge = 0 + } for (var i = 0; i < squareCoords.length; i++) { var coord = squareCoords[i]; var x = pixel.x + coord[0]; @@ -279,7 +322,7 @@ elements.molten_sulfur.tick = function (pixel) { var y = pixel.y + coords[1]; if (isEmpty(x, y) && Math.random() <= 0.0005) { createPixel("stench", x, y) - p = getPixel(x, y) + let p = getPixel(x, y) if (p !== null && p.element == "stench") { p.temp = pixel.temp } @@ -288,7 +331,7 @@ elements.molten_sulfur.tick = function (pixel) { } elements.disco_floor = { - color: ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff"], + color: rainbowColor, breakInto: "glass_shard", category: "machines", forceSaveColor: true, @@ -444,6 +487,9 @@ elements.press = { tryMove(pixel, pixel.x + x, pixel.y + y) } } + if (elements[pixel.element].onPress !== undefined) { + elements[pixel.element].onPress(pixel) + } } } @@ -605,7 +651,7 @@ addRowWhenReady(); elements.randomizer = { - buttonColor: ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff"], + buttonColor: rainbowColor, excludeRandom: true, onSelect: function () { logMessage("Warning: It can fill up the screen with random elements") @@ -618,7 +664,7 @@ elements.randomizer = { var y = pixel.y + coords[1]; let p = getPixel(x, y) if (!isEmpty(x, y) && !outOfBounds(x, y)) { - if (p.element !== "randomizer") { + if (p && p.element !== "randomizer") { changePixel(p, "random") } } @@ -667,10 +713,6 @@ function drawCircle(x0, y0, radius, element) { } } - -let circleRad = 7; -let circle_element = "wood"; - elements.circle = { color: "#ffffff", behavior: behaviors.WALL, @@ -682,20 +724,20 @@ elements.circle = { function (input1) { let ans1 = Number(input1) if (Number.isInteger(ans1) && ans1 > 0) { - circleRad = ans1 + globals.circleRad = ans1 } else { - circleRad = 7 - logMessage("Invalid radius, using default size: " + circleRad); + globals.circleRad = 7 + logMessage("Invalid radius, using default size: " + globals.circleRad); } promptInput( "Select the element you want your circle to be:", function (ans2) { let similar = mostSimilarElement(ans2); if (similar && elements[similar]) { - circle_element = similar; + globals.circleElem = similar; } else { - circle_element = "wood" - logMessage("Invalid element, using default element: " + circle_element); + globals.circleElem = "wood" + logMessage("Invalid element, using default element: " + globals.circleElem); } }, "Element prompt", @@ -707,29 +749,35 @@ elements.circle = { ); }, onPlace: function (pixel) { - drawCircle(pixel.x, pixel.y, circleRad, circle_element); - changePixel(pixel, circle_element); - pixel.temp = (elements[circle_element].temp || 20) + drawCircle(pixel.x, pixel.y, globals.circleRad, globals.circleElem); + changePixel(pixel, globals.circleElem); + pixel.temp = (elements[globals.circleElem].temp || 20) }, maxSize: 1, excludeRandom: true }; runAfterReset(function () { - circleRad = 7; - circle_element = "wood"; + if (globals.rCircle) { + globals.circleRad = 7; + globals.circleElem = "wood"; + } }) +/** + * gets a random int in a range + * @param {number} min + * @param {number} max + * @returns {number} + */ function randomIntInRange(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } -let r = randomIntInRange(0, 255); -let g = randomIntInRange(0, 255); -let b = randomIntInRange(0, 255); + elements.rgb_led = { - buttonColor: ["#ff0000", "#ff8800", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff"], + buttonColor: rainbowColor, behavior: behaviors.WALL, desc: "Input the red, green, and blue value (not exceeding 255) and get the color.", renderer: renderPresets.LED, @@ -751,7 +799,7 @@ elements.rgb_led = { if (r_inp > 255 || r_inp < 0 || isNaN(r_inp)) { logMessage("Red value is invalid, using default/last red value: " + r); } else { - r = r_inp; + globals.red = r_inp; } promptInput("Enter green value (0-255):", function (g_inp) { @@ -759,7 +807,7 @@ elements.rgb_led = { if (g_inp > 255 || g_inp < 0 || isNaN(g_inp)) { logMessage("Green value is invalid, using default/last green value: " + g); } else { - g = g_inp; + globals.green = g_inp; } promptInput("Enter blue value (0-255):", function (b_inp) { @@ -767,26 +815,29 @@ elements.rgb_led = { if (b_inp > 255 || b_inp < 0 || isNaN(b_inp)) { logMessage("Blue value is invalid, using default/last blue value: " + b); } else { - b = b_inp; + globals.blue = b_inp; } - }, "Blue Value", b); // optional default input - }, "Green Value", g); - }, "Red Value", r); + }, "Blue Value", globals.blue); // optional default input + }, "Green Value", globals.green); + }, "Red Value", globals.red); }, onPlace: (pixel) => { - var ledColor = RGBToHex({ r: r, g: g, b: b }); + var ledColor = RGBToHex([red, green, blue]); pixel.color = ledColor; } }; runAfterReset(() => { - r = 100; - g = 100; - b = 100; + if (globals.rRGBLed) { + globals.red = 100; + globals.greed = 100; + globals.blue = 100; + } }) +if (!elements.malware.reactions) { elements.malware.reactions = {} } elements.malware.reactions.rgb_led = { elem2: ["led_r", "led_g", "led_b"], chance: 0.01 } elements.malware.reactions.led_r = { elem2: ["rgb_led", "led_g", "led_b"], chance: 0.01 } elements.malware.reactions.led_g = { elem2: ["rgb_led", "led_r", "led_b"], chance: 0.01 } @@ -1132,7 +1183,7 @@ elements.sandstone = { state: "solid", stateHigh: "glass", tempHigh: 1700, - category: "solids" + category: "land" } // Glow.js integrtion @@ -1237,7 +1288,6 @@ elements.realistic_ball = { } } -explodeElem = "fire" elements.custom_bomb = { color: "#49443b", category: "weapons", @@ -1250,16 +1300,15 @@ elements.custom_bomb = { promptInput( "Input the element you want your bomb to explode into", function (input) { - pr1 = mostSimilarElement(input) - if (elements[pr1]) { + let pr1 = mostSimilarElement(input) + if (pr1 && elements[pr1]) { if (pr1 === "custom_bomb") { - explodeElem = 'fire' - logMessage("Element cannot explode to itself. Using default: fire") + promptConfirm("Are you sure you want custom bomb to create itself as it will spread", (a) => { if (!a) { globals.explodeElem = 'fire' } else globals.explodeElem = 'custom_bomb' }, "Warning") } - else { explodeElem = pr1 } + else { globals.explodeElem = pr1 } } else { - explodeElem = 'fire' + globals.explodeElem = 'fire' logMessage("Invalid element. Using default: fire") } }, @@ -1279,7 +1328,9 @@ elements.custom_bomb = { } runAfterReset(() => { - explodeElem = 'fire' + if (globals.rCustomBomb) { + globals.explodeElem = 'fire' + } }) elements.pineapple = { @@ -1417,7 +1468,6 @@ elements.rubidium = { "sugar_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "pool_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "dirty_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, - "salt_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "seltzer": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "primordial_soup": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "nut_milk": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 } @@ -1441,7 +1491,6 @@ elements.liquid_rubidium = { "sugar_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "pool_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "dirty_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, - "salt_water": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "seltzer": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "primordial_soup": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 }, "nut_milk": { elem1: ["explosion", "explosion", "hydrogen"], temp2: 200 } @@ -1618,6 +1667,7 @@ elements.robot_body = { }; // Robot creator element +var mode = "Aimless" elements.robot = { color: "#b1b1b1", category: "machines", @@ -1671,7 +1721,7 @@ elements.mercury_gas.behaviorOn = [ "M2|CR:uv_light%10 AND M1|M2" ] -adjusted_heater_temp = 100 + elements.broken_adjustable_heater = { color: "#ff0000", category: "extras", @@ -1683,15 +1733,15 @@ elements.broken_adjustable_heater = { "Select the temperature you want to adjust to", function (choice) { if (choice && !isNaN(Number(choice))) { - adjusted_heater_temp = choice + globals.adjusted_heater_temp = Number(choice) logMessage("Occasionally creates superheated pixels") } }, - "Temperature Prompt", adjusted_heater_temp + "Temperature Prompt", `${globals.adjusted_heater_temp}` ) }, tick(pixel) { - pixel.heat_temp ??= adjusted_heater_temp + pixel.heat_temp ??= globals.adjusted_heater_temp for (let i = 0; i < adjacentCoords.length; i++) { let x = pixel.x + adjacentCoords[i][0]; let y = pixel.y + adjacentCoords[i][1]; @@ -1708,8 +1758,8 @@ elements.broken_adjustable_heater = { } }; -adjusted_temp = 100 -heatAmount = 2 +globals.adjusted_temp = 100 +globals.heatAmount = 2 elements.adjustable_heater = { color: "#ff0000", @@ -1722,10 +1772,10 @@ elements.adjustable_heater = { "Select the temperature you want to adjust to", function (choice) { if (choice && !isNaN(Number(choice))) { - adjusted_temp = Number(choice); + globals.adjusted_temp = Number(choice); } }, - "Temperature Prompt", adjusted_temp + "Temperature Prompt", `${globals.adjusted_temp}` ); }, @@ -1740,10 +1790,10 @@ elements.adjustable_heater = { !elements[current_pixel.element]?.insulate ) { // Heat or cool toward the adjusted temp - if (current_pixel.temp < adjusted_temp) { - current_pixel.temp = Math.min(current_pixel.temp + heatAmount, adjusted_temp); - } else if (current_pixel.temp > adjusted_temp) { - current_pixel.temp = Math.max(current_pixel.temp - heatAmount, adjusted_temp); + if (current_pixel.temp < globals.adjusted_temp) { + current_pixel.temp = Math.min(current_pixel.temp + globals.heatAmount, globals.adjusted_temp); + } else if (current_pixel.temp > globals.adjusted_temp) { + current_pixel.temp = Math.max(current_pixel.temp - globals.heatAmount, globals.adjusted_temp); } pixelTempCheck(current_pixel) } @@ -1751,7 +1801,7 @@ elements.adjustable_heater = { } }; -adjusted_cooler_temp = 0; // default cooling target +globals.adjusted_cooler_temp = 0; // default cooling target elements.broken_adjustable_cooler = { color: "#0000ff", @@ -1764,16 +1814,16 @@ elements.broken_adjustable_cooler = { "Select the temperature you want to cool to", function (choice) { if (choice && !isNaN(Number(choice))) { - adjusted_cooler_temp = Number(choice); + globals.adjusted_cooler_temp = Number(choice); logMessage("Occasionally creates supercooled pixels"); } }, - "Temperature Prompt", adjusted_cooler_temp + "Temperature Prompt", `${globals.adjusted_cooler_temp}` ); }, tick(pixel) { - pixel.cool_temp ??= adjusted_cooler_temp; + pixel.cool_temp ??= globals.adjusted_cooler_temp; for (let i = 0; i < adjacentCoords.length; i++) { let x = pixel.x + adjacentCoords[i][0]; let y = pixel.y + adjacentCoords[i][1]; @@ -1791,8 +1841,8 @@ elements.broken_adjustable_cooler = { } }; -adjusted_cool_temp = 0; // default cooling target -coolAmount = 2; // adjustable step +globals.adjusted_cool_temp = 0; // default cooling target +globals.coolAmount = 2; // adjustable step elements.adjustable_cooler = { color: "#0000ff", @@ -1805,10 +1855,10 @@ elements.adjustable_cooler = { "Select the temperature you want to cool to", function (choice) { if (choice && !isNaN(Number(choice))) { - adjusted_cool_temp = Number(choice); + globals.adjusted_cool_temp = Number(choice); } }, - "Temperature Prompt", adjusted_cool_temp + "Temperature Prompt", `${globals.adjusted_cool_temp}` ); }, @@ -1823,10 +1873,10 @@ elements.adjustable_cooler = { !elements[current_pixel.element]?.insulate ) { // Cool or heat toward target (mirrors fixed heater logic) - if (current_pixel.temp > adjusted_cool_temp) { - current_pixel.temp = Math.max(current_pixel.temp - coolAmount, adjusted_cool_temp); - } else if (current_pixel.temp < adjusted_cool_temp) { - current_pixel.temp = Math.min(current_pixel.temp + coolAmount, adjusted_cool_temp); + if (current_pixel.temp > globals.adjusted_cool_temp) { + current_pixel.temp = Math.max(current_pixel.temp - globals.coolAmount, globals.adjusted_cool_temp); + } else if (current_pixel.temp < globals.adjusted_cool_temp) { + current_pixel.temp = Math.min(current_pixel.temp + globals.coolAmount, globals.adjusted_cool_temp); } pixelTempCheck(current_pixel) @@ -1836,20 +1886,21 @@ elements.adjustable_cooler = { }; -let polishedList = new Set() +globals.polishedList = new Set() elements.polish = { + desc: "Polishes textured elements", category: "tools", color: ["#a0dff0", "#c0e8f8", "#e0f5ff"], tool(pixel) { let element = pixel.element - if ((elements[pixel.element].colorPattern && !polishedList.has(`${pixel.x}, ${pixel.y}`)) || shiftDown) { + if ((elements[pixel.element].colorPattern && !globals.polishedList.has(`${pixel.x}, ${pixel.y}`)) || shiftDown) { deletePixel(pixel.x, pixel.y) createPixel(element, pixel.x, pixel.y) - polishedList.add(`${pixel.x}, ${pixel.y}`) + globals.polishedList.add(`${pixel.x}, ${pixel.y}`) } }, onUnselect() { - polishedList.clear() + globals.polishedList.clear() } } @@ -1890,8 +1941,9 @@ elements.paper_filter = { let liquid = upPixel.con let viscMove = true - if (elements[liquid.element].viscosity) { - viscMove = (Math.random() * 100) < (100 / Math.pow(elements[liquid.element].viscosity, 0.5)) + let visc = elements[liquid.element].viscosity + if (visc) { + viscMove = (Math.random() * 100) < (100 / Math.pow(visc, 0.5)) } if (viscMove) { @@ -1904,8 +1956,9 @@ elements.paper_filter = { let liquid = pixel.con let viscExit = true - if (elements[liquid.element].viscosity) { - viscExit = (Math.random() * 100) < (100 / Math.pow(elements[liquid.element].viscosity, 0.5)) + let visc = elements[liquid.element].viscosity + if (visc) { + viscExit = (Math.random() * 100) < (100 / Math.pow(visc, 0.5)) } if (viscExit) { @@ -1936,8 +1989,9 @@ elements.indestructable_filter = { let liquid = upPixel.con let viscMove = true - if (elements[liquid.element].viscosity) { - viscMove = (Math.random() * 100) < (100 / Math.pow(elements[liquid.element].viscosity, 0.5)) + let visc = elements[liquid.element].viscosity + if (visc) { + viscMove = (Math.random() * 100) < (100 / Math.pow(visc, 0.5)) } if (viscMove) { @@ -1950,8 +2004,9 @@ elements.indestructable_filter = { let liquid = pixel.con let viscExit = true - if (elements[liquid.element].viscosity) { - viscExit = (Math.random() * 100) < (100 / Math.pow(elements[liquid.element].viscosity, 0.5)) + let visc = elements[liquid.element].viscosity + if (visc) { + viscExit = (Math.random() * 100) < (100 / Math.pow(visc, 0.5)) } if (viscExit) { @@ -1962,7 +2017,7 @@ elements.indestructable_filter = { } } -let black_hole_expand = false +globals.glass_hole_expand = false elements.black_hole = { color: "#111111", hardness: 1, @@ -2050,7 +2105,7 @@ elements.black_hole = { } } } - if (black_hole_expand) { + if (globals.glass_hole_expand) { for (var i = 0; i < adjacentCoords.length; i++) { var x = pixel.x + adjacentCoords[i][0]; var y = pixel.y + adjacentCoords[i][1]; @@ -2069,19 +2124,92 @@ elements.black_hole = { ["Yes", "No"], (choice) => { if (!choice) { - choice = false + choice = "No" } if (choice == "Yes") { - black_hole_expand = true + globals.glass_hole_expand = true } else { - black_hole_expand = false + globals.glass_hole_expand = false } } ) } }; +elements.white_hole = { + color: "#ffffff", + renderer: function (pixel, ctx) { + if (!viewInfo[view].colorEffects) { drawDefault(ctx, pixel); return } + renderPresets.HEATGLOW(pixel, ctx); + if (pixel.alpha === 0) return; + let edge = false; + 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 (isEmpty(x, y) || (!outOfBounds(x, y) && elements[pixelMap[x][y].element].movable !== elements[pixel.element].movable)) { + edge = true; + break; + } + } + if (edge) { drawSquare(ctx, "rgba(0,0,0," + (pixel.alpha || 1) / 4 + ")", pixel.x, pixel.y) } + }, + tick(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) && !outOfBounds(x, y) && Math.random() <= 0.0005) { + createPixel( + ["proton", "neutron", "electric", + "proton", "neutron", "electric", + "proton", "neutron", "electric", + "proton", "neutron", "electric", + "random"], x, y) + } + } + let radius = 20; + for (let dx = -radius; dx <= radius; dx++) { + for (let dy = -radius; dy <= radius; dy++) { + if (dx === 0 && dy === 0) continue; + + let x = pixel.x + dx; + let y = pixel.y + dy; + + if (!outOfBounds(x, y)) { + let other = getPixel(x, y); + if (other && other !== pixel) { + let elemDef = elements[other.element]; + + if (elemDef.hardness === 1 && !other.element === "white_hole") continue; + + let dist = Math.sqrt(dx * dx + dy * dy); + + if (dist <= radius) { + let chance = 1 / (dist * 0.75); + if (Math.random() < chance) { + let stepX = Math.sign(pixel.x - x); + let stepY = Math.sign(pixel.y - y); + + let newX = x - stepX; + let newY = y - stepY; + + if (isEmpty(newX, newY) && !outOfBounds(newX, newY)) { + movePixel(other, newX, newY); + } + } + } + } + } + } + } + }, + hardness: 1, + behavior: behaviors.WALL, + category: "special" +} + elements.cacao_fruit = { color: "#854700", behavior: [ @@ -2133,7 +2261,6 @@ elements.dried_cacao_bean = { elements.coffee_bean.reactions.sugar_water = { elem2: "coffee", tempMin: 80 } elements.coffee.reactions.sugar_water = { elem2: "coffee", tempMin: 70, chance: 0.2 } elements.coffee_ground.reactions.sugar_water = elements.coffee_ground.reactions.water - elements._ = { category: "extras", onSelect() { @@ -2196,7 +2323,9 @@ elements.cacao_stem = { if (pixel.stage === 1 && isEmpty(pixel.x, pixel.y - 1) && Math.random() <= 0.05) { tryMove(pixel, pixel.x, pixel.y - 1, "cacao_stem") let oldPixel = getPixel(pixel.x, pixel.y + 1) - delete oldPixel.stage + if (oldPixel) { + delete oldPixel.stage + } if (Math.random() <= 0.3) { pixel.stage = 2 } @@ -2213,11 +2342,11 @@ elements.cacao_stem = { else nx = 0; if (isEmpty(pixel.x + nx, pixel.y - 1) && Math.random() <= 0.05) { createPixel(["cacao_stem", "plant"], pixel.x + nx, pixel.y - 1) - newPixel = getPixel(pixel.x + nx, pixel.y - 1) - if (Math.random() <= 0.2) { + let newPixel = getPixel(pixel.x + nx, pixel.y - 1) + if (Math.random() <= 0.2 && newPixel) { newPixel.stage = 3 } - else newPixel.stage = 2; + else if (newPixel) newPixel.stage = 2; } if (!isEmpty(pixel.x + 1, pixel.y - 1) && !isEmpty(pixel.x, pixel.y - 1) && !isEmpty(pixel.x - 1, pixel.y - 1) && Math.random() <= 0.005) { shuffleArray(adjacentCoordsShuffle) @@ -2318,21 +2447,21 @@ const pianoFrequencies = { }; -let note = 261.626; // default C4 -let notesToPlay = []; +globals.note = 261.626; // default C4 +globals.notesToPlay = []; function flushNotes() { - if (notesToPlay.length === 0) return; + if (globals.notesToPlay.length === 0) return; let baseVolume = 0.2; - let volume = baseVolume / Math.sqrt(notesToPlay.length); + let volume = baseVolume / Math.sqrt(globals.notesToPlay.length); - for (let f of notesToPlay) { + for (let f of globals.notesToPlay) { playNote(f, 1, "sine", volume); } - notesToPlay = []; + globals.notesToPlay = []; } elements.note_block = { @@ -2343,25 +2472,25 @@ elements.note_block = { "Select the note this note block should be", function (choice) { if (!choice) { - if (!note) { note = 261.626; } + if (!globals.note) { globals.note = 261.626; } return; } let key = choice.toUpperCase(); if (key in pianoFrequencies) { - note = pianoFrequencies[key]; + globals.note = pianoFrequencies[key]; } else { - note = 261.626; // fallback = C4 + globals.note = 261.626; // fallback = C4 } }, "Note prompt" ); }, onPlace(pixel) { - pixel.note = note; + pixel.note = globals.note; }, tick(pixel) { if (pixel.charge) { - notesToPlay.push(Number(pixel.note)); + globals.notesToPlay.push(Number(pixel.note)); } }, conduct: 1, @@ -2458,4 +2587,739 @@ function doWaterReactions(element, reaction) { elements[element].reactions.nut_milk = reaction } -doWaterReactions("slaked_lime", {elem1:"roman_cement", elem2: null, chance: 0.25}) +doWaterReactions("slaked_lime", { elem1: "roman_cement", elem2: null, chance: 0.25 }) + +globals.cachedWords = []; +globals.otherCachedWords = []; +globals.filteredCachedWords = []; +globals.loadingLogged = false; + + +async function getWordList() { + if (globals.cachedWords) return globals.cachedWords; + try { + const response = await fetch("https://raw.githubusercontent.com/first20hours/google-10000-english/refs/heads/master/google-10000-english-no-swears.txt"); + if (!response.ok) throw new Error('Network error'); + const text = await response.text(); + globals.cachedWords = text.split("\n"); + return globals.cachedWords; + } catch (err) { + console.error(err); + return []; + } +} + + +async function getCleanWordList() { + if (globals.otherCachedWords) return globals.otherCachedWords; + try { + const response = await fetch("https://raw.githubusercontent.com/dwyl/english-words/refs/heads/master/words.txt"); + if (!response.ok) throw new Error('Network error'); + const text = await response.text(); + globals.otherCachedWords = text.split("\n"); + return globals.otherCachedWords; + } catch (err) { + console.error(err); + return []; + } +} + +async function filterWordList() { + if (!globals.cachedWords) await getWordList(); + if (!globals.otherCachedWords) await getCleanWordList(); + + if (globals.filteredCachedWords) return globals.filteredCachedWords; + + const cleanSet = new Set(globals.otherCachedWords); + globals.filteredCachedWords = []; + + const chunkSize = 1000; + for (let i = 0; i < globals.cachedWords.length; i += chunkSize) { + const chunk = globals.cachedWords.slice(i, i + chunkSize); + globals.filteredCachedWords.push(...chunk.filter(word => cleanSet.has(word))); + await new Promise(requestAnimationFrame); + } + console.log("Finished filtering") + return globals.filteredCachedWords; +} + +filterWordList() + +// getting myself familiar with async functions +elements.random_word_generator = { + color: "#ffffff", + behavior: behaviors.WALL, + desc: "When shocked logs a random word", + conduct: 1, + category: "special", + tick: function (pixel) { + pixel.cd ??= 0; + + if (pixel.charge === 1 && pixel.cd <= 0) { + if (!globals.filteredCachedWords && !globals.loadingLogged) { + logMessage("Loading..."); + globals.loadingLogged = true; + } + + const words = globals.filteredCachedWords ?? [] + + if (words.length > 0) { + const word = words[Math.floor(Math.random() * words.length)]; + logMessage("Your word is: " + word); + pixel.cd = 10; + } else { + logMessage("Failed to load words."); + } + } else if (pixel.cd > 0) { + pixel.cd--; + } + } +}; + +globals.paint_color = "#ff0000"; + +elements.paint_using_hex = { + color: elements.paint.color, + onSelect() { + promptInput( + "Select the color you want in hex. If you pass an array, make sure each hex value is wrapped with quotes.", + (choice) => { + if (!choice) return; + + const regex = /^#([a-fA-F0-9]{6})$/; + + if (regex.test(choice)) { + globals.paint_color = choice; + return; + } + + try { + const converted = choice.replace(/'/g, '"') + const parsed = JSON.parse(converted); + + if (Array.isArray(parsed)) { + let valid = true; + for (let value of parsed) { + if (!regex.test(value)) { + valid = false; + break; + } + } + if (valid) { + globals.paint_color = parsed; + return; + } + } + } catch (e) { + logMessage("Parse failed") + console.log(e) + } + + logMessage("Invalid color or array of colors"); + console.log(`${choice}`) + } + ); + }, + tool(pixel) { + if (!shiftDown) { + pixel.color = pixelColorPick(pixel, globals.paint_color); + } + else if (Array.isArray(globals.paint_color)) { + pixel.color = choose(globals.paint_color) + } + else { + pixel.color = globals.paint_color + } + }, + category: "tools" +}; + +globals.rCircle = false +globals.rRGBLed = false +globals.rCustomBomb = false +dependOn("betterSettings.js", () => { + var Reset = new SettingsTab("Reset"); + var resetCircle = new Setting("Reset circle value and radius on reset", "Reset circle", settingType.BOOLEAN, false, defaultValue = false); + var resetRGBLed = new Setting("Reset RGB Led value on reset", "Reset RGB Led", settingType.BOOLEAN, false, defaultValue = false); + var resetCustomBomb = new Setting("Reset Custom Bomb value on reset", "Reset Custom Bomb", settingType.BOOLEAN, false, defaultValue = false); + Reset.registerSettings("Reset", resetRGBLed) + Reset.registerSettings("Reset", resetCircle) + Reset.registerSettings("Reset", resetCustomBomb) + settingsManager.registerTab(Reset); + runEveryTick(() => { + if (resetCircle.value == true) { + globals.rCircle = true + } else globals.rCircle = false + if (resetCustomBomb.value == true) { + globals.rCustomBomb = true + } else globals.rCustomBomb = false + if (resetRGBLed.value == true) { + globals.rRGBLed = true + } else globals.rRGBLed = false + }) +}, true) + +elements.quartz = { + color: ["#f0f0f0", "#ebebeb", "#f8f8f8"], + grain: 0, + conduct: 0.3, + behavior: behaviors.WALL, + onBreak(pixel) { + if (Math.random() <= 0.2) { + pixel.charge = 1 + } + }, + onPress(pixel) { + if (Math.random() <= 0.2) { + pixel.charge = 1 + } + }, + density: 2650, + hardness: 0.7, + breakInto: "quartz_powder", + category: "solids" +} + +elements.quartz_powder = { + color: ["#dddddd", "#e0e0e0", "#f0f0f0"], + grain: 0, + conduct: 0.3, + behavior: behaviors.POWDER, + onBreak(pixel) { + if (Math.random() <= 0.2) { + pixel.charge = 1 + } + }, + density: 2650, + hardness: 0.7, + category: "powders" +} + +elements.carrot_seed = { + color: '#925420', + behavior: behaviors.STURDYPOWDER, + cooldown: defaultCooldown, + category: "life", + tempHigh: 400, + stateHigh: "fire", + tempLow: -2, + stateLow: "frozen_plant", + burn: 50, + burnTime: 20, + state: "solid", + tick(pixel) { + let belowPixel = getPixel(pixel.x, pixel.y + 1) + if (belowPixel && eLists.SOIL.includes(belowPixel.element) && Math.random() <= 0.05) { + changePixel(belowPixel, "carrot") + belowPixel.stage = 1 + belowPixel.connected = true + changePixel(pixel, "plant") + } + } +} + + +elements.carrot = { + color: ["#ec9a00", "#d67200"], + behavior: behaviors.STURDYPOWDER, + category: "food", + density: 700, + isFood: true, + breakInto: "juice", + breakIntoColor: "#ffa600", + hoverStat(pixel) { + if (pixel.stage) return pixel.stage + else return 0; + }, + tick(pixel) { + let belowPixel = getPixel(pixel.x, pixel.y + 1) + if (getPixel(pixel.x, pixel.y - 1) && getPixel(pixel.x, pixel.y - 1).element != "carrot" && getPixel(pixel.x, pixel.y - 1).element != "plant") { + pixel.connected = false; + } + if (!getPixel(pixel.x, pixel.y - 1)) { + pixel.connected = false + } + if (pixel.stage == 1 && Math.random() <= 0.05 && belowPixel && eLists.SOIL.includes(belowPixel.element) && pixel.connected) { + changePixel(belowPixel, "carrot"); + let newStage; + if (Math.random() <= 0.3) newStage = 1; else newStage = 2; + belowPixel.stage = newStage + belowPixel.connected = true + } + else if (pixel.stage == 2) { + if (Math.random() <= 0.1 && belowPixel && eLists.SOIL.includes(belowPixel.element) && pixel.connected) { + changePixel(belowPixel, "root") + } + } + } +} + +function factorial(n) { + if (n < 0) throw new Error("Factorial not defined for negative numbers"); + let result = 1; + for (let i = 2; i <= Number(n); i++) { + result *= i; + } + return result; +} + +function parseEquation(eq) { + eq = eq.replace(/\s+/g, ""); + + let tokens = []; + let current_num = ""; + + for (let i = 0; i < eq.length; i++) { + // Multi-character constants + if (eq.startsWith("pi", i)) { + if (current_num !== "") { tokens.push(parseFloat(current_num)); current_num = ""; } + tokens.push(Math.PI); + i++; + continue; + } + if (eq.startsWith("phi", i)) { + if (current_num !== "") { tokens.push(parseFloat(current_num)); current_num = ""; } + tokens.push(1.6180339887); + i += 2; + continue; + } + // Multi-character functions + if (eq.startsWith("sqrt", i)) { + if (current_num !== "") { tokens.push(parseFloat(current_num)); current_num = ""; } + tokens.push("sqrt"); + i += 3; // skip 'qrt' + continue; + } + + let char = eq[i]; + + if (/[0-9.]/.test(char)) { + current_num += char; + } else { + if (current_num !== "") { tokens.push(parseFloat(current_num)); current_num = ""; } + + if (char === "-" && (i === 0 || /[+\-*/(]/.test(eq[i - 1]))) { + current_num = "-"; + } + else if (char === "e") { tokens.push(Math.E); } + else if (char === "π") { tokens.push(Math.PI); } + else if (char === "φ") { tokens.push(1.6180339887); } + else if (char === "×") { tokens.push("*"); } + else if (char === "÷") { tokens.push("/"); } + else { tokens.push(char); } + } + } + + if (current_num !== "") { tokens.push(parseFloat(current_num)); } + + // Implicit multiplication + let fixed = []; + for (let i = 0; i < tokens.length; i++) { + fixed.push(tokens[i]); + let curr = tokens[i]; + let next = tokens[i + 1]; + + if ( + (typeof curr === "number" && next === "(") || + (curr === ")" && typeof next === "number") || + (curr === ")" && next === "(") || + (typeof curr === "number" && next === "sqrt") // implicit multiplication with functions + ) { + fixed.push("*"); + } + } + + return fixed; +} + + +function evaluate(tokens) { + function helper(start = 0) { + let stack = []; + let i = start; + + while (i < tokens.length) { + let token = tokens[i]; + + if (typeof token === "number") { + stack.push(token); + + // Check for factorial + if (tokens[i + 1] === "!") { + stack[stack.length - 1] = factorial(stack[stack.length - 1]); + i++; // skip the '!' token + } + } + else if (token === "sqrt") { + let [val, nextIndex] = helper(i + 1); + stack.push(Math.sqrt(val)); + + // Check for factorial after function + if (tokens[nextIndex + 1] === "!") { + stack[stack.length - 1] = factorial(stack[stack.length - 1]); + nextIndex++; + } + + i = nextIndex; + } + else if (token === "(") { + let [val, nextIndex] = helper(i + 1); + stack.push(val); + + // Factorial for parentheses + if (tokens[nextIndex + 1] === "!") { + stack[stack.length - 1] = factorial(stack[stack.length - 1]); + nextIndex++; + } + + i = nextIndex; + } + else if (token === ")") { + break; + } + else { + // operator + stack.push(token); + } + i++; + } + + + // First handle * and / + for (let j = 0; j < stack.length; j++) { + if (stack[j] === "*" || stack[j] === "/") { + let left = stack[j - 1]; + let right = stack[j + 1]; + let result = stack[j] === "*" ? left * right : left / right; + stack.splice(j - 1, 3, result); + j--; + } + } + + // Then handle + and - + for (let j = 0; j < stack.length; j++) { + if (stack[j] === "+" || stack[j] === "-") { + let left = stack[j - 1]; + let right = stack[j + 1]; + let result = stack[j] === "+" ? left + right : left - right; + stack.splice(j - 1, 3, result); + j--; + } + } + + return [stack[0], i]; + } + + return helper(0)[0]; +} + +function calculate(expr) { + let tokens = parseEquation(expr); + return evaluate(tokens); +} + +elements.calculator = { + canPlace: false, + onSelect() { + promptInput("Input your equation", (eq) => { + try { + let ans = calculate(eq) + if (ans == Infinity) { + logMessage("Division by 0 error") + return; + } + if (isNaN(ans)) { + logMessage("Error") + return; + } + logMessage(Number(ans.toFixed(10))) + } + catch (e) { + logMessage("Invalid Characters Detected") + console.log(e) + } + }) + }, + category: "tools" +} + +elements.random_teleporter = { + buttonColor: ["#5fdaff", "#dd8500"], + color: "#5fdaff", + grain: 0, + tick(pixel) { + // Color fading + pixel.fadeTo ??= "orange" + pixel.colorStay ??= 30 + if (pixel.fadeTo === "orange") { + if (pixel.colorStay > 0) { + let ratio = pixel.colorStay / 30 + pixel.color = fadeColor("#dd8500", "#5fdaff", ratio) + pixel.colorStay-- + } else pixel.fadeTo = "blue"; + } else { + if (30 - pixel.colorStay > 0) { + let ratio = (30 - pixel.colorStay) / 30 + pixel.color = fadeColor("#5fdaff", "#dd8500", ratio) + pixel.colorStay++ + } else pixel.fadeTo = "orange"; + } + + for (var i = 0; i < squareCoords.length; i++) { + let coord = squareCoords[i]; + let x = pixel.x + coord[0]; + let y = pixel.y + coord[1]; + if (!isEmpty(x, y)) { + let p = getPixel(x, y) + if (!p) return; + if (elements[p.element]?.hardness === 1) return; + let availableCoords = []; + for (let j = 0; j <= width; j++) { + for (let k = 0; k <= height; k++) { + if (isEmpty(j, k)) { + availableCoords.push([j, k]) + } + } + } + if (availableCoords.length > 0) { + let newCoords = choose(availableCoords) + tryMove(p, newCoords[0], newCoords[1]) + } + } + } + }, + hardness: 1, + behavior: behaviors.WALL, + renderer: renderPresets.BORDER, + category: "machines" +} + +elements.fill_all = { + color: "#ae4cd9", + behavior: behaviors.WALL, + category: "special", + reactions: { + "neutron": { elem1: "lattice" }, + "proton": { elem1: "vertical" }, + "electric": { elem1: "horizontal" }, + "positron": { elem1: "vertical" }, + "plasma": { elem1: "armageddon", tempMin: 500, charged: true } + }, + density: 1834, + tick(pixel) { + for (let x = 0; x <= width; x++) { + for (let y = 0; y <= height; y++) { + tryCreate("filler", x, y) + } + } + changePixel(pixel, "filler") + }, + maxSize: 1, + excludeRandom: true +} + +globals.elemFillAll = "sand" +elements.element_fill_all = { + color: "#ae4cd9", + behavior: behaviors.WALL, + category: "special", + excludeRandom: true, + reactions: { + "neutron": { elem1: "lattice" }, + "proton": { elem1: "vertical" }, + "electric": { elem1: "horizontal" }, + "positron": { elem1: "vertical" }, + "plasma": { elem1: "armageddon", tempMin: 500, charged: true } + }, + onSelect() { + promptInput("What element should this filler fill", (elem) => { + let el = mostSimilarElement(elem) + if (el) { + globals.elemFillAll = el + } + }, "Element Prompt") + }, + tick(pixel) { + for (let x = 0; x <= width; x++) { + for (let y = 0; y <= height; y++) { + tryCreate(globals.elemFillAll, x, y) + } + } + changePixel(pixel, globals.elemFillAll) + }, + maxSize: 1, +} + +globals.pulsecol1 = "#ffffff" +globals.pulsecol2 = "#000000" +elements.pulsing_color = { + buttonColor: rainbowColor, + behavior: behaviors.WALL, + category: "special", + onSelect() { + promptInput("Input the starting color of the pulse in hex", (col1) => { + if (col1) { + if (/^#([A-Fa-f0-9]{6})$/.test(col1)) { + globals.pulsecol1 = col1 + promptInput("Input the second color that it should pulse to", (col2) => { + if (col2) { + if (/^#([A-Fa-f0-9]{6})$/.test(col2)) { + globals.pulsecol2 = col2 + } + } + }) + } + } + }) + }, + onPlace(pixel) { + pixel.color = globals.pulsecol1 + }, + tick(pixel) { + // 1 = fade to color 2, 0 = fade to color 1 + let frames = 30 + pixel.fadeTo ??= 1 + pixel.colorStay ??= frames + pixel.col1 ??= globals.pulsecol1 + pixel.col2 ??= globals.pulsecol2 + if (pixel.fadeTo === 1) { + if (pixel.colorStay > 0) { + let ratio = pixel.colorStay / frames + pixel.color = fadeColor(pixel.col2, pixel.col1, ratio) + pixel.colorStay-- + } else pixel.fadeTo = 0; + } else { + if (frames - pixel.colorStay > 0) { + let ratio = (frames - pixel.colorStay) / frames + pixel.color = fadeColor(pixel.col1, pixel.col2, ratio) + pixel.colorStay++ + } else pixel.fadeTo = 1; + } + } +} + +globals.createElemWDir = "wood" +globals.lineDir = 0 +elements.element_line = { + color: "#d9d9d9", + behavior: behaviors.WALL, + category: "special", + hidden: true, + excludeRandom: true, + insulate: true, + onSelect() { + promptInput("Input the element it should use", (input) => { + if (!input) return; + let elem = mostSimilarElement(input) + if (elem) { + globals.createElemWDir = elem + } + promptDir("Choose The direction the line should draw in", (dir) => { + globals.lineDir = dir + }, "Direction Input") + }, "Element Input") + }, + onPlace(pixel) { + pixel.clone ??= globals.createElemWDir + pixel.dir ??= globals.lineDir + }, + tick(pixel) { + if (pixel.dir === 0) { + if (!tryMove(pixel, pixel.x + 1, pixel.y, pixel.clone)) { + changePixel(pixel, pixel.clone, true) + } + } + if (pixel.dir === 1) { + if (!tryMove(pixel, pixel.x, pixel.y - 1, pixel.clone)) { + changePixel(pixel, pixel.clone, true) + } + } + if (pixel.dir === 2) { + if (!tryMove(pixel, pixel.x + 1, pixel.y, pixel.clone)) { + changePixel(pixel, pixel.clone, true) + } + } + if (pixel.dir === 3) { + if (!tryMove(pixel, pixel.x, pixel.y - 1, pixel.clone)) { + changePixel(pixel, pixel.clone, true) + } + } + } +} + +function getSquareCoords(pixel) { + let x, y; + for (let i = 0; i < squareCoords.length; i++) { + let coord = squareCoords[i]; + x = pixel.x + coord[0]; + y = pixel.y + coord[1]; + } + return { x, y } +} + +function getAdjacentCoords(pixel) { + let x, y; + for (let i = 0; i < adjacentCoords.length; i++) { + x = pixel.x + adjacentCoords[i][0]; + y = pixel.y + adjacentCoords[i][1]; + } + return { x, y } +} + +function getSquareCoordsShuffle(pixel) { + shuffleArray(squareCoordsShuffle); + let x, y; + for (var i = 0; i < squareCoordsShuffle.length; i++) { + var coord = squareCoordsShuffle[i]; + x = pixel.x + coord[0]; + y = pixel.y + coord[1]; + } + return { x, y } +} + +function getAdjacentCoordsShuffle(pixel) { + shuffleArray(adjacentCoordsShuffle) + let x, y + for (var i = 0; i < adjacentCoordsShuffle.length; i++) { + x = pixel.x + adjacentCoordsShuffle[i][0]; + y = pixel.y + adjacentCoordsShuffle[i][1]; + } + return { x, y } +} + +function getScreenCoords() { + let coords = [] + for (let x = 0; x <= width; x++) { + for (let y = 0; y <= height; y++) { + coords.push([x, y]) + } + } + return coords +} + +globals.replaceElem = "wood" +elements.replace_all_of_element = { + color: ["#35008a", "#000000"], + category: "tools", + onSelect() { + promptInput("What should it change into?", (input) => { + if (!input) return; + let elem = mostSimilarElement(input) + if (elem) { + globals.replaceElem = elem + } + }, "Element Prompt") + }, + tool(pixel) { + let elem = pixel.element + if (elem === globals.replaceElem) return + + for (let row of pixelMap) { + for (let p of row) { + if (p?.element === elem) { + changePixel(p, globals.replaceElem) + } + } + } + } +}