1) What is a mod in this game?
A mod is just a JavaScript file that runs in the browser and calls two functions:
-
registerMod({...})→ tells the game your mod name/author/version -
registerTNT({...})→ adds new TNT types to the TNT dropdown
Mods are loaded 3 ways:
-
Paste code into the mod editor → Load code
-
Load a
.jsfile → Load file -
Load from URL → Load URL
2) The minimum template
Explain this is the smallest working mod:
registerMod({
id: “my_mod”,
name: “My Mod”,
author: “YourName”,
version: “1.0.0”,
description: “What this mod does”
});
registerTNT({
id: “my_mod:test_tnt”,
name: “Test TNT”,
desc: “Basic explosion”,
color: “#ff3333”,
onDetonate(ctx){
const p = ctx.ent.power;
const r = ctx.strongBlast(ctx.ent.x, ctx.ent.y, p);
ctx.spawnExplosionVFX(ctx.ent.x, ctx.ent.y, r, p);
ctx.applyExplosionDamage(ctx.ent.x, ctx.ent.y, r, p);
}
});
3) What is ctx?
Tell people: ctx is your toolbox (engine API). The most used ones:
-
ctx.ent.x / ctx.ent.y / ctx.ent.power(where TNT is + selected multiplier) -
ctx.strongBlast(x,y,power)(terrain destruction) -
ctx.spawnExplosionVFX(x,y,r,power)(visual) -
ctx.applyExplosionDamage(x,y,r,power)(hurts villagers) -
ctx.fillCircle(x,y,r,tile)(spawn water/lava/etc) -
ctx.carveCircle(x,y,r)(delete blocks) -
ctx.spawnProjectile({ ... })(for airstrike / flying TNT)
4) Power and “TNT x150”
Important tip: your TNT can scale using either:
-
multiplier math (
p * 150) -
or the built-in buster mapping (only for those special IDs)
So in a custom mod: do const p = ctx.ent.power * 150;
5) Tile IDs cheat-sheet (for freezing/lava stuff)
From your game:
-
0 AIR -
3 WATER -
4 LAVA -
10 SNOW -
12 ICE
6) Debugging
Tell them:
-
If it doesn’t show up → check console errors
-
Duplicate IDs will crash load
-
Mod ID and TNT ID must be unique
Example mod pack: x150 TNT + Volcano + Ice Age + Doomsday (airstrike x500)
Put this as the main “example code” in your tutorial:
registerMod({
id: “tutorial_pack”,
name: “Tutorial Pack”,
author: “rolikthedev”,
version: “1.0.0”,
description: “Examples: TNT x150, Volcano, Ice Age, Doomsday airstrikes”
});
// Tile IDs (match your v0.3 file)
const AIR = 0;
const WATER = 3;
const LAVA = 4;
const FIRE = 5;
const SNOW = 10;
const ICE = 12;
// ———- 1) TNT x150 ———-
registerTNT({
id: “tutorial_pack:tnt_x150”,
name: “TNT x150”,
desc: “Simple: just multiply power by 150.”,
color: “#ff4d4d”,
onDetonate(ctx){
const p = ctx.ent.power * 150;
const r = ctx.strongBlast(ctx.ent.x, ctx.ent.y, p);
ctx.spawnExplosionVFX(ctx.ent.x, ctx.ent.y, r, p);
ctx.applyExplosionDamage(ctx.ent.x, ctx.ent.y, r, p);
}
});
// ———- helper: freeze circle ———-
function freezeCircle(ctx, cx, cy, r, strength=1.0){
const r2 = r*r;
for (let y = cy – r; y <= cy + r; y++){
for (let x = cx – r; x <= cx + r; x++){
if (!ctx.inBounds(x,y)) continue;
const d2 = ctx.dist2(x,y,cx,cy);
if (d2 > r2) continue;
const t = 1 – (d2 / r2);
const chance = (0.22 + 0.78*t) * strength;
if (Math.random() > chance) continue;
const v = ctx.getTile(x,y);
if (v === FIRE) { ctx.setTile(x,y, AIR); continue; }
if (v === WATER){ ctx.setTile(x,y, ICE); continue; }
if (v === LAVA) { ctx.setTile(x,y, 2); continue; } // STONE
// frost air pockets by dropping snow in crater edges:
if (v === AIR && Math.random() < 0.06 * strength) ctx.setTile(x,y, SNOW);
}
}
}
// ———- 2) Volcano TNT (spawns lava TNT as airstrikes) ———-
registerTNT({
id: “tutorial_pack:volcano”,
name: “Volcano TNT”,
desc: “Creates a crater + rains lava bombs from sky.”,
color: “#ff8a1a”,
onDetonate(ctx){
const p = ctx.ent.power;
// crater
const r = ctx.strongBlast(ctx.ent.x, ctx.ent.y, Math.max(1, Math.floor(p * 2)));
ctx.spawnExplosionVFX(ctx.ent.x, ctx.ent.y, r, p);
ctx.applyExplosionDamage(ctx.ent.x, ctx.ent.y, r, p);
// a lava pool in the middle
ctx.fillCircle(ctx.ent.x, ctx.ent.y, ctx.clamp(Math.floor(r * 0.45), 3, 90), LAVA);
// lava bombs (projectiles) -> detonate as core:lava when they land
const count = ctx.clamp(10 + Math.floor(Math.sqrt(p) * 10), 10, 70);
const spread = ctx.clamp(14 + Math.floor(r * 0.9), 14, 70);
for (let i=0;i<count;i++){
const x = ctx.clamp(ctx.ent.x + ctx.randInt(-spread, spread), 0, ctx.W-1);
const y = ctx.randInt(0, 6);
ctx.spawnProjectile({
x: x + 0.2,
y: y,
vx: (Math.random()-0.5) * 0.22,
vy: 0.28 + Math.random()*0.42,
fuse: 130 + ctx.randInt(0, 140), // long fuse so they reach ground
typeId: “core:lava”, // uses your built-in Lava TNT
power: Math.max(1, Math.floor(p / 2)),
detonateOnImpact: true
});
}
}
});
// ———- 3) Ice Age TNT ———-
registerTNT({
id: “tutorial_pack:ice_age”,
name: “Ice Age TNT”,
desc: “Freezes water into ice + snows the area.”,
color: “#66ccff”,
onDetonate(ctx){
const p = ctx.ent.power;
const r = ctx.clamp(Math.floor(ctx.powerToRadius(p) * 1.35), 10, 90);
// small crater
ctx.carveCircle(ctx.ent.x, ctx.ent.y, ctx.clamp(Math.floor(r * 0.10), 2, 14));
// freeze effect
freezeCircle(ctx, ctx.ent.x, ctx.ent.y, r, 1.25);
// visuals
ctx.spawnFireworkBurst(ctx.ent.x, ctx.ent.y, Math.max(1, Math.floor(Math.sqrt(p))));
ctx.spawnExplosionVFX(ctx.ent.x, ctx.ent.y, Math.max(6, Math.floor(r*0.25)), 1);
// chill damage (lighter)
ctx.applyExplosionDamage(ctx.ent.x, ctx.ent.y, ctx.clamp(Math.floor(r*0.6), 8, 90), Math.max(1, Math.floor(p/3)));
}
});
// ———- 4) Doomsday TNT (shoots TNT x500 as airstrikes) ———-
registerTNT({
id: “tutorial_pack:doomsday”,
name: “Doomsday TNT”,
desc: “Rains TNT x500 from the sky. Run.”,
color: “#ffffff”,
onDetonate(ctx){
const p = ctx.ent.power;
// small “warning” blast
const rr = ctx.clamp(Math.floor(ctx.powerToRadius(p)*0.25), 4, 35);
ctx.carveCircle(ctx.ent.x, ctx.ent.y, rr);
ctx.spawnExplosionVFX(ctx.ent.x, ctx.ent.y, rr, 1);
const count = ctx.clamp(10 + Math.floor(Math.sqrt(p) * 6), 10, 40);
const spread = ctx.clamp(18 + Math.floor(ctx.powerToRadius(p) * 1.6), 18, 90);
for (let i=0;i<count;i++){
const x = ctx.clamp(ctx.ent.x + ctx.randInt(-spread, spread), 0, ctx.W-1);
const y = ctx.randInt(0, 6);
// We spawn a NORMAL TNT projectile but with power multiplied to x500
ctx.spawnProjectile({
x: x + 0.2,
y: y,
vx: (Math.random()-0.5) * 0.18,
vy: 0.25 + Math.random()*0.35,
fuse: 150 + ctx.randInt(0, 160),
typeId: “core:normal”,
power: Math.max(1, p * 500),
detonateOnImpact: true
});
}
}
});
Extra: how to explain “airstrike projectile” in words
In your tutorial, describe it like:
-
You aren’t placing TNT blocks directly
-
You’re spawning a projectile that falls with gravity
-
It has a fuse
-
It detonates on impact or when fuse hits zero
Fields that matter:
-
x, ystart position (spawn near top of map for airstrike) -
vx, vyvelocity (small random sideways looks cool) -
fuseshould be long (like 130–300) so it reaches the ground -
typeIdwhich TNT it will detonate as -
powerhow strong it will be -
detonateOnImpact: truemakes it explode on ground hit
