commit f73fa84ca6600f5401527c439c0b5795b6c84101 Author: nak Date: Sun Mar 15 21:57:31 2026 +0000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..751553b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.bak diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8e3fde --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Overte WWW +========== + +Overte static assets diff --git a/avatars/RemiliaJackson.glb b/avatars/RemiliaJackson.glb new file mode 100644 index 0000000..377abb1 Binary files /dev/null and b/avatars/RemiliaJackson.glb differ diff --git a/avatars/RemiliaJackson.jpg b/avatars/RemiliaJackson.jpg new file mode 100644 index 0000000..21cc878 Binary files /dev/null and b/avatars/RemiliaJackson.jpg differ diff --git a/images/underconstruction.gif b/images/underconstruction.gif new file mode 100644 index 0000000..b9b4c82 Binary files /dev/null and b/images/underconstruction.gif differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..05532e8 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ +
+              *
+             / \
+            /___\
+           ( o o )            * *
+           )  L  (           /   * *
+   ________()(-)()________  /     * * *
+ E\| _____ )()()() ______ |/B     * * *
+   |/      ()()()(       \|      * * * *
+           | )() |
+           /     \
+          / *  *  \
+         /   *  *  \
+        / *_  *  _  \   wizards see you
+
diff --git a/models/shape_store.glb b/models/shape_store.glb new file mode 100644 index 0000000..7d5ed79 Binary files /dev/null and b/models/shape_store.glb differ diff --git a/music/spawn_music.mp3 b/music/spawn_music.mp3 new file mode 100644 index 0000000..67e1f68 Binary files /dev/null and b/music/spawn_music.mp3 differ diff --git a/music/spawn_music.ogg b/music/spawn_music.ogg new file mode 100644 index 0000000..35c2319 Binary files /dev/null and b/music/spawn_music.ogg differ diff --git a/scripts/manifoldDomain.js b/scripts/manifoldDomain.js new file mode 100644 index 0000000..2ee5eac --- /dev/null +++ b/scripts/manifoldDomain.js @@ -0,0 +1,116 @@ +// +// manifoldDomain.js — Manifold Domain Script +// +// Assignment client script — do NOT wrap in an IIFE. +// Requires EntityViewer to be initialized before entity queries work. +// +// Deploy via Domain Settings > Scripts. +// + +var WS_URL = "wss://wizards.cyou/ws"; +var SCAN_CENTER = { x: 0, y: 0, z: 0 }; +var SCAN_RADIUS = 500; + +var ws = null; +var reconnectTimer = null; +var heartbeatTimer = null; +var isReady = false; + +// ── EntityViewer init ──────────────────────────────────────────── +// AC scripts have no spatial presence — must set up EntityViewer +// before findEntitiesByType will return anything. + +var initInterval = Script.setInterval(function () { + if (Entities.serversExist() && Entities.canRez()) { + EntityViewer.setPosition(SCAN_CENTER); + EntityViewer.setCenterRadius(SCAN_RADIUS); + EntityViewer.queryOctree(); + Script.clearInterval(initInterval); + + Script.setTimeout(function () { + isReady = true; + print("[Manifold] EntityViewer ready"); + connect(); + // Re-query octree periodically so newly spawned entities stay visible + heartbeatTimer = Script.setInterval(function () { + EntityViewer.queryOctree(); + }, 10000); + }, 2000); + } +}, 500); + +// ── entity cleanup ─────────────────────────────────────────────── + +function deleteClonesFor(username) { + if (!isReady) return; + EntityViewer.queryOctree(); + Script.setTimeout(function () { + var ids = Entities.findEntitiesByType("Shape", SCAN_CENTER, SCAN_RADIUS); + var deleted = 0; + ids.forEach(function (id) { + try { + var ud = JSON.parse(Entities.getEntityProperties(id, ["userData"]).userData || "{}"); + if (ud.manifoldClone && ud.owner === username) { + Entities.deleteEntity(id); + deleted++; + } + } catch (e) {} + }); + print("[Manifold] deleted " + deleted + " clone(s) for " + username); + }, 1000); +} + +// ── websocket ──────────────────────────────────────────────────── + +function connect() { + print("[Manifold] Connecting to " + WS_URL); + ws = new WebSocket(WS_URL); + + ws.onopen = function () { + print("[Manifold] Connected"); + if (reconnectTimer) { + Script.clearTimeout(reconnectTimer); + reconnectTimer = null; + } + }; + + ws.onmessage = function (event) { + try { + var msg = JSON.parse(event.data); + switch (msg.type) { + case "user:connected": + print("[Manifold] >>> " + msg.username + " joined"); + break; + case "user:disconnected": + print("[Manifold] <<< " + msg.username + " left"); + deleteClonesFor(msg.username); + break; + case "recall": + print("[Manifold] recall for " + msg.username); + deleteClonesFor(msg.username); + break; + } + } catch (e) { + print("[Manifold] message parse error: " + e); + } + }; + + ws.onerror = function (err) { + print("[Manifold] WS error: " + JSON.stringify(err)); + }; + + ws.onclose = function (event) { + print("[Manifold] Disconnected (code=" + event.code + "). Reconnecting in 5s..."); + ws = null; + reconnectTimer = Script.setTimeout(connect, 5000); + }; +} + +// ── lifecycle ──────────────────────────────────────────────────── + +Script.scriptEnding.connect(function () { + print("[Manifold] script ending"); + if (reconnectTimer) { Script.clearTimeout(reconnectTimer); } + if (heartbeatTimer) { Script.clearInterval(heartbeatTimer); } + if (ws) { ws.close(); ws = null; } +}); diff --git a/scripts/shelfShape.js b/scripts/shelfShape.js new file mode 100644 index 0000000..03e598a --- /dev/null +++ b/scripts/shelfShape.js @@ -0,0 +1,186 @@ +// +// shelfShape.js — Manifold Shop Entity Script +// +// Attach to each shelf shape entity. Set userData to: +// {"shapeId": "tetrahedron"} +// +// Clones are domain-owned. The domain script (manifoldDomain.js) handles +// all cleanup via WebSocket presence events and recall messages. +// + +(function () { + var API_BASE = "https://wizards.cyou/api"; + var SPAWN_OFFSET = { x: 0, y: 0.3, z: 0.5 }; + + var SHAPE_TYPE = { + tetrahedron: "Tetrahedron", + hexahedron: "Cube", + octahedron: "Octahedron", + dodecahedron: "Dodecahedron", + icosahedron: "Icosahedron", + }; + + var SHAPE_COLOR = { + tetrahedron: { red: 255, green: 180, blue: 60 }, + hexahedron: { red: 180, green: 220, blue: 255 }, + octahedron: { red: 140, green: 255, blue: 180 }, + dodecahedron: { red: 255, green: 120, blue: 180 }, + icosahedron: { red: 200, green: 140, blue: 255 }, + }; + + var _entityID = null; + var _shapeId = null; + var _token = null; + var _username = null; + var _busy = false; + + // ── helpers ────────────────────────────────────────────────── + + function getShapeId() { + try { + var props = Entities.getEntityProperties(_entityID, ["userData"]); + return JSON.parse(props.userData || "{}").shapeId || null; + } catch (e) { return null; } + } + + function notify(msg) { + Window.displayAnnouncement(msg); + } + + // ── session ────────────────────────────────────────────────── + + function ensureSession(cb) { + if (_token) { cb(null, _token); return; } + _username = MyAvatar.displayName; + if (!_username) { cb("Not logged in to Overte"); return; } + + var xhr = new XMLHttpRequest(); + xhr.open("POST", API_BASE + "/session", true); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) return; + if (xhr.status === 200) { + try { + _token = JSON.parse(xhr.responseText).token; + Script.setTimeout(function () { _token = null; }, 5 * 60 * 1000); + cb(null, _token); + } catch (e) { cb("Session parse error"); } + } else if (xhr.status === 403) { + cb("Must be in-world to use the shop"); + } else { + cb("Session error (" + xhr.status + ")"); + } + }; + xhr.send(JSON.stringify({ username: _username })); + } + + // ── inventory ──────────────────────────────────────────────── + + function getInventory(cb) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", API_BASE + "/inventory/" + _username, true); + xhr.onreadystatechange = function () { + if (xhr.readyState !== 4) return; + if (xhr.status === 200) { + try { cb(null, JSON.parse(xhr.responseText).inventory || {}); } + catch (e) { cb("Inventory parse error"); } + } else { + cb("Inventory fetch failed (" + xhr.status + ")"); + } + }; + xhr.send(); + } + + function countLiveClones(username, shapeId) { + var results = Entities.findEntitiesByType("Shape", MyAvatar.position, 100); + var count = 0; + results.forEach(function (id) { + try { + var ud = JSON.parse(Entities.getEntityProperties(id, ["userData"]).userData || "{}"); + if (ud.manifoldClone && ud.owner === username && ud.shapeId === shapeId) count++; + } catch (e) {} + }); + return count; + } + + // ── spawn ──────────────────────────────────────────────────── + + function spawnClone() { + var shelfProps = Entities.getEntityProperties(_entityID, ["position", "rotation"]); + var spawnPos = Vec3.sum(shelfProps.position, + Vec3.multiplyQbyV(shelfProps.rotation, SPAWN_OFFSET)); + + Entities.addEntity({ + type: "Shape", + shape: SHAPE_TYPE[_shapeId] || "Sphere", + name: "manifold_" + _shapeId + "_" + _username, + position: spawnPos, + dimensions: { x: 0.25, y: 0.25, z: 0.25 }, + color: SHAPE_COLOR[_shapeId] || { red: 200, green: 200, blue: 200 }, + userData: JSON.stringify({ manifoldClone: true, owner: _username, shapeId: _shapeId }), + dynamic: true, + grabbable: true, + restitution: 0.6, + friction: 0.4, + density: 800, + emissive: true, + lifetime: 3600, // hard ceiling fallback + }, "domain"); + } + + // ── click handler ──────────────────────────────────────────── + + function onClickDown() { + if (_busy) return; + _busy = true; + + _shapeId = getShapeId(); + if (!_shapeId) { + notify("Shop item not configured (missing shapeId in userData)"); + _busy = false; + return; + } + + ensureSession(function (err) { + if (err) { notify(err); _busy = false; return; } + + getInventory(function (err, inventory) { + if (err) { notify(err); _busy = false; return; } + + var owned = inventory[_shapeId] || 0; + if (owned === 0) { + notify("You don't own any " + _shapeId + "s. Buy one in the Manifold tablet."); + _busy = false; + return; + } + + var live = countLiveClones(_username, _shapeId); + if (live >= owned) { + notify("You have " + live + "/" + owned + " " + _shapeId + + "(s) active. Recall them or buy more."); + _busy = false; + return; + } + + spawnClone(); + notify("Spawned " + _shapeId + " (" + (live + 1) + "/" + owned + " active)"); + _busy = false; + }); + }); + } + + // ── lifecycle ──────────────────────────────────────────────── + + this.preload = function (entityID) { + _entityID = entityID; + }; + + this.clickDownOnEntity = function () { + onClickDown(); + }; + + this.unload = function () { + _token = null; + _username = null; + }; +}); diff --git a/scripts/spawnShapeRotator.js b/scripts/spawnShapeRotator.js new file mode 100644 index 0000000..000e7dd --- /dev/null +++ b/scripts/spawnShapeRotator.js @@ -0,0 +1,61 @@ +(function() { + var ORBIT_RADIUS = 2.5; // distance from origin + var ORBIT_SPEED = 0.2; // radians per second + var SPIN_SPEED = 1.5; // radians per second + var ORBIT_Y = 2; // height above ground + + var solids = ["tetrahedron", "cube", "octahedron", "dodecahedron", "icosahedron"]; + var count = solids.length; + var entities = {}; + var startTime = Date.now(); + + function findEntities() { + var found = Entities.findEntities({x:0, y:0, z:0}, 200); + solids.forEach(function(name) { + found.forEach(function(id) { + var props = Entities.getEntityProperties(id, ["name"]); + if (props.name === name) { + entities[name] = id; + } + }); + }); + + // If we didn't find all 5, try again shortly + if (Object.keys(entities).length < solids.length) { + print("Only found " + Object.keys(entities).length + " solids, retrying..."); + Script.setTimeout(findEntities, 500); + } else { + print("Found all solids, starting update loop"); + Script.update.connect(update); + } + } + + // Initial delay before first attempt + Script.setTimeout(findEntities, 500); + + function update(dt) { + var elapsed = (Date.now() - startTime) / 1000; + + solids.forEach(function(name, i) { + var id = entities[name]; + if (!id) return; + + // orbit angle offset per solid so they're evenly spaced + var orbitAngle = elapsed * ORBIT_SPEED + (i / count) * 2 * Math.PI; + + var x = Math.cos(orbitAngle) * ORBIT_RADIUS; + var z = Math.sin(orbitAngle) * ORBIT_RADIUS; + + // spin around local Y axis + var spinAngle = elapsed * SPIN_SPEED; + + Entities.editEntity(id, { + position: { x: x, y: ORBIT_Y, z: z }, + rotation: Quat.fromPitchYawRollRadians(spinAngle, spinAngle, -spinAngle * 0.5) + }); + }); + } + + Script.update.connect(update); + findEntities(); +})(); diff --git a/scripts/tablet.js b/scripts/tablet.js new file mode 100644 index 0000000..97008fd --- /dev/null +++ b/scripts/tablet.js @@ -0,0 +1,36 @@ +(function() { + var APP_NAME = "MANIFOLD" + var APP_URL = "https://wizards.cyou/tablet/manifold.html"; + var APP_ICON = "https://wizards.cyou/tablet/manifold-icon.svg"; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: APP_NAME, + icon: APP_ICON + }); + + var webEventHandler = function(data) { + try { + var msg = JSON.parse(data); + if (msg.type === "ready") { + var username = AccountServices.username || ""; + tablet.emitScriptEvent(JSON.stringify({ + type: "username", + username: username + })); + } + } catch(e) { + print("Manifold web event error: " + e); + } + }; + + button.clicked.connect(function() { + tablet.gotoWebScreen(APP_URL); + tablet.webEventReceived.connect(webEventHandler); + }); + + Script.scriptEnding.connect(function() { + tablet.removeButton(button); + tablet.webEventReceived.disconnect(webEventHandler); + }); +})(); diff --git a/tablet/gifs/dodecahedron.gif b/tablet/gifs/dodecahedron.gif new file mode 100644 index 0000000..7061c02 Binary files /dev/null and b/tablet/gifs/dodecahedron.gif differ diff --git a/tablet/gifs/hexahedron.gif b/tablet/gifs/hexahedron.gif new file mode 100644 index 0000000..9252afb Binary files /dev/null and b/tablet/gifs/hexahedron.gif differ diff --git a/tablet/gifs/icosahedron.gif b/tablet/gifs/icosahedron.gif new file mode 100644 index 0000000..284d6bf Binary files /dev/null and b/tablet/gifs/icosahedron.gif differ diff --git a/tablet/gifs/octahedron.gif b/tablet/gifs/octahedron.gif new file mode 100644 index 0000000..cc580d7 Binary files /dev/null and b/tablet/gifs/octahedron.gif differ diff --git a/tablet/gifs/tetrahedron.gif b/tablet/gifs/tetrahedron.gif new file mode 100644 index 0000000..d20eb22 Binary files /dev/null and b/tablet/gifs/tetrahedron.gif differ diff --git a/tablet/manifold-icon.svg b/tablet/manifold-icon.svg new file mode 100644 index 0000000..0ae31a9 --- /dev/null +++ b/tablet/manifold-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/tablet/manifold.html b/tablet/manifold.html new file mode 100644 index 0000000..a9c9dea --- /dev/null +++ b/tablet/manifold.html @@ -0,0 +1,786 @@ + + + + + + MANIFOLD + + + + + + +
+ +
+
MANIFOLD
+
Platonic Exchange
+
+
+
Balance
+
+
VERTS
+
+
+ +
Initialising...
+ +
+ + + + diff --git a/tablet/manifold.html.bak b/tablet/manifold.html.bak new file mode 100644 index 0000000..d1da517 --- /dev/null +++ b/tablet/manifold.html.bak @@ -0,0 +1,120 @@ + + + + + + MANIFOLD + + + + +
Universal Shape Income
+
...
+
VERTS
+
Fetching balance...
+ + + + diff --git a/textures/seamless_red_rock.jpg b/textures/seamless_red_rock.jpg new file mode 100644 index 0000000..f2b4842 Binary files /dev/null and b/textures/seamless_red_rock.jpg differ