Static site
Browse files- index.html +4 -3
- static/game.js +0 -0
- static/goals.js +4 -1
- static/main.js +12 -6
index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
| 8 |
<meta name="theme-color" content="#10131b">
|
| 9 |
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;800;900&family=Inter:wght@400;500;600;700&family=Noto+Kufi+Arabic:wght@700&display=swap" rel="stylesheet">
|
| 11 |
-
<link rel="stylesheet" href="/static/style.css?v=
|
| 12 |
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Crect width=%2764%27 height=%2764%27 rx=%2714%27 fill=%27%23050506%27/%3E%3Ccircle cx=%2732%27 cy=%2732%27 r=%2716%27 fill=%27none%27 stroke=%27%23fff%27 stroke-width=%275%27/%3E%3C/svg%3E">
|
| 13 |
<script type="importmap">
|
| 14 |
{ "imports": {
|
|
@@ -158,6 +158,7 @@
|
|
| 158 |
<label class="set">Living city (day/night + weather change)<span class="seg" data-set="cycle"><button data-v="off">Off</button><button data-v="on">On</button></span></label>
|
| 159 |
<label class="set">Handling<span class="seg" data-set="handling"><button data-v="arcade">Arcade</button><button data-v="realistic">Realistic</button></span></label>
|
| 160 |
<label class="set">Screen shake and speed blur<span class="seg" data-set="motion"><button data-v="on">On</button><button data-v="off">Off</button></span></label>
|
|
|
|
| 161 |
<label class="set">Text size<span class="seg" data-set="text"><button data-v="normal">Normal</button><button data-v="large">Large</button></span></label>
|
| 162 |
<label class="set">Touch steering<span class="seg" data-set="steer"><button data-v="stick">Joystick</button><button data-v="buttons">Buttons</button></span></label>
|
| 163 |
<div class="set save-row">Secret code<span><input id="gCode" maxlength="16" placeholder="type a code" autocomplete="off"><button class="pill" id="gCodeGo">Enter</button></span></div>
|
|
@@ -168,7 +169,7 @@
|
|
| 168 |
<section class="wide"><h4>Contracts</h4><div class="tiles con-tiles" id="gContracts"></div></section>
|
| 169 |
<section class="wide"><h4>Routes and ghosts</h4><div class="routes" id="gRoutes"></div></section>
|
| 170 |
<section class="wide"><h4>Challenges</h4><div class="goals" id="gGoals"></div></section>
|
| 171 |
-
<section class="wide"><h4>Controls</h4><p class="small"><b>Drive:</b> W A S D or arrows · Space handbrake/drift · on a jump: A/D spin, X (Drift) backflip · Shift nitro · F fly · L land · C camera · R reset · H horn · T time · M GPS · O autopilot · Enter starts a race at a 🏁 marker · B drops a gate in the route builder · Y hoverboard
|
| 172 |
</div>
|
| 173 |
<div class="menu-foot"><button class="pill" id="gExit">Exit to home</button></div>
|
| 174 |
</div>
|
|
@@ -215,6 +216,6 @@
|
|
| 215 |
<div id="toast" role="status"></div>
|
| 216 |
<noscript><p style="position:fixed;inset:auto 0 0;padding:16px;background:#111;color:#fff;text-align:center">VOID needs JavaScript for the 3D car.</p></noscript>
|
| 217 |
<script src="/static/hub.js" data-app="void" defer></script>
|
| 218 |
-
<script type="module" src="/static/main.js?v=
|
| 219 |
</body>
|
| 220 |
</html>
|
|
|
|
| 8 |
<meta name="theme-color" content="#10131b">
|
| 9 |
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;800;900&family=Inter:wght@400;500;600;700&family=Noto+Kufi+Arabic:wght@700&display=swap" rel="stylesheet">
|
| 11 |
+
<link rel="stylesheet" href="/static/style.css?v=1793285738">
|
| 12 |
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Crect width=%2764%27 height=%2764%27 rx=%2714%27 fill=%27%23050506%27/%3E%3Ccircle cx=%2732%27 cy=%2732%27 r=%2716%27 fill=%27none%27 stroke=%27%23fff%27 stroke-width=%275%27/%3E%3C/svg%3E">
|
| 13 |
<script type="importmap">
|
| 14 |
{ "imports": {
|
|
|
|
| 158 |
<label class="set">Living city (day/night + weather change)<span class="seg" data-set="cycle"><button data-v="off">Off</button><button data-v="on">On</button></span></label>
|
| 159 |
<label class="set">Handling<span class="seg" data-set="handling"><button data-v="arcade">Arcade</button><button data-v="realistic">Realistic</button></span></label>
|
| 160 |
<label class="set">Screen shake and speed blur<span class="seg" data-set="motion"><button data-v="on">On</button><button data-v="off">Off</button></span></label>
|
| 161 |
+
<label class="set">Hoverboard<span class="seg" data-set="board"><button data-v="ion">Ion</button><button data-v="blaze">Blaze</button><button data-v="toxic">Toxic</button><button data-v="violet">Violet</button><button data-v="gold">Gold</button><button data-v="ghost">Ghost</button></span></label>
|
| 162 |
<label class="set">Text size<span class="seg" data-set="text"><button data-v="normal">Normal</button><button data-v="large">Large</button></span></label>
|
| 163 |
<label class="set">Touch steering<span class="seg" data-set="steer"><button data-v="stick">Joystick</button><button data-v="buttons">Buttons</button></span></label>
|
| 164 |
<div class="set save-row">Secret code<span><input id="gCode" maxlength="16" placeholder="type a code" autocomplete="off"><button class="pill" id="gCodeGo">Enter</button></span></div>
|
|
|
|
| 169 |
<section class="wide"><h4>Contracts</h4><div class="tiles con-tiles" id="gContracts"></div></section>
|
| 170 |
<section class="wide"><h4>Routes and ghosts</h4><div class="routes" id="gRoutes"></div></section>
|
| 171 |
<section class="wide"><h4>Challenges</h4><div class="goals" id="gGoals"></div></section>
|
| 172 |
+
<section class="wide"><h4>Controls</h4><p class="small"><b>Drive:</b> W A S D or arrows · Space handbrake/drift · on a jump: A/D spin, X (Drift) backflip · Shift nitro · F fly · L land · C camera · R reset · H horn · T time · M GPS · O autopilot · Enter starts a race at a 🏁 marker · B drops a gate in the route builder · Y hoverboard: hold Space and let go to ollie, Space again in the air to double jump; in the air A/D spin, X kickflip, Q heelflip, Shift grab; land on a rail to grind (stunt park, boardwalk, mountain foot). <b>Mountains:</b> take the highway over the bridge and through Brooklyn; trucks from VOID Motors handle the rough ground best. <b>On foot:</b> stop and press E to get out · WASD walk · Shift run · Space jump · E next to any car to drive it (taking traffic cars brings the police). VOID Motors in the park sells 50 cars. <b>Look:</b> click the game, then move the mouse. <b>Phone:</b> joystick on the left, pedals on the right, drag anywhere to look. <b>Fuel:</b> about 10 min driving, 5 min flying; stop at ⛽ to fill up and repair, or drive through a green 🔧.</p></section>
|
| 173 |
</div>
|
| 174 |
<div class="menu-foot"><button class="pill" id="gExit">Exit to home</button></div>
|
| 175 |
</div>
|
|
|
|
| 216 |
<div id="toast" role="status"></div>
|
| 217 |
<noscript><p style="position:fixed;inset:auto 0 0;padding:16px;background:#111;color:#fff;text-align:center">VOID needs JavaScript for the 3D car.</p></noscript>
|
| 218 |
<script src="/static/hub.js" data-app="void" defer></script>
|
| 219 |
+
<script type="module" src="/static/main.js?v=1793285738"></script>
|
| 220 |
</body>
|
| 221 |
</html>
|
static/game.js
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
static/goals.js
CHANGED
|
@@ -15,10 +15,13 @@ export const GOALS = [
|
|
| 15 |
{ id: "coins", icon: "🪙", name: "Collector", text: "Find 20 hidden coins", stat: "coins", need: 20 },
|
| 16 |
{ id: "taxi", icon: "🚕", name: "Cabbie", text: "Complete 5 taxi fares", stat: "fares", need: 5 },
|
| 17 |
{ id: "tuner", icon: "🔧", name: "Tuner", text: "Buy 4 garage upgrades", stat: "upg", need: 4 },
|
|
|
|
|
|
|
|
|
|
| 18 |
{ id: "brooklyn", icon: "🌉", name: "Bridge and tunnel", text: "Cross the bridge to Brooklyn", stat: "brooklyn", need: 1 },
|
| 19 |
];
|
| 20 |
export function createGoals(onDone) {
|
| 21 |
-
const S = Object.assign({ top: 0, smashed: 0, drift: 0, air: 0, pads: 0, butter: 0, wins: 0, escapes: 0, rings: 0, km: 0, refuels: 0, brooklyn: 0, best: 0, coins: 0, fares: 0, upg: 0, steals: 0, meds: 0 }, store.get("void.stats") || {});
|
| 22 |
const done = new Set(store.get("void.goals") || []);
|
| 23 |
let saveT = 0, lastSmash = null, lastRings = null, lastFuel = null, pads = new Set();
|
| 24 |
function check() { for (const g of GOALS) if (!done.has(g.id) && S[g.stat] >= g.need) { done.add(g.id); store.set("void.goals", [...done]); onDone(g); } }
|
|
|
|
| 15 |
{ id: "coins", icon: "🪙", name: "Collector", text: "Find 20 hidden coins", stat: "coins", need: 20 },
|
| 16 |
{ id: "taxi", icon: "🚕", name: "Cabbie", text: "Complete 5 taxi fares", stat: "fares", need: 5 },
|
| 17 |
{ id: "tuner", icon: "🔧", name: "Tuner", text: "Buy 4 garage upgrades", stat: "upg", need: 4 },
|
| 18 |
+
{ id: "grind", icon: "🛹", name: "Rail slider", text: "Grind 100 m of rails on the hoverboard", stat: "grind", need: 100 },
|
| 19 |
+
{ id: "b720", icon: "🌀", name: "Sky skater", text: "Land a 720 on the hoverboard", stat: "b720", need: 1 },
|
| 20 |
+
{ id: "bcombo", icon: "🔥", name: "Combo king", text: "Land 5 flip combos on the hoverboard", stat: "combo", need: 5 },
|
| 21 |
{ id: "brooklyn", icon: "🌉", name: "Bridge and tunnel", text: "Cross the bridge to Brooklyn", stat: "brooklyn", need: 1 },
|
| 22 |
];
|
| 23 |
export function createGoals(onDone) {
|
| 24 |
+
const S = Object.assign({ top: 0, smashed: 0, drift: 0, air: 0, pads: 0, butter: 0, wins: 0, escapes: 0, rings: 0, km: 0, refuels: 0, brooklyn: 0, best: 0, coins: 0, fares: 0, upg: 0, steals: 0, meds: 0, grind: 0, b720: 0, combo: 0 }, store.get("void.stats") || {});
|
| 25 |
const done = new Set(store.get("void.goals") || []);
|
| 26 |
let saveT = 0, lastSmash = null, lastRings = null, lastFuel = null, pads = new Set();
|
| 27 |
function check() { for (const g of GOALS) if (!done.has(g.id) && S[g.stat] >= g.need) { done.add(g.id); store.set("void.goals", [...done]); onDone(g); } }
|
static/main.js
CHANGED
|
@@ -60,11 +60,11 @@ setInterval(() => { if (document.hidden) net.tick(.5, null); }, 500); // a h
|
|
| 60 |
const GPU = (() => { try { const c = renderer.getContext(), e = c.getExtension("WEBGL_debug_renderer_info"); return e ? c.getParameter(e.UNMASKED_RENDERER_WEBGL) : ""; } catch { return ""; } })();
|
| 61 |
const STRONG = /RTX|GTX 1[06-9]|GTX 9[78]|Radeon RX|Radeon Pro|Apple M[1-9]/i.test(GPU) && !COARSE;
|
| 62 |
const TOP = /RTX\s?(40[6-9]0|50[5-9]0)|RTX\s?A?[45]\d{3}/i.test(GPU) && !COARSE; // recent RTX cards start on Ultra
|
| 63 |
-
const SET = Object.assign({ q: LITE ? "lite" : TOP ? "ultra" : STRONG ? "high" : COARSE ? "low" : "medium", sens: 1, autogas: "off", steer: "stick", weather: "clear", radio: "score", cycle: "off", handling: "arcade", motion: "on", text: "normal" }, store.get("void.settings") || {});
|
| 64 |
const gameOpts = {
|
| 65 |
handling: () => SET.handling, buyFood: () => { if (cash < 10) return false; cash -= 10; store.set("void.cash", cash); earn(0); return true; }, heroOwned: () => !!store.get("void.hero"), starter: () => { const m = life.models.find(m => m.id === "compact-3") || life.models.find(m => m.kind.id === "compact"); return { model: { ...m, top: 31, acc: .7, grip: .9 }, color: 0x6b5a44, rust: .75, owned: true }; }, onSteal: c => { police && police.event("steal"); goals.stats.steals = (goals.stats.steals || 0) + 1; },
|
| 66 |
garage: () => openGarage(true), cycleTime, hornType: () => build.horn, autoGas: () => SET.autogas === "on", onRepair: () => car.repair && car.repair(),
|
| 67 |
-
onScore: (n, why, pts) => { if (why === "Drift") goals.drift(n); if (/^(🥇|🥈|🥉)/.test(why)) goals.stats.meds = (goals.stats.meds || 0) + 1; goals.note(why); earn(pts ?? n); },
|
| 68 |
onSmash: kind => police && police.event("smash", kind), policeClear: () => police && police.clear(),
|
| 69 |
autopilot: (g, dt, over) => nav ? nav.controls(g, dt, over) : null, openGps: () => document.getElementById("gGpsBtn").click(),
|
| 70 |
mapExtra(mx, g, MM) {
|
|
@@ -106,6 +106,7 @@ menuEl.addEventListener("click", e => { const t = e.target.closest(".tile"); if
|
|
| 106 |
menuEl.querySelectorAll("[data-veh]").forEach(b => b.onclick = () => game.api.setVehicle(b.dataset.veh));
|
| 107 |
document.getElementById("gFuelNav").onclick = () => { const st = game.state; if (!st) return; const best = W.city.stations.slice().sort((a, b) => Math.hypot(a.x - st.x, a.z - st.z) - Math.hypot(b.x - st.x, b.z - st.z))[0]; nav.setDest({ name: "⛽ Fuel station", x: best.x, z: best.z }); };
|
| 108 |
function applySettings(save = true) {
|
|
|
|
| 109 |
if (typeof life !== "undefined" && life.setVoidDetail) life.setVoidDetail(SET.q !== "lite"); // Lite: simple flying VOID cars
|
| 110 |
document.body.classList.toggle("big-ui", SET.text === "large");
|
| 111 |
for (const seg of menuEl.querySelectorAll(".seg[data-set]")) for (const b of seg.children) b.classList.toggle("on", SET[seg.dataset.set] === b.dataset.v);
|
|
@@ -174,8 +175,10 @@ const boardMesh = (() => {
|
|
| 174 |
const under = new THREE.Mesh(new THREE.PlaneGeometry(1.2, .3).rotateX(Math.PI / 2), glow); under.position.y = .005; g.add(under);
|
| 175 |
for (const x of [-.4, .4]) { const pod = new THREE.Mesh(new THREE.CylinderGeometry(.11, .09, .1, 12), new THREE.MeshStandardMaterial({ color: 0x2a2c32, metalness: .7, roughness: .3 })); pod.position.set(x, -.03, 0); g.add(pod); const ring = new THREE.Mesh(new THREE.TorusGeometry(.1, .02, 6, 16).rotateX(Math.PI / 2), glow); ring.position.set(x, -.08, 0); g.add(ring); }
|
| 176 |
const strip = new THREE.Mesh(new THREE.BoxGeometry(1.32, .015, .02), glow); strip.position.set(0, .085, .19); g.add(strip); const strip2 = strip.clone(); strip2.position.z = -.19; g.add(strip2);
|
| 177 |
-
g.visible = false; scene.add(g); return g;
|
| 178 |
})();
|
|
|
|
|
|
|
| 179 |
const qSide = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2), boardUp = new THREE.Vector3();
|
| 180 |
// cars that aren't your VOID car: the one you're driving and the one you left parked, drawn from the traffic shapes
|
| 181 |
const rideMesh = () => { const g = new THREE.Group(); g.visible = false; scene.add(g); return { g, key: "" }; };
|
|
@@ -335,7 +338,8 @@ applyBuild();
|
|
| 335 |
const UPG = { engine: ["⚙️", "Engine", "More power and top speed"], grip: ["🛞", "Tyres", "Grip in corners, longer drifts"], nitro: ["🔥", "Nitro", "A bigger nitro tank"], armor: ["🛡️", "Armour", "Takes less damage"] };
|
| 336 |
const PRICE = [2500, 6000, 15000];
|
| 337 |
const upg = Object.assign({ engine: 0, grip: 0, nitro: 0, armor: 0 }, store.get("void.upg") || {});
|
| 338 |
-
let cash = store.get("void.cash") ??
|
|
|
|
| 339 |
gameOpts.upg = upg;
|
| 340 |
addEventListener("pagehide", () => store.set("void.cash", cash)); // phones kill tabs without warning: save on the way out
|
| 341 |
function earn(n) { cash += Math.max(0, Math.round(n)); clearTimeout(cashSaveT); cashSaveT = setTimeout(() => store.set("void.cash", cash), 800); const el = document.getElementById("gCash"); if (el) el.textContent = "$" + cash.toLocaleString(); }
|
|
@@ -546,9 +550,11 @@ function frameLoop(fixed) {
|
|
| 546 |
if (veh === "board") { // stand sideways on the board, knees apart, arms out for balance
|
| 547 |
boardMesh.position.copy(S.pos); boardMesh.quaternion.copy(S.quat);
|
| 548 |
walker.g.position.copy(boardUp.set(0, .1, 0).applyQuaternion(S.quat).add(S.pos)); walker.g.quaternion.copy(S.quat).multiply(qSide);
|
| 549 |
-
|
|
|
|
|
|
|
| 550 |
}
|
| 551 |
-
if (veh === "foot") { walker.legs[0].rotation.x = walker.legs[1].rotation.x = walker.arms[0].rotation.x = walker.arms[1].rotation.x = 0; walker.g.position.copy(S.pos); walker.g.rotation.set(0, -gsx.yaw, 0); const sw = Math.sin(S.walk || 0) * Math.min(.7, S.speed / 14) * (S.alt > .3 ? .3 : 1); walker.legs[0].rotation.z = sw; walker.legs[1].rotation.z = -sw; walker.arms[0].rotation.z = -sw * .8; walker.arms[1].rotation.z = sw * .8; }
|
| 552 |
vehicles.show(veh === "car" || veh === "foot" || veh === "board" ? null : veh, S.pos, S.quat, S, time, dt);
|
| 553 |
if (S.splash) for (let k = 0; k < 6; k++) { const pf = fx.puffs[fx.puffI++ % fx.puffs.length]; pf.s.position.copy(S.pos).add(new THREE.Vector3((Math.random() - .5) * 3, .3, (Math.random() - .5) * 3)); pf.life = 1; pf.s.visible = true; pf.grow = .8; pf.s.scale.setScalar(.8); pf.s.material.color.set(0xeaf6ff); pf.v.set((Math.random() - .5) * 6, 4 + Math.random() * 4, (Math.random() - .5) * 6); }
|
| 554 |
if (veh === "boat" && S.speed > 12 && Math.random() < .6) { const back = new THREE.Vector3(-3.6, .1, (Math.random() - .5) * 2).applyQuaternion(S.quat).add(S.pos); const pf = fx.puffs[fx.puffI++ % fx.puffs.length]; pf.s.position.copy(back); pf.life = 1; pf.s.visible = true; pf.grow = .5; pf.s.scale.setScalar(.5); pf.s.material.color.set(0xf4fbff); pf.v.set((Math.random() - .5) * 2, .6, (Math.random() - .5) * 2); }
|
|
|
|
| 60 |
const GPU = (() => { try { const c = renderer.getContext(), e = c.getExtension("WEBGL_debug_renderer_info"); return e ? c.getParameter(e.UNMASKED_RENDERER_WEBGL) : ""; } catch { return ""; } })();
|
| 61 |
const STRONG = /RTX|GTX 1[06-9]|GTX 9[78]|Radeon RX|Radeon Pro|Apple M[1-9]/i.test(GPU) && !COARSE;
|
| 62 |
const TOP = /RTX\s?(40[6-9]0|50[5-9]0)|RTX\s?A?[45]\d{3}/i.test(GPU) && !COARSE; // recent RTX cards start on Ultra
|
| 63 |
+
const SET = Object.assign({ q: LITE ? "lite" : TOP ? "ultra" : STRONG ? "high" : COARSE ? "low" : "medium", sens: 1, autogas: "off", steer: "stick", weather: "clear", radio: "score", cycle: "off", handling: "arcade", motion: "on", text: "normal", board: "ion" }, store.get("void.settings") || {});
|
| 64 |
const gameOpts = {
|
| 65 |
handling: () => SET.handling, buyFood: () => { if (cash < 10) return false; cash -= 10; store.set("void.cash", cash); earn(0); return true; }, heroOwned: () => !!store.get("void.hero"), starter: () => { const m = life.models.find(m => m.id === "compact-3") || life.models.find(m => m.kind.id === "compact"); return { model: { ...m, top: 31, acc: .7, grip: .9 }, color: 0x6b5a44, rust: .75, owned: true }; }, onSteal: c => { police && police.event("steal"); goals.stats.steals = (goals.stats.steals || 0) + 1; },
|
| 66 |
garage: () => openGarage(true), cycleTime, hornType: () => build.horn, autoGas: () => SET.autogas === "on", onRepair: () => car.repair && car.repair(),
|
| 67 |
+
onScore: (n, why, pts) => { if (why === "Drift") goals.drift(n); if (/^(🥇|🥈|🥉)/.test(why)) goals.stats.meds = (goals.stats.meds || 0) + 1; { const gm = /^Grind (\d+) m/.exec(why); if (gm) goals.stats.grind = (goals.stats.grind || 0) + +gm[1]; const sp = /^(\d+)( |$)/.exec(why); if (sp && +sp[1] >= 720) goals.stats.b720 = 1; if (/Kickflip|Heelflip/.test(why) && / \+ /.test(why)) goals.stats.combo = (goals.stats.combo || 0) + 1; } goals.note(why); earn(pts ?? n); },
|
| 68 |
onSmash: kind => police && police.event("smash", kind), policeClear: () => police && police.clear(),
|
| 69 |
autopilot: (g, dt, over) => nav ? nav.controls(g, dt, over) : null, openGps: () => document.getElementById("gGpsBtn").click(),
|
| 70 |
mapExtra(mx, g, MM) {
|
|
|
|
| 106 |
menuEl.querySelectorAll("[data-veh]").forEach(b => b.onclick = () => game.api.setVehicle(b.dataset.veh));
|
| 107 |
document.getElementById("gFuelNav").onclick = () => { const st = game.state; if (!st) return; const best = W.city.stations.slice().sort((a, b) => Math.hypot(a.x - st.x, a.z - st.z) - Math.hypot(b.x - st.x, b.z - st.z))[0]; nav.setDest({ name: "⛽ Fuel station", x: best.x, z: best.z }); };
|
| 108 |
function applySettings(save = true) {
|
| 109 |
+
if (typeof boardMesh !== "undefined") paintBoard();
|
| 110 |
if (typeof life !== "undefined" && life.setVoidDetail) life.setVoidDetail(SET.q !== "lite"); // Lite: simple flying VOID cars
|
| 111 |
document.body.classList.toggle("big-ui", SET.text === "large");
|
| 112 |
for (const seg of menuEl.querySelectorAll(".seg[data-set]")) for (const b of seg.children) b.classList.toggle("on", SET[seg.dataset.set] === b.dataset.v);
|
|
|
|
| 175 |
const under = new THREE.Mesh(new THREE.PlaneGeometry(1.2, .3).rotateX(Math.PI / 2), glow); under.position.y = .005; g.add(under);
|
| 176 |
for (const x of [-.4, .4]) { const pod = new THREE.Mesh(new THREE.CylinderGeometry(.11, .09, .1, 12), new THREE.MeshStandardMaterial({ color: 0x2a2c32, metalness: .7, roughness: .3 })); pod.position.set(x, -.03, 0); g.add(pod); const ring = new THREE.Mesh(new THREE.TorusGeometry(.1, .02, 6, 16).rotateX(Math.PI / 2), glow); ring.position.set(x, -.08, 0); g.add(ring); }
|
| 177 |
const strip = new THREE.Mesh(new THREE.BoxGeometry(1.32, .015, .02), glow); strip.position.set(0, .085, .19); g.add(strip); const strip2 = strip.clone(); strip2.position.z = -.19; g.add(strip2);
|
| 178 |
+
g.visible = false; scene.add(g); g.userData = { glow, deck: deck.material }; return g;
|
| 179 |
})();
|
| 180 |
+
const BOARDS = { ion: [[.3, 2.2, 2.6], 0x15161a], blaze: [[2.6, .9, .2], 0x1a1210], toxic: [[.8, 2.6, .3], 0x111a12], violet: [[1.6, .6, 2.6], 0x15121c], gold: [[2.6, 1.9, .5], 0x2a2418], ghost: [[2.4, 2.4, 2.4], 0xe8e8ea] };
|
| 181 |
+
function paintBoard() { const b = BOARDS[SET.board] || BOARDS.ion; boardMesh.userData.glow.color.setRGB(...b[0]); boardMesh.userData.deck.color.setHex(b[1]); }
|
| 182 |
const qSide = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2), boardUp = new THREE.Vector3();
|
| 183 |
// cars that aren't your VOID car: the one you're driving and the one you left parked, drawn from the traffic shapes
|
| 184 |
const rideMesh = () => { const g = new THREE.Group(); g.visible = false; scene.add(g); return { g, key: "" }; };
|
|
|
|
| 338 |
const UPG = { engine: ["⚙️", "Engine", "More power and top speed"], grip: ["🛞", "Tyres", "Grip in corners, longer drifts"], nitro: ["🔥", "Nitro", "A bigger nitro tank"], armor: ["🛡️", "Armour", "Takes less damage"] };
|
| 339 |
const PRICE = [2500, 6000, 15000];
|
| 340 |
const upg = Object.assign({ engine: 0, grip: 0, nitro: 0, armor: 0 }, store.get("void.upg") || {});
|
| 341 |
+
let cash = store.get("void.cash") ?? 100000, cashSaveT = 0;
|
| 342 |
+
if (!store.get("void.start100k")) { store.set("void.start100k", 1); if (cash < 100000) { cash = 100000; store.set("void.cash", cash); setTimeout(() => toast("You now start with $100,000. Go shopping at VOID Motors!"), 3000); } }
|
| 343 |
gameOpts.upg = upg;
|
| 344 |
addEventListener("pagehide", () => store.set("void.cash", cash)); // phones kill tabs without warning: save on the way out
|
| 345 |
function earn(n) { cash += Math.max(0, Math.round(n)); clearTimeout(cashSaveT); cashSaveT = setTimeout(() => store.set("void.cash", cash), 800); const el = document.getElementById("gCash"); if (el) el.textContent = "$" + cash.toLocaleString(); }
|
|
|
|
| 550 |
if (veh === "board") { // stand sideways on the board, knees apart, arms out for balance
|
| 551 |
boardMesh.position.copy(S.pos); boardMesh.quaternion.copy(S.quat);
|
| 552 |
walker.g.position.copy(boardUp.set(0, .1, 0).applyQuaternion(S.quat).add(S.pos)); walker.g.quaternion.copy(S.quat).multiply(qSide);
|
| 553 |
+
const cr = S.crouch || 0; walker.g.position.add(boardUp.set(0, -.22 * cr, 0).applyQuaternion(S.quat)); walker.g.scale.set(1, 1 - .14 * cr, 1);
|
| 554 |
+
walker.legs[0].rotation.set(-.24 - .2 * cr, 0, .5 * cr); walker.legs[1].rotation.set(.24 + .2 * cr, 0, .5 * cr); walker.arms[0].rotation.set(-.95 + .5 * cr, 0, Math.sin(time * 2) * .1 + 1.1 * cr); walker.arms[1].rotation.set(.95 - .5 * cr, 0, -Math.sin(time * 2) * .1 + 1.1 * cr);
|
| 555 |
+
if (S.speed > 25 && Math.random() < .5) { const pf = fx.puffs[fx.puffI++ % fx.puffs.length], gc = boardMesh.userData.glow.color; pf.s.position.copy(S.pos).add(boardUp.set((Math.random() - .5) * .3, .02, (Math.random() - .5) * .3)); pf.life = .6; pf.s.visible = true; pf.grow = .15; pf.s.scale.setScalar(.15); pf.s.material.color.setRGB(Math.min(1, gc.r / 2.6), Math.min(1, gc.g / 2.6), Math.min(1, gc.b / 2.6)); pf.v.set(0, .3, 0); }
|
| 556 |
}
|
| 557 |
+
if (veh === "foot") { walker.legs[0].rotation.x = walker.legs[1].rotation.x = walker.arms[0].rotation.x = walker.arms[1].rotation.x = 0; walker.g.scale.set(1, 1, 1); walker.g.position.copy(S.pos); walker.g.rotation.set(0, -gsx.yaw, 0); const sw = Math.sin(S.walk || 0) * Math.min(.7, S.speed / 14) * (S.alt > .3 ? .3 : 1); walker.legs[0].rotation.z = sw; walker.legs[1].rotation.z = -sw; walker.arms[0].rotation.z = -sw * .8; walker.arms[1].rotation.z = sw * .8; }
|
| 558 |
vehicles.show(veh === "car" || veh === "foot" || veh === "board" ? null : veh, S.pos, S.quat, S, time, dt);
|
| 559 |
if (S.splash) for (let k = 0; k < 6; k++) { const pf = fx.puffs[fx.puffI++ % fx.puffs.length]; pf.s.position.copy(S.pos).add(new THREE.Vector3((Math.random() - .5) * 3, .3, (Math.random() - .5) * 3)); pf.life = 1; pf.s.visible = true; pf.grow = .8; pf.s.scale.setScalar(.8); pf.s.material.color.set(0xeaf6ff); pf.v.set((Math.random() - .5) * 6, 4 + Math.random() * 4, (Math.random() - .5) * 6); }
|
| 560 |
if (veh === "boat" && S.speed > 12 && Math.random() < .6) { const back = new THREE.Vector3(-3.6, .1, (Math.random() - .5) * 2).applyQuaternion(S.quat).add(S.pos); const pf = fx.puffs[fx.puffI++ % fx.puffs.length]; pf.s.position.copy(back); pf.life = 1; pf.s.visible = true; pf.grow = .5; pf.s.scale.setScalar(.5); pf.s.material.color.set(0xf4fbff); pf.v.set((Math.random() - .5) * 2, .6, (Math.random() - .5) * 2); }
|