Download static/extras.js from Death242/void: direct link, hf CLI and curl.
- Browser
- Download file 18.1 kB
-
https://huggingface.co/spaces/Death242/void/resolve/main/static/extras.js
- Command line
-
hf download hf://spaces/Death242/void/static/extras.js
-
curl -L -o extras.js https://huggingface.co/spaces/Death242/void/resolve/main/static/extras.js
18.1 kB
| // Taxi jobs and hidden coins. | |
| // Taxi: press J (or Menu → Taxi job). Passengers wave from the kerb near you; stop next to one and they hop in, | |
| // the GPS shows where they're going, and the fare drops the longer you take. Back-to-back fares build a streak. | |
| // Coins: 80 spinning VOID coins: rooftops, piers, the bridge towers, the park, and since the city doubled the south | |
| // supertalls, the Ferris wheel, the El, the stunt park, Brooklyn, the highway and up the mountain trail; found ones stay found. | |
| import * as THREE from "three"; | |
| import { GRID } from "./city.js"; | |
| const store = { get(k) { try { return JSON.parse(localStorage.getItem(k)); } catch { return null; } }, set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch { } } }; | |
| const fmt = t => `${Math.floor(t / 60)}:${String(Math.floor(t % 60)).padStart(2, "0")}`; | |
| const ord = n => n + (n % 100 > 10 && n % 100 < 14 ? "th" : ["th", "st", "nd", "rd"][n % 10] || "th"); | |
| export function createExtras(W, api, nav, sound) { | |
| const { scene } = W, $ = id => document.getElementById(id); | |
| let sd = 77; const rnd = () => (sd = (sd * 16807) % 2147483647) / 2147483647; | |
| // ---- passengers: a little figure with a yellow beacon ---- | |
| const personMat = new THREE.MeshStandardMaterial({ color: 0x3060c0, roughness: .7 }), skin = new THREE.MeshStandardMaterial({ color: 0xd9a07a, roughness: .7 }); | |
| const beaconMat = new THREE.MeshBasicMaterial({ color: new THREE.Color(0xffd23d).multiplyScalar(1.8), transparent: true, opacity: .35, depthWrite: false, toneMapped: false, side: THREE.DoubleSide }); | |
| const fig = () => { const g = new THREE.Group(); const b = new THREE.Mesh(new THREE.CylinderGeometry(.25, .3, 1.2, 10).translate(0, .9, 0), personMat); const h = new THREE.Mesh(new THREE.SphereGeometry(.17, 12, 8).translate(0, 1.7, 0), skin); const arm = new THREE.Mesh(new THREE.BoxGeometry(.1, .6, .1).translate(0, .3, 0), personMat); arm.position.set(0, 1.35, .3); arm.rotation.x = -2.6; g.add(b, h, arm); const beam = new THREE.Mesh(new THREE.CylinderGeometry(1.4, 1.4, 30, 16, 1, true).translate(0, 15, 0), beaconMat); g.add(beam); g.userData.arm = arm; scene.add(g); return g; }; | |
| const waiting = [fig(), fig(), fig()], dropMark = new THREE.Mesh(new THREE.CylinderGeometry(5, 5, 40, 24, 1, true).translate(0, 20, 0), new THREE.MeshBasicMaterial({ color: new THREE.Color(0x3dff8a).multiplyScalar(1.6), transparent: true, opacity: .22, depthWrite: false, toneMapped: false, side: THREE.DoubleSide })); | |
| scene.add(dropMark); dropMark.visible = false; waiting.forEach(w => w.visible = false); | |
| const kerb = (x0, z0, rMin, rMax) => { // a sidewalk spot rMin..rMax metres away | |
| for (let t = 0; t < 40; t++) { | |
| const a = rnd() * 6.28, r = rMin + rnd() * (rMax - rMin); let x = x0 + Math.cos(a) * r, z = z0 + Math.sin(a) * r; | |
| const i = Math.round((x - GRID.X0) / GRID.AV), j = Math.round((z - GRID.Z0) / GRID.ST); | |
| if (i < 0 || i >= GRID.NX || j < 0 || j >= GRID.NZ) continue; | |
| if (rnd() < .5) x = GRID.ax(i) + (GRID.avW(i) / 2 + 2) * (rnd() < .5 ? 1 : -1); else z = GRID.sz(j) + (GRID.stW(j) / 2 + 2) * (rnd() < .5 ? 1 : -1); | |
| if (api.hitBox(x, z, .5, 1.2) || api.isWater(x, z)) continue; | |
| return [x, z]; | |
| } | |
| return null; | |
| }; | |
| const J = { on: false, fare: null, streak: 0, t: 0, earned: 0 }; | |
| function placeWaiting(p) { for (const w of waiting) { const s = kerb(p.x, p.z, 60, 260); if (s) { w.position.set(s[0], 0, s[1]); w.visible = true; } } } | |
| function toggle(on = !J.on) { | |
| const p = api.state; if (!p) return; | |
| J.on = on; J.fare = null; dropMark.visible = false; | |
| if (on) { placeWaiting(p); api.say("🚕 Taxi: pick up a passenger (yellow beams). J to stop", 4); } | |
| else { waiting.forEach(w => w.visible = false); if (nav.dest && nav.dest.taxi) nav.setDest(null); api.say(J.earned ? `🚕 Shift over: $${J.earned} earned` : "🚕 Taxi off", 3); J.earned = 0; J.streak = 0; } | |
| $("gJob").hidden = !on; | |
| } | |
| addEventListener("keydown", e => { if (e.code === "KeyJ" && api.active() && !e.repeat) toggle(); }); | |
| // ---- coins ---- | |
| const COINS = [], got = new Set(store.get("void.coins") || []); let coinMesh, glowMesh; | |
| const cm = new THREE.Matrix4(), cq = new THREE.Quaternion(), cv = new THREE.Vector3(), cs = new THREE.Vector3(), Y = new THREE.Vector3(0, 1, 0); | |
| { | |
| const spots = []; | |
| for (const h of W.city.helipads.slice(0, 8)) spots.push([h.x + 4, h.y + 1.5, h.z]); | |
| const roofs = W.buildings.filter(b => b.h > 12 && b.h < 60 && b.hw > 4).sort(() => rnd() - .5).slice(0, 14); | |
| for (const b of roofs) spots.push([b.x, b.h + 1.5, b.z]); | |
| for (const [x, y, z] of [[1210, 112, 15], [1570, 112, -15], [-1080, 1.5, -700 + 140 * 2], [-1080, 1.5, -700 + 140 * 7], [W.city.lake.x, 1.5, W.city.lake.z + W.city.lake.rz + 8], [W.city.lawn.x, 1.5, W.city.lawn.z], [-2600, 1.5, 0], [1900, 1.5, 0], [134, 6, 4.2], [16, 18.5, 2]]) spots.push([x, y, z]); | |
| while (spots.length < 40) { const s = kerb(rnd() * 1800 - 900, rnd() * 1300 - 650, 0, 40); if (s) spots.push([s[0], 1.5, s[1]]); } | |
| spots.length = 40; | |
| // the new half of the map: the tallest south roofs, the top of the Ferris wheel, the El platforms, over the stunt park ramps, | |
| // the gas station canopies down south, the highway, Brooklyn's streets, up the mountain trail and the summit | |
| const C = W.city, M = C.mountains; | |
| for (const h of C.helipads.filter(h => h.z > 900).sort((a, b) => b.y - a.y).slice(0, 8)) spots.push([h.x - 4, h.y + 1.5, h.z]); | |
| if (C.wheel) spots.push([C.wheel.x, C.wheel.y + C.wheel.R + 3, C.wheel.z]); | |
| if (C.el) for (const z of C.el.stations) spots.push([C.el.x, C.el.top + 1.6, z]); | |
| spots.push([-680, 11, 1440], [-620, 7, 1350], [-760, 5, 1405]); | |
| for (const s of C.stations) if (s.z > 900) spots.push([s.x, 7.6, s.z]); | |
| spots.push([2150, 1.5, 0], [2420, 1.5, 0]); | |
| for (let n = 0, t = 0; n < 4 && t < 200; t++) { const x = 1760 + rnd() * 700, z = rnd() * 1000 - 500; if (!api.hitBox(x, z, .5, 1.2) && !api.isWater(x, z) && Math.abs(z) > 14) { spots.push([x, 1.5, z]); n++; } } | |
| if (M) { for (let k = 2; k < M.trail.length; k += 2) { const [x, y, z] = M.trail[k]; spots.push([x, y + 1.6, z]); } spots.push([M.peak[0], M.peak[1] + 2, M.peak[2]]); } | |
| for (let t = 0; spots.length < 80 && t < 400; t++) { const s = kerb(rnd() * 1800 - 900, 900 + rnd() * 1150, 0, 40); if (s) spots.push([s[0], 1.5, s[1]]); } | |
| const gold = new THREE.MeshStandardMaterial({ color: 0xffc23d, metalness: 1, roughness: .2, emissive: 0x6a4a00, emissiveIntensity: .6 }); | |
| const geo = new THREE.CylinderGeometry(.8, .8, .14, 28).rotateX(Math.PI / 2); | |
| const vTex = (() => { const c = document.createElement("canvas"); c.width = c.height = 64; const g = c.getContext("2d"); g.fillStyle = "#ffc23d"; g.fillRect(0, 0, 64, 64); g.fillStyle = "#7a5200"; g.font = "900 44px sans-serif"; g.textAlign = "center"; g.fillText("V", 32, 48); const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t; })(); | |
| const face = new THREE.MeshStandardMaterial({ map: vTex, metalness: .8, roughness: .3, emissive: 0x6a4a00, emissiveIntensity: .5 }); | |
| const all = spots.slice(0, 80); | |
| coinMesh = new THREE.InstancedMesh(geo, [gold, face, face], all.length); | |
| glowMesh = new THREE.InstancedMesh(new THREE.IcosahedronGeometry(1.6, 1), new THREE.MeshBasicMaterial({ color: new THREE.Color(0xffc23d).multiplyScalar(1.4), transparent: true, opacity: .12, depthWrite: false, toneMapped: false }), all.length); | |
| for (const im of [coinMesh, glowMesh]) { im.frustumCulled = false; im.instanceMatrix.setUsage(THREE.DynamicDrawUsage); scene.add(im); } | |
| all.forEach(([x, y, z], k) => COINS.push({ id: k, x, y, z, on: !got.has(k) })); | |
| for (const c of COINS) { cm.compose(cv.set(c.x, c.y, c.z), cq.identity(), cs.setScalar(c.on ? 1 : 0)); coinMesh.setMatrixAt(c.id, cm); glowMesh.setMatrixAt(c.id, cm); } | |
| } | |
| // ---- boost pads: glowing chevrons on the avenues that kick you forward ---- | |
| const PADS = []; | |
| { | |
| const tex = (() => { const c = document.createElement("canvas"); c.width = 64; c.height = 128; const g = c.getContext("2d"); g.clearRect(0, 0, 64, 128); g.strokeStyle = "#3df5ff"; g.lineWidth = 9; g.lineCap = "round"; for (const y of [30, 70, 110]) { g.beginPath(); g.moveTo(10, y); g.lineTo(32, y - 22); g.lineTo(54, y); g.stroke(); } const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t; })(); | |
| const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, depthWrite: false, toneMapped: false, color: new THREE.Color(1.8, 1.8, 1.8), polygonOffset: true, polygonOffsetFactor: -4 }); | |
| for (let k = 0; k < 14; k++) { | |
| const axis = rnd() < .5, i = 1 + Math.floor(rnd() * (GRID.NX - 2)), j = 1 + Math.floor(rnd() * (GRID.NZ - 2)); | |
| const x = axis ? GRID.ax(i) + 4.5 : GRID.ax(i) + 60 + rnd() * 40, z = axis ? GRID.sz(j) + 25 + rnd() * 20 : GRID.sz(j) + 3, yaw = axis ? (rnd() < .5 ? Math.PI / 2 : -Math.PI / 2) : (rnd() < .5 ? 0 : Math.PI); | |
| if (api.isWater(x, z) || api.hitBox(x, z, .5, 2)) continue; | |
| const m = new THREE.Mesh(new THREE.PlaneGeometry(4, 8).rotateX(-Math.PI / 2), mat); m.position.set(x, .07, z); m.rotation.y = -yaw + Math.PI / 2; scene.add(m); | |
| PADS.push({ x, z, yaw, t: 0 }); | |
| } | |
| } | |
| // ---- speed cameras: drive past and your speed is recorded; beat your best for points ---- | |
| const TRAPS = [], best = store.get("void.traps") || {}; | |
| { | |
| const flashM = new THREE.MeshBasicMaterial({ color: new THREE.Color(1, 1, 1).multiplyScalar(4), toneMapped: false, transparent: true, opacity: 0 }), pole = new THREE.MeshStandardMaterial({ color: 0x2a2b30, metalness: .5, roughness: .4 }), boxM = new THREE.MeshStandardMaterial({ color: 0xf7c414, roughness: .5 }); | |
| const spots = [[GRID.ax(9), GRID.sz(3), "Midtown"], [GRID.ax(3), GRID.sz(15), "Park Ave"], [GRID.ax(11), GRID.sz(17), "Downtown"], [600, 0, "Broadway"], [GRID.ax(1), GRID.sz(6), "West Side"], [1400, 18, "Bridge"], | |
| [1880, 0, "Highway West"], [2380, 0, "Highway East"], [GRID.ax(5), GRID.sz(27), "South Ave"], [GRID.ax(10), GRID.sz(32), "Harbor"], [GRID.ax(2), GRID.sz(37), "Bay Ridge"], [GRID.ax(7), GRID.sz(22), "Midtown South"], [480, GRID.sz(20), "Under the El"]]; | |
| if (W.city.mountains) { const [x, , z] = W.city.mountains.trail[1]; spots.push([x, z, "Hill Climb"]); } | |
| spots.forEach(([x, z, name]) => { | |
| const g = new THREE.Group(); g.position.set(x + 11, 0, z + 9); scene.add(g); | |
| g.add(new THREE.Mesh(new THREE.CylinderGeometry(.12, .15, 5, 8).translate(0, 2.5, 0), pole)); const b = new THREE.Mesh(new THREE.BoxGeometry(.8, .5, .5), boxM); b.position.y = 5; g.add(b); | |
| const f = new THREE.Mesh(new THREE.SphereGeometry(.18, 8, 6), flashM.clone()); f.position.set(-.45, 5, 0); g.add(f); | |
| TRAPS.push({ x, z, name, f, cool: 0 }); | |
| }); | |
| } | |
| // ---- sky traffic: an airliner crossing high up, a slow blimp with a glowing VOID sign ---- | |
| const plane = new THREE.Group(), blimp = new THREE.Group(); | |
| { | |
| const w = new THREE.MeshStandardMaterial({ color: 0xe8e8ea, roughness: .4, metalness: .3 }); | |
| const body = new THREE.Mesh(new THREE.CylinderGeometry(1.6, 1.4, 34, 14).rotateZ(Math.PI / 2), w); plane.add(body); | |
| const wing = new THREE.Mesh(new THREE.BoxGeometry(6, .3, 36), w); wing.position.x = 1; plane.add(wing); | |
| const tail = new THREE.Mesh(new THREE.BoxGeometry(4, 6, .3), w); tail.position.set(-15, 3, 0); plane.add(tail); | |
| for (const [z, c] of [[18, 0x22ff66], [-18, 0xff2233]]) { const l = new THREE.Mesh(new THREE.SphereGeometry(.4, 8, 6), new THREE.MeshBasicMaterial({ color: new THREE.Color(c).multiplyScalar(3), toneMapped: false })); l.position.set(1, 0, z); plane.add(l); } | |
| scene.add(plane); | |
| const env = new THREE.Mesh(new THREE.SphereGeometry(8, 24, 16), new THREE.MeshStandardMaterial({ color: 0x1a1a20, roughness: .5, metalness: .2 })); env.scale.set(3.4, 1, 1); blimp.add(env); | |
| const signT = (() => { const c = document.createElement("canvas"); c.width = 512; c.height = 128; const g = c.getContext("2d"); g.fillStyle = "#000"; g.fillRect(0, 0, 512, 128); g.fillStyle = "#ff2a3a"; g.shadowColor = "#ff2a3a"; g.shadowBlur = 20; g.font = "900 86px Unbounded, Impact, sans-serif"; g.textAlign = "center"; g.fillText("VOID", 256, 100); const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t; })(); | |
| for (const s of [-1, 1]) { const p = new THREE.Mesh(new THREE.PlaneGeometry(30, 7.5), new THREE.MeshBasicMaterial({ map: signT, toneMapped: false, color: new THREE.Color(1.6, 1.6, 1.6) })); p.position.z = s * 8.1; p.rotation.y = s > 0 ? 0 : Math.PI; blimp.add(p); } | |
| const gond = new THREE.Mesh(new THREE.BoxGeometry(8, 2.4, 3), new THREE.MeshStandardMaterial({ color: 0x3a3b40 })); gond.position.y = -8.5; blimp.add(gond); | |
| scene.add(blimp); | |
| } | |
| let skyT = 0; | |
| return { | |
| toggle, pads: PADS, get job() { return J; }, coins: () => ({ got: got.size, total: COINS.length }), | |
| fastCams: () => TRAPS.filter(t => (best[t.name] || 0) >= 250).length, | |
| sky(dt, time) { | |
| skyT += dt; | |
| const a = (skyT * .012) % (Math.PI * 2); plane.position.set(Math.cos(a) * 2600, 620, Math.sin(a) * 1600); plane.rotation.y = -Math.atan2(Math.cos(a) * 1600, -Math.sin(a) * 2600); | |
| const b = (skyT * .004) % (Math.PI * 2); blimp.position.set(-200 + Math.cos(b) * 700, 230, -300 + Math.sin(b) * 500); blimp.rotation.y = -Math.atan2(Math.cos(b) * 500, -Math.sin(b) * 700); | |
| }, | |
| boosts(dt) { | |
| const p = api.state; if (!p || p.mode !== "drive" || p.y > 1) return; | |
| for (const b of PADS) { | |
| if (b.t > 0) { b.t -= dt; continue; } | |
| if (Math.abs(p.x - b.x) < 2.6 && Math.abs(p.z - b.z) < 4.6) { | |
| b.t = 1.5; const sp = Math.hypot(p.vel.x, p.vel.z) + 18; p.vel.set(Math.cos(p.yaw) * sp, 0, Math.sin(p.yaw) * sp); api.say("⚡ BOOST", 1.2); sound.whoosh && sound.whoosh(); | |
| } | |
| } | |
| for (const t of TRAPS) { | |
| t.f.material.opacity = Math.max(0, t.f.material.opacity - dt * 3); | |
| if (t.cool > 0) { t.cool -= dt; continue; } | |
| if (Math.hypot(p.x - t.x, p.z - t.z) < 14) { | |
| const kmh = Math.round(Math.hypot(p.vel.x, p.vel.z) * 3.6); if (kmh < 60) continue; | |
| t.cool = 4; t.f.material.opacity = 1; | |
| if (kmh > (best[t.name] || 0)) { best[t.name] = kmh; store.set("void.traps", best); api.addScore(Math.round(kmh * 2), `📸 ${t.name} speed camera: ${kmh} km/h (new best, ${TRAPS.filter(c => best[c.name]).length}/${TRAPS.length} cameras)`); } | |
| else api.say(`📸 ${t.name}: ${kmh} km/h (best ${best[t.name]})`, 2.5); | |
| } | |
| } | |
| }, | |
| update(dt, time) { | |
| const p = api.state; if (!p || !p.started) return; | |
| this.boosts(dt); | |
| // coins | |
| let near = 1e9; | |
| for (const c of COINS) { | |
| if (c.on && Math.abs(p.x - c.x) < 3 && Math.abs(p.z - c.z) < 3 && Math.abs(p.y + .6 - c.y) < 3.5) { | |
| c.on = false; got.add(c.id); store.set("void.coins", [...got]); api.addScore(250, `🪙 Coin ${got.size}/${COINS.length}`); sound.win(); | |
| } | |
| if (c.on) { near = Math.min(near, Math.hypot(p.x - c.x, p.z - c.z, (p.y - c.y) * .5)); cv.set(c.x, c.y + Math.sin(time * 2 + c.id) * .2, c.z); cq.setFromAxisAngle(Y, time * 2.4 + c.id); cs.set(1, 1, 1); } | |
| else cs.set(0, 0, 0); // found: shrunk to nothing | |
| cm.compose(cv, cq, cs); coinMesh.setMatrixAt(c.id, cm); glowMesh.setMatrixAt(c.id, cm); | |
| } | |
| coinMesh.instanceMatrix.needsUpdate = glowMesh.instanceMatrix.needsUpdate = true; | |
| $("gCoins").textContent = `🪙 ${got.size}/${COINS.length}` + (near < 150 ? ` · one ${Math.round(near)} m away` : ""); | |
| if (!J.on) return; | |
| const sp = Math.hypot(p.vel.x, p.vel.z); | |
| // waving passengers; the nearest one gets in when you stop beside it | |
| for (const w of waiting) { | |
| if (!w.visible) continue; | |
| w.userData.arm.rotation.z = Math.sin(time * 6) * .4; | |
| if (!J.fare && Math.hypot(p.x - w.position.x, p.z - w.position.z) < 9 && sp < 4 && p.mode === "drive") { | |
| const d = kerb(w.position.x, w.position.z, 350, 1100); if (!d) continue; | |
| const dist = Math.hypot(d[0] - w.position.x, d[1] - w.position.z); | |
| const j = Math.round((d[1] - GRID.Z0) / GRID.ST), street = j === 10 ? "Broadway" : `${ord(Math.abs(j - 10) * 7 + 14)} St`; | |
| J.fare = { x: d[0], z: d[1], t: dist / 11 + 25, max: 300 + Math.round(dist * .6), street }; | |
| waiting.forEach(o => o.visible = false); dropMark.position.set(d[0], 0, d[1]); dropMark.visible = true; | |
| nav.setDest({ name: `🚕 ${street}`, x: d[0], z: d[1], taxi: true }); | |
| api.say(`🚕 "Take me to ${street}, quick!"`, 3); sound.pop(); | |
| } | |
| } | |
| if (J.fare) { | |
| const f = J.fare; f.t -= dt; | |
| const pay = Math.max(50, Math.round(f.max * Math.min(1, .35 + f.t / 40))); | |
| $("gJob").textContent = `🚕 To ${f.street} · ${fmt(Math.max(0, f.t))} · $${pay}${J.streak > 1 ? ` · streak ×${J.streak}` : ""}`; | |
| if (Math.hypot(p.x - f.x, p.z - f.z) < 12 && sp < 4) { | |
| J.streak = f.t > 0 ? J.streak + 1 : 0; const total = pay + (J.streak > 1 ? J.streak * 100 : 0); J.earned += total; | |
| api.addScore(total, `🚕 Fare $${total}${J.streak > 1 ? ` (streak ×${J.streak})` : ""}`); J.fare = null; dropMark.visible = false; if (nav.dest && nav.dest.taxi) nav.setDest(null); placeWaiting(p); | |
| } else if (f.t < -20) { api.say("🚕 The passenger gave up and got out", 3); J.fare = null; J.streak = 0; dropMark.visible = false; if (nav.dest && nav.dest.taxi) nav.setDest(null); placeWaiting(p); } | |
| } else { | |
| $("gJob").textContent = `🚕 Find a passenger (yellow beams)${J.earned ? ` · $${J.earned} so far` : ""}`; | |
| if (waiting.every(w => !w.visible || Math.hypot(p.x - w.position.x, p.z - w.position.z) > 600)) placeWaiting(p); | |
| } | |
| }, | |
| }; | |
| } | |