Spaces:
Sleeping
Sleeping
| // Seeds the system account, catalog and the pre-made official games. | |
| // Worlds are generated procedurally — no asset files anywhere. | |
| const crypto = require('crypto'); | |
| const { get, saveNow, uid } = require('./db'); | |
| const CATALOG = require('./catalog'); | |
| let nextPart = 1; | |
| function P(type, x, y, z, sx, sy, sz, color, extra) { | |
| return Object.assign( | |
| { id: 'p' + nextPart++, type, x, y, z, sx, sy, sz, color }, | |
| extra || {} | |
| ); | |
| } | |
| function baseplate(parts, size, color) { | |
| parts.push(P('block', 0, -0.5, 0, size, 1, size, color || '#3aa14f')); | |
| } | |
| // ---------- Game 1: Tower of Bother (obby) ---------- | |
| function buildObby() { | |
| const parts = []; | |
| nextPart = 1; | |
| parts.push(P('block', 0, -0.5, 0, 24, 1, 24, '#3aa14f')); | |
| parts.push(P('spawn', 0, 0.25, 8, 6, 0.5, 6, '#cfd8dc')); | |
| const palette = ['#e54b4b', '#f0a020', '#ffd23f', '#3aa14f', '#2a7de1', '#8a4fff']; | |
| let z = -2, y = 0, stage = 0; | |
| const rnd = (() => { let s = 1234567; return () => (s = (s * 16807) % 2147483647) / 2147483647; })(); | |
| for (let i = 0; i < 38; i++) { | |
| const c = palette[i % palette.length]; | |
| const w = 4 - Math.min(2, Math.floor(i / 12)); | |
| const gap = 4 + Math.floor(i / 10); | |
| z -= gap + w / 2; | |
| y += 0.8 + rnd() * 1.4; | |
| const x = Math.round((rnd() - 0.5) * 8); | |
| if (i % 7 === 3) { | |
| // moving platform hop | |
| parts.push(P('mover', x, y, z, w, 1, w, '#9b59b6', { dx: 6, dy: 0, dz: 0, period: 3.5 })); | |
| } else if (i % 7 === 5) { | |
| // kill brick flanked jump | |
| parts.push(P('block', x, y, z, w + 4, 1, w, c)); | |
| parts.push(P('kill', x, y + 0.55, z, w + 4, 0.2, w / 2, '#d40000')); | |
| parts.push(P('block', x, y + 0.55, z - w / 2 + 0.4, w + 4, 0.2, 0.8, c)); | |
| } else if (i % 11 === 9) { | |
| parts.push(P('bounce', x, y, z, w, 1, w, '#ffe14d')); | |
| y += 4; | |
| } else { | |
| parts.push(P('block', x, y, z, w, 1, w, c)); | |
| } | |
| if (i > 0 && i % 6 === 0) { | |
| stage++; | |
| parts.push(P('checkpoint', x, y + 0.75, z, 2.5, 0.5, 2.5, '#19c2ff', { stage })); | |
| } | |
| if (i % 5 === 2) { | |
| parts.push(P('coin', x, y + 2, z, 1, 1, 1, '#ffd700')); | |
| } | |
| } | |
| // summit | |
| z -= 8; | |
| y += 2; | |
| parts.push(P('block', 0, y, z, 10, 1, 10, '#ffffff')); | |
| parts.push(P('goal', 0, y + 1, z, 4, 2, 4, '#19ff8c')); | |
| parts.push(P('deco', 0, y + 6, z, 1, 8, 1, '#e54b4b')); | |
| return parts; | |
| } | |
| // ---------- Game 2: Glimmer Rush (coin collect) ---------- | |
| function buildCollect() { | |
| const parts = []; | |
| nextPart = 1; | |
| baseplate(parts, 80, '#3d9650'); | |
| parts.push(P('spawn', 0, 0.25, 0, 8, 0.5, 8, '#cfd8dc')); | |
| const rnd = (() => { let s = 424242; return () => (s = (s * 16807) % 2147483647) / 2147483647; })(); | |
| // scattered terrain blocks + ramps of stacked slabs | |
| for (let i = 0; i < 26; i++) { | |
| const x = Math.round((rnd() - 0.5) * 64); | |
| const z = Math.round((rnd() - 0.5) * 64); | |
| if (Math.abs(x) < 8 && Math.abs(z) < 8) continue; | |
| const h = 1 + Math.floor(rnd() * 4); | |
| const w = 3 + Math.floor(rnd() * 6); | |
| const cols = ['#7a5230', '#8d6e63', '#6d4c41', '#5d8a3c']; | |
| parts.push(P('block', x, h / 2, z, w, h, w, cols[i % 4])); | |
| if (rnd() > 0.4) parts.push(P('coin', x, h + 1.2, z, 1, 1, 1, '#ffd700')); | |
| } | |
| // floating coin rings | |
| for (let r = 0; r < 3; r++) { | |
| const cy = 6 + r * 5; | |
| for (let a = 0; a < 8; a++) { | |
| const ang = (a / 8) * Math.PI * 2; | |
| parts.push(P('coin', Math.cos(ang) * (14 + r * 8), cy, Math.sin(ang) * (14 + r * 8), 1, 1, 1, '#ffd700')); | |
| } | |
| parts.push(P('mover', 14 + r * 8, cy - 1.5, 0, 4, 1, 4, '#9b59b6', { dx: 0, dy: 4, dz: 0, period: 4 + r })); | |
| } | |
| // bounce pads to reach rings | |
| parts.push(P('bounce', 10, 0.55, 10, 4, 1, 4, '#ffe14d')); | |
| parts.push(P('bounce', -10, 0.55, -10, 4, 1, 4, '#ffe14d')); | |
| parts.push(P('speed', 0, 0.05, 20, 6, 0.1, 12, '#19c2ff')); | |
| // central lake | |
| parts.push(P('water', 28, 0.5, -28, 20, 3, 20, '#2a7de1')); | |
| for (let a = 0; a < 5; a++) parts.push(P('coin', 24 + a * 2, 1.5, -24 - a * 2, 1, 1, 1, '#ffd700')); | |
| return parts; | |
| } | |
| // ---------- Game 3: Zap Tag (tag arena) ---------- | |
| function buildTag() { | |
| const parts = []; | |
| nextPart = 1; | |
| baseplate(parts, 56, '#4a6b8a'); | |
| parts.push(P('spawn', 0, 0.25, 0, 10, 0.5, 10, '#cfd8dc')); | |
| // arena walls | |
| const W = 56; | |
| parts.push(P('block', 0, 2, -W / 2, W, 4, 1, '#37474f')); | |
| parts.push(P('block', 0, 2, W / 2, W, 4, 1, '#37474f')); | |
| parts.push(P('block', -W / 2, 2, 0, 1, 4, W, '#37474f')); | |
| parts.push(P('block', W / 2, 2, 0, 1, 4, W, '#37474f')); | |
| // cover pillars + catwalks | |
| const spots = [[-16, -16], [16, -16], [-16, 16], [16, 16], [0, -20], [0, 20], [-20, 0], [20, 0]]; | |
| for (const [x, z] of spots) { | |
| parts.push(P('block', x, 2.5, z, 4, 5, 4, '#ff8f3c')); | |
| } | |
| parts.push(P('block', 0, 5.5, -16, 36, 1, 4, '#cfd8dc')); | |
| parts.push(P('block', 0, 5.5, 16, 36, 1, 4, '#cfd8dc')); | |
| parts.push(P('block', -16, 5.5, 0, 4, 1, 28, '#cfd8dc')); | |
| parts.push(P('block', 16, 5.5, 0, 4, 1, 28, '#cfd8dc')); | |
| parts.push(P('bounce', 0, 0.55, -8, 3, 1, 3, '#ffe14d')); | |
| parts.push(P('bounce', 0, 0.55, 8, 3, 1, 3, '#ffe14d')); | |
| parts.push(P('speed', -24, 0.05, 0, 4, 0.1, 40, '#19c2ff')); | |
| parts.push(P('speed', 24, 0.05, 0, 4, 0.1, 40, '#19c2ff')); | |
| // teleporter pair for escapes | |
| parts.push(P('tele', -24, 0.3, -24, 3, 0.6, 3, '#8a4fff', { channel: 1 })); | |
| parts.push(P('tele', 24, 0.3, 24, 3, 0.6, 3, '#8a4fff', { channel: 1 })); | |
| return parts; | |
| } | |
| // ---------- Game 4: Blockworks Plaza (social sandbox / showcase) ---------- | |
| function buildPlaza() { | |
| const parts = []; | |
| nextPart = 1; | |
| baseplate(parts, 72, '#9e9e9e'); | |
| parts.push(P('spawn', 0, 0.25, 24, 8, 0.5, 8, '#cfd8dc')); | |
| // fountain | |
| parts.push(P('block', 0, 0.5, 0, 12, 1, 12, '#b0bec5')); | |
| parts.push(P('water', 0, 1.4, 0, 10, 0.8, 10, '#2a7de1')); | |
| parts.push(P('deco', 0, 3, 0, 2, 4, 2, '#b0bec5')); | |
| parts.push(P('mover', 0, 6, 0, 1.5, 1.5, 1.5, '#19c2ff', { dx: 0, dy: 2, dz: 0, period: 2 })); | |
| // four little shops around plaza | |
| const shops = [ | |
| [-24, -24, '#e54b4b'], [24, -24, '#2a7de1'], [-24, 24, '#f0a020'], [24, 24, '#8a4fff'], | |
| ]; | |
| for (const [x, z, c] of shops) { | |
| parts.push(P('block', x, 2.5, z, 10, 5, 10, c)); // walls | |
| parts.push(P('block', x, 5.4, z, 12, 0.8, 12, '#37474f')); // roof | |
| parts.push(P('block', x, 1, z + 5.2, 3, 2, 0.4, '#00000000' === c ? c : '#262626')); // doorway mark | |
| } | |
| // park strip with trees (trunk + leaf cubes) | |
| for (let i = 0; i < 6; i++) { | |
| const x = -30 + i * 12, z = -8; | |
| parts.push(P('deco', x, 1.5, z, 1, 3, 1, '#6d4c41')); | |
| parts.push(P('deco', x, 4, z, 3.5, 3, 3.5, '#2e7d32')); | |
| } | |
| // dance floor of colored speed pads | |
| for (let i = 0; i < 4; i++) | |
| for (let j = 0; j < 4; j++) | |
| parts.push(P('block', -6 + i * 4, 0.05, -26 + j * 4, 4, 0.1, | |
| 4, ['#e54b4b', '#ffd23f', '#19c2ff', '#19ff8c'][(i + j) % 4])); | |
| // observation tower with teleporter up | |
| parts.push(P('block', 30, 7, 0, 6, 14, 6, '#78909c')); | |
| parts.push(P('block', 30, 14.5, 0, 10, 1, 10, '#cfd8dc')); | |
| parts.push(P('tele', 30, 0.3, 6, 3, 0.6, 3, '#8a4fff', { channel: 1 })); | |
| parts.push(P('tele', 30, 15.3, 0, 3, 0.6, 3, '#8a4fff', { channel: 1 })); | |
| parts.push(P('coin', 30, 16.5, 0, 1, 1, 1, '#ffd700')); | |
| return parts; | |
| } | |
| function makeGame(db, owner, def) { | |
| const id = uid(10); | |
| db.games[id] = { | |
| id, | |
| name: def.name, | |
| desc: def.desc, | |
| genre: def.genre, | |
| ownerId: owner.id, | |
| collaborators: [], | |
| public: true, | |
| official: true, | |
| created: Date.now(), | |
| updated: Date.now(), | |
| visits: 0, | |
| likes: [], | |
| favorites: [], | |
| config: Object.assign( | |
| { maxPlayers: 12, mode: 'sandbox', gravity: 50, walkSpeed: 12, jumpPower: 20, respawnTime: 2 }, | |
| def.config | |
| ), | |
| world: { parts: def.parts }, | |
| }; | |
| return db.games[id]; | |
| } | |
| function seed() { | |
| const db = get(); | |
| db.catalog = CATALOG; | |
| if (!Object.values(db.users).some((u) => u.system)) { | |
| const id = uid(10); | |
| db.users[id] = { | |
| id, | |
| username: 'Glint', | |
| system: true, | |
| // random unguessable password — nobody logs in as the system user | |
| passhash: crypto.randomBytes(32).toString('hex'), | |
| salt: crypto.randomBytes(16).toString('hex'), | |
| created: Date.now(), | |
| glimmers: 0, | |
| avatar: { | |
| bodyColors: { head: '#ffd23f', torso: '#19c2ff', leftArm: '#ffd23f', rightArm: '#ffd23f', leftLeg: '#37474f', rightLeg: '#37474f' }, | |
| hat: 'hat_crown', | |
| face: 'face_cool', | |
| }, | |
| inventory: CATALOG.map((c) => c.id), | |
| friends: [], | |
| friendRequests: [], | |
| stats: { visitsReceived: 0, joins: 0 }, | |
| }; | |
| } | |
| const sys = Object.values(db.users).find((u) => u.system); | |
| if (!Object.values(db.games).some((g) => g.official)) { | |
| makeGame(db, sys, { | |
| name: 'Tower of Bother', | |
| desc: 'The classic climb. 38 platforms of pure spite. Checkpoints are your friends; the red bricks are not. First to the green summit pad wins glory.', | |
| genre: 'Obby', | |
| config: { mode: 'obby', maxPlayers: 16 }, | |
| parts: buildObby(), | |
| }); | |
| makeGame(db, sys, { | |
| name: 'Glimmer Rush', | |
| desc: 'Coins everywhere — on hills, in rings, over the lake. Grab the most before everyone else does. Coins respawn, greed is forever.', | |
| genre: 'Collecting', | |
| config: { mode: 'collect', maxPlayers: 12 }, | |
| parts: buildCollect(), | |
| }); | |
| makeGame(db, sys, { | |
| name: 'Zap Tag', | |
| desc: 'One player is Zapped. Touch someone to pass it on. Teleporters, speed lanes and bounce pads keep the chase spicy. Don\'t be holding the zap.', | |
| genre: 'Tag', | |
| config: { mode: 'tag', maxPlayers: 10, walkSpeed: 14 }, | |
| parts: buildTag(), | |
| }); | |
| makeGame(db, sys, { | |
| name: 'Blockworks Plaza', | |
| desc: 'Hang out, dance on the light floor, ride the fountain, take the teleporter up the tower. A chill social plaza — bring friends.', | |
| genre: 'Social', | |
| config: { mode: 'sandbox', maxPlayers: 20 }, | |
| parts: buildPlaza(), | |
| }); | |
| } | |
| saveNow(); | |
| } | |
| module.exports = { seed }; | |