Added mod: noita_demake.js
A mod that is a very early test of trying to demake noita.
This commit is contained in:
parent
8cadcf2079
commit
61e25750d0
|
|
@ -0,0 +1,430 @@
|
|||
// --- KEY STATE ---
|
||||
var KA = false;
|
||||
var KD = false;
|
||||
var KW = false;
|
||||
var KS = false;
|
||||
var mouseX = 0;
|
||||
var mouseY = 0;
|
||||
var selectedSpell = 0;
|
||||
|
||||
|
||||
keybinds["Digit1"] = function() {
|
||||
selectedSpell = 0;
|
||||
};
|
||||
keybinds["Digit2"] = function() {
|
||||
selectedSpell = 1;
|
||||
};
|
||||
keybinds["Digit3"] = function() {
|
||||
selectedSpell = 2;
|
||||
};
|
||||
keybinds["Digit4"] = function() {
|
||||
selectedSpell = 3;
|
||||
};
|
||||
|
||||
|
||||
|
||||
document.addEventListener("keydown", function(e) {
|
||||
if (e.key === "s" || e.key === "S") {
|
||||
e.preventDefault();
|
||||
KS = true;
|
||||
}
|
||||
const key = e.key;
|
||||
if (key >= "0" && key <= "9") {
|
||||
selectedSpell = key === "0" ? 9 : parseInt(key) - 1;
|
||||
}
|
||||
if (e.key === "a" || e.key === "A") KA = true;
|
||||
if (e.key === "d" || e.key === "D") KD = true;
|
||||
if (e.key === "w" || e.key === "W") KW = true;
|
||||
if (e.key === "s" || e.key === "S") KS = true;
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener("keyup", function(e) {
|
||||
if (e.key === "s" || e.key === "S") {
|
||||
e.preventDefault();
|
||||
KS = false;
|
||||
}
|
||||
if (e.key === "a" || e.key === "A") KA = false;
|
||||
if (e.key === "d" || e.key === "D") KD = false;
|
||||
if (e.key === "w" || e.key === "W") KW = false;
|
||||
if (e.key === "s" || e.key === "S") KS = false;
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", function(e) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
mouseX = Math.floor((e.clientX - rect.left) / pixelSize);
|
||||
mouseY = Math.floor((e.clientY - rect.top) / pixelSize);
|
||||
});
|
||||
|
||||
|
||||
// --- WIZARD BASE ---
|
||||
elements.wizard = {
|
||||
color: "#5a00a0",
|
||||
behavior: behaviors.WALL,
|
||||
category: "noita",
|
||||
state: "solid",
|
||||
|
||||
hardness: 1.2,
|
||||
conduct: 0.4,
|
||||
fireResistance: 25,
|
||||
burn: 100,
|
||||
burnTime: 150,
|
||||
burnInto: "ash",
|
||||
density: 2000,
|
||||
desc: "A magical being controlled with wasd. Dies easily.",
|
||||
|
||||
tick: function(pixel) {
|
||||
|
||||
if (typeof pixel.temp === "undefined") pixel.temp = 30;
|
||||
if (pixel.temp < 30) pixel.temp += 1;
|
||||
else if (pixel.temp > 30) pixel.temp -= 1;
|
||||
|
||||
if (pixel.temp > 100) {
|
||||
pixel.hotTicks ??= 0;
|
||||
pixel.hotTicks++;
|
||||
if (pixel.hotTicks >= 60) {
|
||||
changePixel(pixel, "ash");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
pixel.hotTicks = 0;
|
||||
}
|
||||
|
||||
if (pixel.hotTicks >= 30 && Math.random() < 0.2) {
|
||||
createPixel("smoke", pixel.x, pixel.y - 1);
|
||||
}
|
||||
|
||||
let coldFactor = 1;
|
||||
if (pixel.temp < 0) {
|
||||
coldFactor = Math.max(0.02, Math.pow((pixel.temp + 100) / 100, 2));
|
||||
}
|
||||
|
||||
|
||||
if (typeof pixel.spellCooldown === "undefined") pixel.spellCooldown = 0;
|
||||
if (typeof pixel.wasQPressed === "undefined") pixel.wasQPressed = false;
|
||||
|
||||
if (isNaN(pixel.vx)) pixel.vx = 0;
|
||||
if (isNaN(pixel.vy)) pixel.vy = 0;
|
||||
if (isNaN(pixel.frame)) pixel.frame = 0;
|
||||
pixel.frame++;
|
||||
|
||||
const moveAccel = 0.2 * coldFactor;
|
||||
const maxV = 1 * coldFactor;
|
||||
|
||||
if (KA) pixel.vx = -moveAccel;
|
||||
else if (KD) pixel.vx = moveAccel;
|
||||
else pixel.vx = 0;
|
||||
|
||||
if (KW) pixel.vy = -1 * coldFactor;
|
||||
|
||||
pixel.vy += 0.05;
|
||||
if (!KW && pixel.vy < 0) pixel.vy = 0;
|
||||
|
||||
pixel.vx = Math.max(-maxV, Math.min(maxV, pixel.vx));
|
||||
pixel.vy = Math.max(-maxV, Math.min(maxV, pixel.vy));
|
||||
|
||||
if (pixel.vx !== 0) tryMove(pixel, pixel.x + Math.sign(pixel.vx), pixel.y);
|
||||
if (pixel.vy !== 0 && pixel.frame % 2 === 0) tryMove(pixel, pixel.x, pixel.y + Math.sign(pixel.vy));
|
||||
if (!isEmpty(pixel.x, pixel.y + 1) && pixel.vy > 0) pixel.vy = 0;
|
||||
|
||||
if (pixel.spellCooldown > 0) pixel.spellCooldown--;
|
||||
|
||||
if (KS && !pixel.wasQPressed && pixel.spellCooldown === 0) {
|
||||
const spellFns = [
|
||||
// Magic Missile 1
|
||||
() => {
|
||||
let dx = mouseX - pixel.x;
|
||||
let dy = mouseY - (pixel.y - 2);
|
||||
let mag = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
dx /= mag;
|
||||
dy /= mag;
|
||||
let vx = dx * 2;
|
||||
let vy = dy * 2;
|
||||
let spawnX = pixel.x;
|
||||
let spawnY = pixel.y - 2;
|
||||
createPixel("nfirebolt", spawnX, spawnY);
|
||||
let bolt = pixelMap[spawnX]?.[spawnY];
|
||||
if (bolt) {
|
||||
bolt.vx = vx;
|
||||
bolt.vy = vy;
|
||||
}
|
||||
},
|
||||
// Swipe 2
|
||||
() => {
|
||||
for (let r = 6; r <= 8; r++) {
|
||||
for (let a = 0; a < 360; a += 10) {
|
||||
let angle = a * Math.PI / 180;
|
||||
let x = Math.round(pixel.x + r * Math.cos(angle));
|
||||
let y = Math.round(pixel.y + r * Math.sin(angle));
|
||||
if (isEmpty(x, y)) createPixel("cold_fire", x, y);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Shield 3
|
||||
() => {
|
||||
for (let dx = -3; dx <= 3; dx++) {
|
||||
for (let dy = -3; dy <= 3; dy++) {
|
||||
if ((Math.abs(dx) === 3 || Math.abs(dy) === 3) && isEmpty(pixel.x + dx, pixel.y + dy)) {
|
||||
createPixel("nmagicbarrier", pixel.x + dx, pixel.y + dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Meteor 4
|
||||
() => {
|
||||
if (isEmpty(pixel.x, pixel.y - 10)) {
|
||||
createPixel("nbomb", pixel.x, pixel.y + 2);
|
||||
}
|
||||
},
|
||||
// Rain 5
|
||||
() => {
|
||||
createPixel("rain_call", pixel.x, pixel.y - 6);
|
||||
},
|
||||
// Bridge 6
|
||||
() => {
|
||||
for (let y = 1; y <= 3; y++) {
|
||||
if (isEmpty(pixel.x, pixel.y + y)) {
|
||||
createPixel("dirt", pixel.x, pixel.y + y);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Wisp 7
|
||||
() => {
|
||||
createPixel("nwisp", pixel.x, pixel.y - 2);
|
||||
},
|
||||
// Growth Burst 8
|
||||
() => {
|
||||
let growthOptions = ["plant", "grass", "seeds", "vine", "flower_seed", "sapling"];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let gx = pixel.x + Math.floor(Math.random() * 5 - 2);
|
||||
let gy = pixel.y + Math.floor(Math.random() * 5 - 2);
|
||||
if (isEmpty(gx, gy)) {
|
||||
let pick = growthOptions[Math.floor(Math.random() * growthOptions.length)];
|
||||
createPixel(pick, gx, gy);
|
||||
}
|
||||
}
|
||||
},
|
||||
// Obliteration Blink
|
||||
() => {
|
||||
const dashLength = 20;
|
||||
let dx = mouseX - pixel.x;
|
||||
let dy = mouseY - pixel.y;
|
||||
let mag = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
dx = Math.round((dx / mag) * dashLength);
|
||||
dy = Math.round((dy / mag) * dashLength);
|
||||
|
||||
let targetX = pixel.x + dx;
|
||||
let targetY = pixel.y + dy;
|
||||
|
||||
// 💣 Step 1: Destroy 4x4 area around the wizard
|
||||
for (let dx = -2; dx <= 1; dx++) {
|
||||
for (let dy = -2; dy <= 1; dy++) {
|
||||
let tx = pixel.x + dx;
|
||||
let ty = pixel.y + dy;
|
||||
if (!isEmpty(tx, ty) && pixelMap[tx][ty] !== pixel) {
|
||||
deletePixel(tx, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 💨 Step 2: Leave trail of particles
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let tx = Math.round(pixel.x + (i / 5) * (targetX - pixel.x));
|
||||
let ty = Math.round(pixel.y + (i / 5) * (targetY - pixel.y));
|
||||
if (isEmpty(tx, ty)) {
|
||||
createPixel("nspell_dust", tx, ty);
|
||||
}
|
||||
}
|
||||
|
||||
// 🌫️ Puff at old spot
|
||||
for (let i = 0; i < 4; i++) {
|
||||
createPixel("smoke", pixel.x + Math.floor(Math.random()*3 - 1), pixel.y + Math.floor(Math.random()*3 - 1));
|
||||
}
|
||||
|
||||
// 🚀 Step 3: Move the wizard
|
||||
movePixel(pixel, targetX, targetY);
|
||||
|
||||
},
|
||||
|
||||
// Crystal Bridge 0
|
||||
() => {
|
||||
let dx = mouseX - pixel.x;
|
||||
let dy = mouseY - pixel.y;
|
||||
let mag = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
dx /= mag;
|
||||
dy /= mag;
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
let x = Math.round(pixel.x + dx * i);
|
||||
let y = Math.round(pixel.y + dy * i);
|
||||
if (isEmpty(x, y)) {
|
||||
createPixel("stained_glass", x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
// ✨ Visual spell cast effect
|
||||
// ✨ Triangle particle effect
|
||||
let baseAngle = (pixel.frame || 0) * 0.15; // slowly rotates
|
||||
let radius = 4;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let angle = baseAngle + i * (2 * Math.PI / 3); // 0, 120°, 240°
|
||||
let x = Math.round(pixel.x + radius * Math.cos(angle));
|
||||
let y = Math.round(pixel.y + radius * Math.sin(angle));
|
||||
|
||||
if (isEmpty(x, y)) {
|
||||
createPixel("nspell_dust", x, y);
|
||||
let p = pixelMap[x]?.[y];
|
||||
if (p) {
|
||||
// Set velocity tangent to the circle (spinning effect)
|
||||
let spinForce = pixel.spinVelocity ?? 2; // starts strong
|
||||
let tangentAngle = angle + Math.PI / 2;
|
||||
p.vx = spinForce * Math.cos(tangentAngle);
|
||||
p.vy = spinForce * Math.sin(tangentAngle);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spellFns[selectedSpell]?.();
|
||||
pixel.spellCooldown = 5;
|
||||
}
|
||||
|
||||
pixel.wasSPressed = KS;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// NOITA -------------------------------------------------------
|
||||
// SPELLS -----------------------------------------------------
|
||||
// SECTION ---------------------------------------------------
|
||||
|
||||
// --- MAGIC BARRIER ---
|
||||
elements.nmagicbarrier = {
|
||||
color: "#b8f0ff",
|
||||
behavior: behaviors.WALL,
|
||||
category: "noita",
|
||||
state: "solid",
|
||||
density: 50,
|
||||
pushable: 1,
|
||||
tick: function(pixel) {
|
||||
if (!pixel || typeof pixel.x !== "number" || typeof pixel.y !== "number") return;
|
||||
if (Math.random() < 0.02) {
|
||||
deletePixel(pixel.x, pixel.y);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// --- FIREBOLT ---
|
||||
elements.nfirebolt = {
|
||||
color: "#ff7300",
|
||||
behavior: behaviors.MOVE,
|
||||
category: "noita",
|
||||
state: "gas",
|
||||
tick: function(pixel) {
|
||||
if (isNaN(pixel.vx)) pixel.vx = 0;
|
||||
if (isNaN(pixel.vy)) pixel.vy = 0;
|
||||
|
||||
// Slight gravity
|
||||
pixel.vy += 0.02;
|
||||
|
||||
// Move
|
||||
let newX = pixel.x + Math.round(pixel.vx);
|
||||
let newY = pixel.y + Math.round(pixel.vy);
|
||||
|
||||
if (isEmpty(newX, newY)) {
|
||||
movePixel(pixel, newX, newY);
|
||||
} else {
|
||||
// Impact: burst of plasma
|
||||
for (let dx = -1; dx <= 1; dx++) {
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
if (Math.random() < 0.6 && isEmpty(pixel.x + dx, pixel.y + dy)) {
|
||||
createPixel("fire", pixel.x + dx, pixel.y + dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deletePixel(pixel.x, pixel.y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --- BOMB ---
|
||||
elements.nbomb = {
|
||||
color: "#333333",
|
||||
behavior: behaviors.POWDER,
|
||||
category: "noita",
|
||||
state: "solid",
|
||||
desc: "Explodes 2 seconds after being placed.",
|
||||
cooldown: 90, // tracks ticks
|
||||
|
||||
tick: function(pixel) {
|
||||
if (isNaN(pixel.life)) pixel.life = 0;
|
||||
pixel.life++;
|
||||
|
||||
// Visual cue: start flashing after 40 ticks
|
||||
if (pixel.life > 40 && Math.random() < 0.3) {
|
||||
createPixel("electric", pixel.x + (Math.random() < 0.5 ? 1 : -1), pixel.y - 1);
|
||||
}
|
||||
|
||||
if (pixel.life >= 60) {
|
||||
// Boom! Create fire/plasma/etc
|
||||
for (let dx = -2; dx <= 2; dx++) {
|
||||
for (let dy = -2; dy <= 2; dy++) {
|
||||
if (Math.random() < 0.7 && isEmpty(pixel.x + dx, pixel.y + dy)) {
|
||||
createPixel(Math.random() < 0.5 ? "fire" : "pop", pixel.x + dx, pixel.y + dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
deletePixel(pixel.x, pixel.y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- SPELL DUST ---
|
||||
elements.nspell_dust = {
|
||||
color: "#00cfff",
|
||||
behavior: ["XX", "XX", "XX"],
|
||||
category: "noita",
|
||||
state: "gas",
|
||||
density: 1,
|
||||
temp: 20,
|
||||
tick: function(pixel) {
|
||||
if (Math.random() < 0.05) {
|
||||
deletePixel(pixel.x, pixel.y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// --- RAIN CALL ---
|
||||
elements.rain_call = {
|
||||
color: "#0077ff",
|
||||
behavior: behaviors.SUPPORT,
|
||||
category: "noita",
|
||||
state: "gas",
|
||||
hidden: true,
|
||||
|
||||
tick: function(pixel) {
|
||||
if (isNaN(pixel.life)) pixel.life = 0;
|
||||
pixel.life++;
|
||||
|
||||
if (pixel.life <= 60) {
|
||||
// Rain for 2 seconds
|
||||
if (Math.random() < 0.6) {
|
||||
let rx = pixel.x + Math.floor(Math.random() * 5 - 2);
|
||||
if (isEmpty(rx, pixel.y - 1)) {
|
||||
createPixel("water", rx, pixel.y - 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deletePixel(pixel.x, pixel.y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue