This commit is contained in:
slweeb 2025-09-01 16:27:06 -04:00
commit 4e9391d2c1
2 changed files with 5535 additions and 36 deletions

View File

@ -1,3 +1,7 @@
// TypeScript integration for Sandboxels modding
// Enables function autocomplete & element definition hints
/// <reference path="./sandboxels-types.d.ts" />
// Get the file here: https://github.com/Cube14yt/sandboxels-types
// Changelog
// Starts at version 3
@ -21,8 +25,15 @@ Machines: Robot, Adjustable heater/cooler
Bug Fixes
Fixed compatibility issue with nousersthings.js
*/
V4
Machines: Paper filter, Indestructable filter, and Note block
Life: Cacao Plants (seed, stem, fruit)
Tools: Polish
Extras: 2 ways to make an element with no name
Special: Black hole
Building Materials: Roman concrete/cement
*/
elements.button = {
@ -1545,6 +1556,7 @@ function tryJump(headPixel) {
elements.robot_head = {
color: "#d9d9d9",
category: "machines",
state: "solid",
tick(pixel) {
const body = getPixel(pixel.x, pixel.y + 1);
@ -1583,6 +1595,7 @@ elements.robot_head = {
elements.robot_body = {
color: "#b1b1b1",
category: "machines",
state: "solid",
tick(pixel) {
const head = getPixel(pixel.x, pixel.y - 1);
@ -1608,6 +1621,7 @@ elements.robot_body = {
elements.robot = {
color: "#b1b1b1",
category: "machines",
state: "solid",
onSelect() {
promptChoose(
"Choose robot mode",
@ -1663,6 +1677,7 @@ elements.broken_adjustable_heater = {
category: "extras",
insulate: true,
behavior: behaviors.WALL,
onSelect() {
promptInput(
"Select the temperature you want to adjust to",
@ -1730,24 +1745,7 @@ elements.adjustable_heater = {
} else if (current_pixel.temp > adjusted_temp) {
current_pixel.temp = Math.max(current_pixel.temp - heatAmount, adjusted_temp);
}
// Phase change check (forces melting/boiling/etc.)
let elemDef = elements[current_pixel.element];
if (elemDef) {
// Too hot for current state → change to high state
if (typeof elemDef.tempHigh === "number" &&
current_pixel.temp >= elemDef.tempHigh &&
elemDef.stateHigh) {
changePixel(current_pixel, elemDef.stateHigh);
}
// Too cold for current state → change to low state
if (typeof elemDef.tempLow === "number" &&
current_pixel.temp <= elemDef.tempLow &&
elemDef.stateLow) {
changePixel(current_pixel, elemDef.stateLow);
}
}
pixelTempCheck(current_pixel)
}
}
}
@ -1831,27 +1829,633 @@ elements.adjustable_cooler = {
current_pixel.temp = Math.min(current_pixel.temp + coolAmount, adjusted_cool_temp);
}
// Phase change check (forces melting/freezing/etc.)
let elemDef = elements[current_pixel.element];
if (elemDef) {
// Too hot → change to high state
if (typeof elemDef.tempHigh === "number" &&
current_pixel.temp >= elemDef.tempHigh &&
elemDef.stateHigh) {
changePixel(current_pixel, elemDef.stateHigh);
}
// Too cold → change to low state
if (typeof elemDef.tempLow === "number" &&
current_pixel.temp <= elemDef.tempLow &&
elemDef.stateLow) {
changePixel(current_pixel, elemDef.stateLow);
}
}
pixelTempCheck(current_pixel)
}
}
}
};
let polishedList = new Set()
elements.polish = {
category: "tools",
color: ["#a0dff0", "#c0e8f8", "#e0f5ff"],
tool(pixel) {
let element = pixel.element
if ((elements[pixel.element].colorPattern && !polishedList.has(`${pixel.x}, ${pixel.y}`)) || shiftDown) {
deletePixel(pixel.x, pixel.y)
createPixel(element, pixel.x, pixel.y)
polishedList.add(`${pixel.x}, ${pixel.y}`)
}
},
onUnselect() {
polishedList.clear()
}
}
elements[" "] = {
category: "extras",
onSelect() {
logMessage("This Element has weird properties since its a space ' '")
},
alias: "space"
}
elements.paper_filter = {
desc: "Filters solids from liquids",
color: "#ececec",
behavior: behaviors.WALL,
reactions: {
"light": { stain1: "#ebdfa7" },
"oxygen": { stain1: "#ebdfa7" }
},
tempHigh: 248,
stateHigh: ["fire", "fire", "fire", "fire", "fire", "ash"],
burn: 70,
burnTime: 300,
burnInto: ["fire", "fire", "fire", "fire", "fire", "ash"],
category: "machines",
density: 1201,
breakInto: "confetti",
breakIntoColor: ["#ffffff", "#e6e6e6", "#dbdbdb"],
tick(pixel) {
let upPixel = getPixel(pixel.x, pixel.y - 1)
if (upPixel && elements[upPixel.element].state == "liquid" && !pixel.con) {
deletePixel(pixel.x, pixel.y - 1)
pixel.con = upPixel
}
if (upPixel && (upPixel.element === "paper_filter" || upPixel.element === "indestructable_filter") && upPixel.con && !pixel.con) {
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))
}
if (viscMove) {
pixel.con = liquid
delete upPixel.con
}
}
if (isEmpty(pixel.x, pixel.y + 1) && !outOfBounds(pixel.x, pixel.y + 1) && pixel.con) {
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))
}
if (viscExit) {
createPixel(liquid.element, pixel.x, pixel.y + 1)
delete pixel.con
}
}
}
}
elements.indestructable_filter = {
desc: "Filters solids from liquids",
color: "#aaaaaa",
behavior: behaviors.WALL,
category: "machines",
state: "solid",
movable: false,
tick(pixel) {
let upPixel = getPixel(pixel.x, pixel.y - 1)
let belowPixel = getPixel(pixel.x, pixel.y + 1)
if (upPixel && elements[upPixel.element].state == "liquid" && !pixel.con) {
deletePixel(pixel.x, pixel.y - 1)
pixel.con = upPixel
}
if (upPixel && (upPixel.element === "indestructable_filter" || upPixel.element === "paper_filter") && upPixel.con && !pixel.con) {
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))
}
if (viscMove) {
pixel.con = liquid
delete upPixel.con
}
}
if (isEmpty(pixel.x, pixel.y + 1) && !outOfBounds(pixel.x, pixel.y + 1) && pixel.con) {
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))
}
if (viscExit) {
createPixel(liquid.element, pixel.x, pixel.y + 1)
delete pixel.con
}
}
}
}
let black_hole_expand = false
elements.black_hole = {
color: "#111111",
hardness: 1,
category: "special",
properties: {
absorbed: 0
},
renderer: function (pixel, ctx) {
if (!viewInfo[view].colorEffects) { drawDefault(ctx, pixel); return }
renderPresets.HEATGLOW(pixel, ctx);
if (pixel.alpha === 0) return;
let edge = false;
pixel.edge = false;
pixel.color = "#111111";
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 (!outOfBounds(x, y)) {
let neighbor = getPixel(x, y);
if (!neighbor || elements[neighbor.element].movable !== elements[pixel.element].movable) {
edge = true;
break;
}
}
}
if (edge) { pixel.color = "#ffae00"; pixel.edge = true }
},
tick(pixel) {
// Glow effect
if (pixel.edge) {
pixel.glow = true;
if (enabledMods.includes("mods/glow.js")) {
pixel.emit = 10;
}
}
else {
pixel.glow = false;
if (enabledMods.includes("mods/glow.js")) {
pixel.emit = 0;
}
}
// Suction physics
let radius = 20; // how far the suction reaches
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];
// Skip if indestructible
if (elemDef.hardness === 1) continue;
// Distance to black hole
let dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= radius) {
// Suction chance: closer = stronger pull
let chance = 1 / dist;
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);
}
else if (dist <= 1.5) {
deletePixel(x, y); // absorb it
pixel.absorbed++
}
}
}
}
}
}
if (black_hole_expand) {
for (var i = 0; i < adjacentCoords.length; i++) {
var x = pixel.x + adjacentCoords[i][0];
var y = pixel.y + adjacentCoords[i][1];
if (pixel.absorbed >= 30 && isEmpty(x, y)) {
createPixel("black_hole", x, y)
pixel.absorbed = 0
}
}
}
}
},
forceSaveColor: true,
onSelect() {
promptChoose(
"Do you want the black hole to grow?",
["Yes", "No"],
(choice) => {
if (!choice) {
choice = false
}
if (choice == "Yes") {
black_hole_expand = true
}
else {
black_hole_expand = false
}
}
)
}
};
elements.cacao_fruit = {
color: "#854700",
behavior: [
"XX|ST:cacao_stem|XX",
"ST:cacao_stem|XX|ST:cacao_stem",
"XX|ST:cacao_stem AND M1|XX"
],
isFood: true,
burn: 10,
burnTime: 100,
burnInto: "ash",
breakInto: "cacao_bean",
category: "food",
state: "solid",
density: 1000
}
elements.cacao_bean = {
color: "#ffe7ba",
isFood: true,
behavior: [
"XX|XX|XX",
"XX|XX|XX",
"M2%10|M1|M2%10"
],
tempHigh: 100,
stateHigh: "dried_cacao_bean",
onStateHigh(pixel) { releaseElement(pixel, "steam") },
state: "solid",
category: "food",
density: 1000
}
elements.dried_cacao_bean = {
color: "#61321e",
behavior: behaviors.POWDER,
reactions: {
"sugar_water": { elem2: "melted_chocolate", tempMin: 65 },
"water": { elem2: "melted_chocolate", tempMin: 65 }
},
tempHigh: 400,
stateHigh: "ash",
isFood: true,
category: "food",
state: "solid",
density: 1000
}
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() {
logMessage("Another way to make an element with no name \"_\"")
},
alias: ["underscore"]
}
elements.cacao_seed = {
color: "#8b3f00",
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 ((!isEmpty(pixel.x, pixel.y + 1) && belowPixel) || outOfBounds(pixel.x, pixel.y + 1) && Math.random() <= 0.005) {
changePixel(pixel, "cacao_stem")
pixel.stage = 1
}
}
}
elements.cacao_stem = {
color: "#916a00",
renderer: renderPresets.WOODCHAR,
movable: false,
tempHigh: 100,
stateHigh: "wood",
tempLow: -30,
stateLow: "wood",
category: "life",
burn: 2,
burnTime: 300,
burnInto: ["sap", "ember", "charcoal", "smoke"],
state: "solid",
density: 1500,
hardness: 0.15,
breakInto: ["sap", "sawdust"],
seed: "cacao_seed",
forceSaveColor: true,
stateHighColorMultiplier: 0.95,
onPlace(pixel) {
pixel.stage = 1
},
hoverStat(pixel) {
if (pixel.stage) return pixel.stage;
else return 0;
},
tick(pixel) {
// 1 = trunk
// 2 = spread
// 3 = stop
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 (Math.random() <= 0.3) {
pixel.stage = 2
}
}
if (pixel.stage === 2) {
let rand = Math.random()
let nx;
if (rand < 0.4) {
nx = 1
}
else if (rand < 0.8) {
nx = -1
}
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) {
newPixel.stage = 3
}
else 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)
for (var i = 0; i < adjacentCoordsShuffle.length; i++) {
var x = pixel.x + adjacentCoordsShuffle[i][0];
var y = pixel.y + adjacentCoordsShuffle[i][1];
if (isEmpty(x, y) && !pixel.fruitMade) {
createPixel("cacao_fruit", x, y)
pixel.fruitMade = true
pixel.fruitCoordsx = x
pixel.fruitCoordsy = y
break
}
}
}
}
if (pixel.fruitCoordsx && pixel.fruitCoordsy) {
if (getPixel(pixel.fruitCoordsx, pixel.fruitCoordsy) && getPixel(pixel.fruitCoordsx, pixel.fruitCoordsy).element === "cacao_fruit") return;
pixel.fruitMade = false
delete pixel.fruitCoordsx
delete pixel.fruitCoordsy
}
}
}
// --- audio setup ---
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playNote(frequency, duration = 1, type = "sine", volume = 0.1) {
if (!Number.isFinite(frequency)) {
console.error("Invalid frequency:", frequency);
return;
}
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = frequency;
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
osc.connect(gain);
gain.connect(audioCtx.destination);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
const pianoFrequencies = {
"A0": 27.500, "A#0": 29.135, "BB0": 29.135, "B0": 30.868,
"C1": 32.703, "C#1": 34.648, "DB1": 34.648, "D1": 36.708,
"D#1": 38.891, "EB1": 38.891, "E1": 41.203, "F1": 43.654,
"F#1": 46.249, "GB1": 46.249, "G1": 48.999, "G#1": 51.913,
"AB1": 51.913, "A1": 55.000, "A#1": 58.270, "BB1": 58.270,
"B1": 61.735,
"C2": 65.406, "C#2": 69.296, "DB2": 69.296, "D2": 73.416,
"D#2": 77.782, "EB2": 77.782, "E2": 82.407, "F2": 87.307,
"F#2": 92.499, "GB2": 92.499, "G2": 97.999, "G#2": 103.826,
"AB2": 103.826, "A2": 110.000, "A#2": 116.541, "BB2": 116.541,
"B2": 123.471,
"C3": 130.813, "C#3": 138.591, "DB3": 138.591, "D3": 146.832,
"D#3": 155.563, "EB3": 155.563, "E3": 164.814, "F3": 174.614,
"F#3": 184.997, "GB3": 184.997, "G3": 195.998, "G#3": 207.652,
"AB3": 207.652, "A3": 220.000, "A#3": 233.082, "BB3": 233.082,
"B3": 246.942,
"C4": 261.626, "C#4": 277.183, "DB4": 277.183, "D4": 293.665,
"D#4": 311.127, "EB4": 311.127, "E4": 329.628, "F4": 349.228,
"F#4": 369.994, "GB4": 369.994, "G4": 391.995, "G#4": 415.305,
"AB4": 415.305, "A4": 440.000, "A#4": 466.164, "BB4": 466.164,
"B4": 493.883,
"C5": 523.251, "C#5": 554.365, "DB5": 554.365, "D5": 587.330,
"D#5": 622.254, "EB5": 622.254, "E5": 659.255, "F5": 698.456,
"F#5": 739.989, "GB5": 739.989, "G5": 783.991, "G#5": 830.609,
"AB5": 830.609, "A5": 880.000, "A#5": 932.328, "BB5": 932.328,
"B5": 987.767,
"C6": 1046.502, "C#6": 1108.731, "DB6": 1108.731, "D6": 1174.659,
"D#6": 1244.508, "EB6": 1244.508, "E6": 1318.510, "F6": 1396.913,
"F#6": 1479.978, "GB6": 1479.978, "G6": 1567.982, "G#6": 1661.219,
"AB6": 1661.219, "A6": 1760.000, "A#6": 1864.655, "BB6": 1864.655,
"B6": 1975.533,
"C7": 2093.005, "C#7": 2217.461, "DB7": 2217.461, "D7": 2349.318,
"D#7": 2489.016, "EB7": 2489.016, "E7": 2637.020, "F7": 2793.826,
"F#7": 2959.955, "GB7": 2959.955, "G7": 3135.963, "G#7": 3322.438,
"AB7": 3322.438, "A7": 3520.000, "A#7": 3729.310, "BB7": 3729.310,
"B7": 3951.066,
"C8": 4186.009
};
let note = 261.626; // default C4
let notesToPlay = [];
function flushNotes() {
if (notesToPlay.length === 0) return;
let baseVolume = 0.2;
let volume = baseVolume / Math.sqrt(notesToPlay.length);
for (let f of notesToPlay) {
playNote(f, 1, "sine", volume);
}
notesToPlay = [];
}
elements.note_block = {
color: "#965500",
behavior: behaviors.WALL,
onSelect() {
promptInput(
"Select the note this note block should be",
function (choice) {
if (!choice) {
if (!note) { note = 261.626; }
return;
}
let key = choice.toUpperCase();
if (key in pianoFrequencies) {
note = pianoFrequencies[key];
} else {
note = 261.626; // fallback = C4
}
},
"Note prompt"
);
},
onPlace(pixel) {
pixel.note = note;
},
tick(pixel) {
if (pixel.charge) {
notesToPlay.push(Number(pixel.note));
}
},
conduct: 1,
category: "machines"
};
runEveryTick(function () { flushNotes() });
/*
elements.uncook = {
color: ["#4dcdff", "#70ddff", "#bcddff", "#ffffff"],
category: "tools",
tool(pixel) {
if (!pixel || !pixel.element) return;
// 1) If the current element itself defines stateLow, use it (common case)
const cur = elements[pixel.element];
if (cur && cur.stateLow !== undefined) {
const low = cur.stateLow;
pixel.element = Array.isArray(low) ? low[Math.floor(Math.random() * low.length)] : low;
if (typeof pixel.temp === "number") pixel.temp = Math.max(0, pixel.temp - 1);
return; // done
}
// 2) Otherwise search for an element whose stateHigh === the current element
for (const key in elements) {
const el = elements[key];
if (!el) continue;
if (el.stateHigh === pixel.element) {
// 'key' is the low-state element name
changePixel(pixel, key)
if (typeof pixel.temp === "number") pixel.temp = Math.max(0, pixel.temp - 1);
return; // done
}
// If el.stateHigh can be an array of high-state names:
if (Array.isArray(el.stateHigh) && el.stateHigh.includes(pixel.element)) {
changePixel(pixel, key)
if (typeof pixel.temp === "number") pixel.temp = Math.max(0, pixel.temp - 1);
return;
}
}
}
};
*/
elements.roman_cement = {
color: "#b8b8b8",
behavior: behaviors.LIQUID,
category: "liquids",
viscosity: 1000,
density: 1400,
state: "solid",
tempLow: -10,
stateLow: "roman_concrete",
tempHigh: 1550,
stateHigh: "magma",
tick(pixel) {
if (pixelTicks - pixel.start > 100 && Math.random() <= 0.1) {
changePixel(pixel, "roman_concrete")
}
}
}
elements.roman_concrete = {
color: "#ababab",
behavior: behaviors.SUPPORT,
tempHigh: 1500,
stateHigh: "magma",
category: "powders",
state: "solid",
density: 2400,
hardness: 0.5,
breakInto: "dust",
darkText: true
}
/**
*
* @param {string} element
* @param {object} reaction
* @returns {void}
*/
function doWaterReactions(element, reaction) {
if (!elements[element].reactions) {
elements[element].reactions = {}
}
elements[element].reactions.water = reaction
elements[element].reactions.salt_water = reaction
elements[element].reactions.pool_water = reaction
elements[element].reactions.sugar_water = reaction
elements[element].reactions.dirty_water = reaction
elements[element].reactions.selter = reaction
elements[element].reactions.primordial_soup = reaction
elements[element].reactions.nut_milk = reaction
}
doWaterReactions("slaked_lime", {elem1:"roman_cement", elem2: null, chance: 0.25})

4895
mods/noita.js Normal file

File diff suppressed because it is too large Load Diff