solanapolis 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +518 -0
- package/bin/solanapolis.js +197 -0
- package/convex/_generated/api.d.ts +175 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +60 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/agent/conversation.ts +352 -0
- package/convex/agent/embeddingsCache.ts +110 -0
- package/convex/agent/memory.ts +450 -0
- package/convex/agent/schema.ts +53 -0
- package/convex/aiChat.ts +54 -0
- package/convex/aiTown/agent.ts +382 -0
- package/convex/aiTown/agentDescription.ts +27 -0
- package/convex/aiTown/agentInputs.ts +155 -0
- package/convex/aiTown/agentOperations.ts +178 -0
- package/convex/aiTown/conversation.ts +395 -0
- package/convex/aiTown/conversationMembership.ts +38 -0
- package/convex/aiTown/game.ts +371 -0
- package/convex/aiTown/ids.ts +32 -0
- package/convex/aiTown/inputHandler.ts +9 -0
- package/convex/aiTown/inputs.ts +25 -0
- package/convex/aiTown/insertInput.ts +20 -0
- package/convex/aiTown/location.ts +32 -0
- package/convex/aiTown/main.ts +154 -0
- package/convex/aiTown/movement.ts +189 -0
- package/convex/aiTown/player.ts +310 -0
- package/convex/aiTown/playerDescription.ts +35 -0
- package/convex/aiTown/schema.ts +79 -0
- package/convex/aiTown/world.ts +65 -0
- package/convex/aiTown/worldMap.ts +74 -0
- package/convex/chat.ts +79 -0
- package/convex/constants.ts +78 -0
- package/convex/convex.config.ts +6 -0
- package/convex/crons.ts +89 -0
- package/convex/engine/abstractGame.ts +199 -0
- package/convex/engine/historicalObject.ts +355 -0
- package/convex/engine/schema.ts +56 -0
- package/convex/http.ts +36 -0
- package/convex/init.ts +110 -0
- package/convex/messages.ts +53 -0
- package/convex/npcCarAgents.ts +415 -0
- package/convex/schema.ts +61 -0
- package/convex/streaming.ts +23 -0
- package/convex/testing.ts +202 -0
- package/convex/tsconfig.json +18 -0
- package/convex/util/FastIntegerCompression.ts +221 -0
- package/convex/util/assertNever.ts +4 -0
- package/convex/util/asyncMap.ts +20 -0
- package/convex/util/compression.ts +71 -0
- package/convex/util/geometry.ts +132 -0
- package/convex/util/isSimpleObject.ts +11 -0
- package/convex/util/llm.ts +724 -0
- package/convex/util/minheap.ts +38 -0
- package/convex/util/object.ts +22 -0
- package/convex/util/sleep.ts +3 -0
- package/convex/util/types.ts +33 -0
- package/convex/util/xxhash.ts +228 -0
- package/convex/world.ts +257 -0
- package/data/animations/campfire.json +45 -0
- package/data/animations/gentlesparkle.json +37 -0
- package/data/animations/gentlesplash.json +61 -0
- package/data/animations/gentlewaterfall.json +61 -0
- package/data/animations/windmill.json +78 -0
- package/data/characters.ts +121 -0
- package/data/convertMap.js +74 -0
- package/data/gentle.js +330 -0
- package/data/spritesheets/f1.ts +75 -0
- package/data/spritesheets/f2.ts +75 -0
- package/data/spritesheets/f3.ts +75 -0
- package/data/spritesheets/f4.ts +75 -0
- package/data/spritesheets/f5.ts +75 -0
- package/data/spritesheets/f6.ts +75 -0
- package/data/spritesheets/f7.ts +75 -0
- package/data/spritesheets/f8.ts +75 -0
- package/data/spritesheets/p1.ts +59 -0
- package/data/spritesheets/p2.ts +59 -0
- package/data/spritesheets/p3.ts +59 -0
- package/data/spritesheets/player.ts +59 -0
- package/data/spritesheets/types.ts +26 -0
- package/eslint.config.mjs +37 -0
- package/next.config.ts +7 -0
- package/package.json +85 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/helius-icon.svg +84 -0
- package/public/helius-logo.svg +85 -0
- package/public/next.svg +1 -0
- package/public/plane.glb +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/clear-city.ts +74 -0
- package/scripts/seed-wallets.ts +185 -0
- package/scripts/setup-webhook.ts +73 -0
- package/src/app/api/auth/callback/route.ts +6 -0
- package/src/app/api/auth/link-wallet/route.ts +6 -0
- package/src/app/api/auth/phantom/route.ts +6 -0
- package/src/app/api/broadcast-position/route.ts +59 -0
- package/src/app/api/leaderboard/route.ts +85 -0
- package/src/app/api/network-stats/route.ts +86 -0
- package/src/app/api/parcel-reward/route.ts +181 -0
- package/src/app/api/queue-status/route.ts +30 -0
- package/src/app/api/snapshots/route.ts +37 -0
- package/src/app/api/transactions/enhanced/route.ts +57 -0
- package/src/app/api/treasury/route.ts +83 -0
- package/src/app/api/wallet/[address]/balances/route.ts +124 -0
- package/src/app/api/wallet/[address]/identity/route.ts +32 -0
- package/src/app/api/wallet/[address]/route.ts +216 -0
- package/src/app/api/wallet/[address]/traded-tokens/route.ts +41 -0
- package/src/app/api/wallets/route.ts +68 -0
- package/src/app/api/webhooks/helius/route.ts +76 -0
- package/src/app/auth/callback/page.tsx +29 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +39 -0
- package/src/app/layout.tsx +43 -0
- package/src/app/page.tsx +16 -0
- package/src/components/AITownNPCs.tsx +206 -0
- package/src/components/ActivityFeed.tsx +189 -0
- package/src/components/AuthPanel.tsx +163 -0
- package/src/components/BeachScene.tsx +280 -0
- package/src/components/Building.tsx +138 -0
- package/src/components/CesiumFlight.tsx +1768 -0
- package/src/components/CesiumGlobe.tsx +616 -0
- package/src/components/CitizenCard.tsx +442 -0
- package/src/components/CitizenCardModal.tsx +153 -0
- package/src/components/CityGrid.tsx +313 -0
- package/src/components/CityLandmarks.tsx +427 -0
- package/src/components/CityScene.tsx +1289 -0
- package/src/components/CitySlotsBadge.tsx +68 -0
- package/src/components/CockpitHUD.tsx +460 -0
- package/src/components/ConvexWrapper.tsx +19 -0
- package/src/components/DubaiDistrict.tsx +630 -0
- package/src/components/FlightMiniMap.tsx +133 -0
- package/src/components/GameChat.tsx +383 -0
- package/src/components/GameHUD.tsx +393 -0
- package/src/components/Ground.tsx +14 -0
- package/src/components/HowItWorksModal.tsx +251 -0
- package/src/components/IngestionBanner.tsx +123 -0
- package/src/components/InstancedBuildings.tsx +316 -0
- package/src/components/InstancedCars.tsx +504 -0
- package/src/components/InstancedCityPlanes.tsx +259 -0
- package/src/components/InstancedHouses.tsx +246 -0
- package/src/components/InstancedLampPosts.tsx +201 -0
- package/src/components/InstancedResidentCars.tsx +357 -0
- package/src/components/InstancedRoadDashes.tsx +42 -0
- package/src/components/InstancedSkyscrapers.tsx +434 -0
- package/src/components/InstancedTrees.tsx +67 -0
- package/src/components/LeaderboardPanel.tsx +136 -0
- package/src/components/MultiplayerPlanes.tsx +128 -0
- package/src/components/NetworkStats.tsx +83 -0
- package/src/components/NewBuildingSpotlight.tsx +93 -0
- package/src/components/ParcelChallengeBanner.tsx +242 -0
- package/src/components/ParcelReward.tsx +191 -0
- package/src/components/Park.tsx +42 -0
- package/src/components/PhantomWrapper.tsx +22 -0
- package/src/components/PixelStreamViewer.tsx +335 -0
- package/src/components/PlaneMode.tsx +190 -0
- package/src/components/PlayerCar.tsx +211 -0
- package/src/components/PlayerPlane.tsx +255 -0
- package/src/components/ProjectileRenderer.tsx +249 -0
- package/src/components/QueueStatusBanner.tsx +86 -0
- package/src/components/RealPlayerTags.tsx +82 -0
- package/src/components/SceneLighting.tsx +382 -0
- package/src/components/SelectionBeam.tsx +59 -0
- package/src/components/SwapPanel.tsx +104 -0
- package/src/components/SwapParticles.tsx +237 -0
- package/src/components/TreasureGate.tsx +505 -0
- package/src/components/WalletPanel.tsx +421 -0
- package/src/components/WalletSearch.tsx +244 -0
- package/src/components/WelcomeOverlay.tsx +135 -0
- package/src/components/WindowTooltip.tsx +498 -0
- package/src/context/AuthContext.tsx +230 -0
- package/src/lib/bot-detection.ts +125 -0
- package/src/lib/building-math.ts +136 -0
- package/src/lib/building-shader.ts +253 -0
- package/src/lib/car-paths.ts +244 -0
- package/src/lib/car-system.ts +182 -0
- package/src/lib/city-constants.ts +29 -0
- package/src/lib/city-slots.ts +35 -0
- package/src/lib/city-zoning.ts +64 -0
- package/src/lib/collision-map.ts +147 -0
- package/src/lib/day-night.ts +252 -0
- package/src/lib/export-card.ts +28 -0
- package/src/lib/helius-webhook.ts +90 -0
- package/src/lib/helius.ts +74 -0
- package/src/lib/house-shader.ts +119 -0
- package/src/lib/mock-data.ts +56 -0
- package/src/lib/multiplayer-manager.ts +329 -0
- package/src/lib/plane-physics.ts +66 -0
- package/src/lib/player-car.ts +147 -0
- package/src/lib/player-plane.ts +200 -0
- package/src/lib/projectile-system.ts +272 -0
- package/src/lib/skyscraper-types.ts +52 -0
- package/src/lib/sound-engine.ts +464 -0
- package/src/lib/supabase-admin.ts +9 -0
- package/src/lib/supabase.ts +8 -0
- package/src/lib/swap-events.ts +70 -0
- package/src/middleware.ts +37 -0
- package/src/types/phantom.d.ts +16 -0
- package/src/types/wallet.ts +20 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRef, useMemo, useEffect, MutableRefObject } from "react";
|
|
4
|
+
import { useFrame } from "@react-three/fiber";
|
|
5
|
+
import * as THREE from "three";
|
|
6
|
+
import { ProjectileManager, HitEvent } from "@/lib/projectile-system";
|
|
7
|
+
import { playGunshot, playExplosion, playScoreChime, initAudio } from "@/lib/sound-engine";
|
|
8
|
+
|
|
9
|
+
interface ProjectileRendererProps {
|
|
10
|
+
manager: ProjectileManager;
|
|
11
|
+
/** Positions of all resident planes (for hit detection) */
|
|
12
|
+
planePositionsRef: MutableRefObject<Array<{ x: number; y: number; z: number }>>;
|
|
13
|
+
/** Positions of all resident cars (for hit detection) */
|
|
14
|
+
carPositionsRef: MutableRefObject<Array<{ x: number; y: number; z: number }>>;
|
|
15
|
+
/** Is the player actively in car or plane mode? */
|
|
16
|
+
active: boolean;
|
|
17
|
+
/** Player position ref (car or plane) */
|
|
18
|
+
playerPosRef: MutableRefObject<[number, number, number] | null>;
|
|
19
|
+
/** Player heading ref */
|
|
20
|
+
playerHeadingRef: MutableRefObject<number>;
|
|
21
|
+
/** Player pitch (planes only, 0 for cars) */
|
|
22
|
+
playerPitchRef?: MutableRefObject<number>;
|
|
23
|
+
/** "car" | "plane" */
|
|
24
|
+
mode: "car" | "plane";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const MAX_VISIBLE = 50;
|
|
28
|
+
const MAX_EXPLOSIONS = 20;
|
|
29
|
+
const _dummy = new THREE.Object3D();
|
|
30
|
+
|
|
31
|
+
export default function ProjectileRenderer({
|
|
32
|
+
manager,
|
|
33
|
+
planePositionsRef,
|
|
34
|
+
carPositionsRef,
|
|
35
|
+
active,
|
|
36
|
+
playerPosRef,
|
|
37
|
+
playerHeadingRef,
|
|
38
|
+
playerPitchRef,
|
|
39
|
+
mode,
|
|
40
|
+
}: ProjectileRendererProps) {
|
|
41
|
+
const bulletRef = useRef<THREE.InstancedMesh>(null);
|
|
42
|
+
const trailRef = useRef<THREE.InstancedMesh>(null);
|
|
43
|
+
const explosionRef = useRef<THREE.InstancedMesh>(null);
|
|
44
|
+
const fireKeyRef = useRef(false);
|
|
45
|
+
const prevHitCountRef = useRef(0);
|
|
46
|
+
const lastFireSoundRef = useRef(0);
|
|
47
|
+
|
|
48
|
+
// Materials
|
|
49
|
+
const bulletMat = useMemo(() => new THREE.MeshStandardMaterial({
|
|
50
|
+
color: "#ff4400",
|
|
51
|
+
emissive: "#ff6600",
|
|
52
|
+
emissiveIntensity: 3.0,
|
|
53
|
+
}), []);
|
|
54
|
+
|
|
55
|
+
const trailMat = useMemo(() => new THREE.MeshStandardMaterial({
|
|
56
|
+
color: "#ff8800",
|
|
57
|
+
emissive: "#ff6600",
|
|
58
|
+
emissiveIntensity: 2.0,
|
|
59
|
+
transparent: true,
|
|
60
|
+
opacity: 0.5,
|
|
61
|
+
}), []);
|
|
62
|
+
|
|
63
|
+
const explosionMat = useMemo(() => new THREE.MeshStandardMaterial({
|
|
64
|
+
color: "#ffaa00",
|
|
65
|
+
emissive: "#ff4400",
|
|
66
|
+
emissiveIntensity: 4.0,
|
|
67
|
+
transparent: true,
|
|
68
|
+
opacity: 0.8,
|
|
69
|
+
}), []);
|
|
70
|
+
|
|
71
|
+
// Keyboard: fire on F or left-click
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!active) return;
|
|
74
|
+
|
|
75
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
76
|
+
if (e.key === "f" || e.key === "F") fireKeyRef.current = true;
|
|
77
|
+
}
|
|
78
|
+
function onKeyUp(e: KeyboardEvent) {
|
|
79
|
+
if (e.key === "f" || e.key === "F") fireKeyRef.current = false;
|
|
80
|
+
}
|
|
81
|
+
function onMouseDown(e: MouseEvent) {
|
|
82
|
+
if (e.button === 0) fireKeyRef.current = true;
|
|
83
|
+
}
|
|
84
|
+
function onMouseUp(e: MouseEvent) {
|
|
85
|
+
if (e.button === 0) fireKeyRef.current = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
window.addEventListener("keydown", onKeyDown);
|
|
89
|
+
window.addEventListener("keyup", onKeyUp);
|
|
90
|
+
window.addEventListener("mousedown", onMouseDown);
|
|
91
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
95
|
+
window.removeEventListener("keyup", onKeyUp);
|
|
96
|
+
window.removeEventListener("mousedown", onMouseDown);
|
|
97
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
98
|
+
fireKeyRef.current = false;
|
|
99
|
+
};
|
|
100
|
+
}, [active]);
|
|
101
|
+
|
|
102
|
+
useFrame((_, delta) => {
|
|
103
|
+
if (!active) return;
|
|
104
|
+
const pos = playerPosRef.current;
|
|
105
|
+
if (!pos) return;
|
|
106
|
+
|
|
107
|
+
// Fire projectiles
|
|
108
|
+
if (fireKeyRef.current) {
|
|
109
|
+
const pitch = playerPitchRef?.current ?? 0;
|
|
110
|
+
manager.fire(pos[0], pos[1], pos[2], playerHeadingRef.current, pitch, mode);
|
|
111
|
+
// Gunshot sound (throttled to ~5/sec max)
|
|
112
|
+
const now = performance.now();
|
|
113
|
+
if (now - lastFireSoundRef.current > 200) {
|
|
114
|
+
initAudio();
|
|
115
|
+
playGunshot();
|
|
116
|
+
lastFireSoundRef.current = now;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
manager.update(
|
|
121
|
+
delta,
|
|
122
|
+
planePositionsRef.current,
|
|
123
|
+
carPositionsRef.current,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Play explosion + score chime on new hits
|
|
127
|
+
if (manager.hits.length > prevHitCountRef.current) {
|
|
128
|
+
playExplosion();
|
|
129
|
+
playScoreChime();
|
|
130
|
+
}
|
|
131
|
+
prevHitCountRef.current = manager.hits.length;
|
|
132
|
+
|
|
133
|
+
// Render bullets
|
|
134
|
+
const bullets = bulletRef.current;
|
|
135
|
+
const trails = trailRef.current;
|
|
136
|
+
const alive = manager.getAlive();
|
|
137
|
+
|
|
138
|
+
if (bullets && trails) {
|
|
139
|
+
for (let i = 0; i < MAX_VISIBLE; i++) {
|
|
140
|
+
if (i < alive.length) {
|
|
141
|
+
const p = alive[i];
|
|
142
|
+
// Bullet head
|
|
143
|
+
_dummy.position.set(p.x, p.y, p.z);
|
|
144
|
+
_dummy.scale.set(0.25, 0.25, 0.25);
|
|
145
|
+
_dummy.rotation.set(0, 0, 0);
|
|
146
|
+
_dummy.updateMatrix();
|
|
147
|
+
bullets.setMatrixAt(i, _dummy.matrix);
|
|
148
|
+
|
|
149
|
+
// Trail (stretched behind)
|
|
150
|
+
const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy + p.vz * p.vz);
|
|
151
|
+
const trailLen = Math.min(speed * 0.02, 4.0);
|
|
152
|
+
const headingAngle = Math.atan2(p.vx, p.vz);
|
|
153
|
+
const pitchAngle = Math.atan2(-p.vy, Math.sqrt(p.vx * p.vx + p.vz * p.vz));
|
|
154
|
+
_dummy.position.set(
|
|
155
|
+
p.x - p.vx * 0.008,
|
|
156
|
+
p.y - p.vy * 0.008,
|
|
157
|
+
p.z - p.vz * 0.008,
|
|
158
|
+
);
|
|
159
|
+
_dummy.scale.set(0.08, 0.08, trailLen);
|
|
160
|
+
_dummy.rotation.set(pitchAngle, headingAngle, 0, "YXZ");
|
|
161
|
+
_dummy.updateMatrix();
|
|
162
|
+
trails.setMatrixAt(i, _dummy.matrix);
|
|
163
|
+
} else {
|
|
164
|
+
// Hide unused instances
|
|
165
|
+
_dummy.position.set(0, -1000, 0);
|
|
166
|
+
_dummy.scale.set(0, 0, 0);
|
|
167
|
+
_dummy.updateMatrix();
|
|
168
|
+
bullets.setMatrixAt(i, _dummy.matrix);
|
|
169
|
+
trails.setMatrixAt(i, _dummy.matrix);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
bullets.instanceMatrix.needsUpdate = true;
|
|
173
|
+
trails.instanceMatrix.needsUpdate = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Render explosions at hit locations
|
|
177
|
+
const explosions = explosionRef.current;
|
|
178
|
+
if (explosions) {
|
|
179
|
+
const hits = manager.hits;
|
|
180
|
+
const now = performance.now();
|
|
181
|
+
for (let i = 0; i < MAX_EXPLOSIONS; i++) {
|
|
182
|
+
if (i < hits.length) {
|
|
183
|
+
const h = hits[i];
|
|
184
|
+
const age = (now - h.timestamp) / 1000; // seconds
|
|
185
|
+
const life = 1 - Math.min(age / 1.0, 1);
|
|
186
|
+
const scale = 0.5 + age * 4; // expand quickly
|
|
187
|
+
_dummy.position.set(h.x, h.y, h.z);
|
|
188
|
+
_dummy.scale.set(scale * life, scale * life, scale * life);
|
|
189
|
+
_dummy.rotation.set(age * 3, age * 5, 0);
|
|
190
|
+
_dummy.updateMatrix();
|
|
191
|
+
explosions.setMatrixAt(i, _dummy.matrix);
|
|
192
|
+
} else {
|
|
193
|
+
_dummy.position.set(0, -1000, 0);
|
|
194
|
+
_dummy.scale.set(0, 0, 0);
|
|
195
|
+
_dummy.updateMatrix();
|
|
196
|
+
explosions.setMatrixAt(i, _dummy.matrix);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
explosions.instanceMatrix.needsUpdate = true;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (!active) return null;
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<group>
|
|
207
|
+
{/* Bullet heads */}
|
|
208
|
+
<instancedMesh
|
|
209
|
+
ref={bulletRef}
|
|
210
|
+
args={[undefined!, undefined!, MAX_VISIBLE]}
|
|
211
|
+
frustumCulled={false}
|
|
212
|
+
>
|
|
213
|
+
<sphereGeometry args={[1, 6, 6]} />
|
|
214
|
+
<primitive object={bulletMat} attach="material" />
|
|
215
|
+
</instancedMesh>
|
|
216
|
+
|
|
217
|
+
{/* Bullet trails */}
|
|
218
|
+
<instancedMesh
|
|
219
|
+
ref={trailRef}
|
|
220
|
+
args={[undefined!, undefined!, MAX_VISIBLE]}
|
|
221
|
+
frustumCulled={false}
|
|
222
|
+
>
|
|
223
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
224
|
+
<primitive object={trailMat} attach="material" />
|
|
225
|
+
</instancedMesh>
|
|
226
|
+
|
|
227
|
+
{/* Explosion flashes */}
|
|
228
|
+
<instancedMesh
|
|
229
|
+
ref={explosionRef}
|
|
230
|
+
args={[undefined!, undefined!, MAX_EXPLOSIONS]}
|
|
231
|
+
frustumCulled={false}
|
|
232
|
+
>
|
|
233
|
+
<icosahedronGeometry args={[1, 1]} />
|
|
234
|
+
<primitive object={explosionMat} attach="material" />
|
|
235
|
+
</instancedMesh>
|
|
236
|
+
|
|
237
|
+
{/* Muzzle flash light */}
|
|
238
|
+
{playerPosRef.current && (
|
|
239
|
+
<pointLight
|
|
240
|
+
position={playerPosRef.current}
|
|
241
|
+
color="#ff6600"
|
|
242
|
+
intensity={fireKeyRef.current ? 8 : 0}
|
|
243
|
+
distance={15}
|
|
244
|
+
decay={2}
|
|
245
|
+
/>
|
|
246
|
+
)}
|
|
247
|
+
</group>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
interface QueueCounts {
|
|
6
|
+
queued: number;
|
|
7
|
+
processing: number;
|
|
8
|
+
complete: number;
|
|
9
|
+
failed: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function QueueStatusBanner() {
|
|
13
|
+
const [counts, setCounts] = useState<QueueCounts | null>(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
let active = true;
|
|
17
|
+
|
|
18
|
+
async function poll() {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch("/api/queue-status");
|
|
21
|
+
if (res.ok && active) setCounts(await res.json());
|
|
22
|
+
} catch { /* ignore */ }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
poll();
|
|
26
|
+
const id = setInterval(poll, 10_000);
|
|
27
|
+
return () => { active = false; clearInterval(id); };
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
if (!counts) return null;
|
|
31
|
+
|
|
32
|
+
const total = counts.queued + counts.processing + counts.complete + counts.failed;
|
|
33
|
+
const pending = counts.queued + counts.processing;
|
|
34
|
+
|
|
35
|
+
// Hide when everything is done
|
|
36
|
+
if (pending === 0) return null;
|
|
37
|
+
|
|
38
|
+
const pct = total > 0 ? Math.round((counts.complete / total) * 100) : 0;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="absolute top-[5.5rem] left-3 sm:top-[7.5rem] sm:left-5 z-10 bg-black/50 backdrop-blur-xl border border-white/[0.08] rounded-2xl px-4 py-3 w-[220px]">
|
|
42
|
+
{/* Header */}
|
|
43
|
+
<div className="flex items-center gap-2 mb-2.5">
|
|
44
|
+
<span className="relative flex h-2 w-2">
|
|
45
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#E35930] opacity-75" />
|
|
46
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-[#E35930]" />
|
|
47
|
+
</span>
|
|
48
|
+
<span className="text-xs font-medium text-white/70">Indexing Pipeline</span>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
{/* Progress bar */}
|
|
52
|
+
<div className="w-full h-1.5 bg-white/[0.06] rounded-full overflow-hidden mb-2.5">
|
|
53
|
+
<div
|
|
54
|
+
className="h-full bg-[#E35930] rounded-full transition-all duration-700"
|
|
55
|
+
style={{ width: `${pct}%` }}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Stats */}
|
|
60
|
+
<div className="space-y-1.5 text-[11px]">
|
|
61
|
+
<div className="flex justify-between">
|
|
62
|
+
<span className="text-white/40">Complete</span>
|
|
63
|
+
<span className="text-white/70 font-mono">{counts.complete}/{total}</span>
|
|
64
|
+
</div>
|
|
65
|
+
{counts.processing > 0 && (
|
|
66
|
+
<div className="flex justify-between">
|
|
67
|
+
<span className="text-white/40">Processing</span>
|
|
68
|
+
<span className="text-[#E35930] font-mono">{counts.processing}</span>
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
{counts.queued > 0 && (
|
|
72
|
+
<div className="flex justify-between">
|
|
73
|
+
<span className="text-white/40">Queued</span>
|
|
74
|
+
<span className="text-yellow-400/80 font-mono">{counts.queued}</span>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Throughput info */}
|
|
80
|
+
<div className="mt-2.5 pt-2.5 border-t border-white/[0.06] space-y-1 text-[10px] text-white/30">
|
|
81
|
+
<p>Dynamic parallel slicing</p>
|
|
82
|
+
<p>Rate-limited at 90 req/s</p>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Html } from "@react-three/drei";
|
|
5
|
+
import * as THREE from "three";
|
|
6
|
+
import type { RemotePlayer } from "@/lib/multiplayer-manager";
|
|
7
|
+
|
|
8
|
+
interface RealPlayerTagsProps {
|
|
9
|
+
active: boolean;
|
|
10
|
+
remotePlayers: Map<string, RemotePlayer>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function shortWallet(wallet: string): string {
|
|
14
|
+
return wallet.length <= 10 ? wallet : `${wallet.slice(0, 4)}…${wallet.slice(-4)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function RealPlayerTags({ active, remotePlayers }: RealPlayerTagsProps) {
|
|
18
|
+
const [players, setPlayers] = useState<RemotePlayer[]>([]);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!active) {
|
|
22
|
+
setPlayers([]);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const sync = () => {
|
|
27
|
+
setPlayers(
|
|
28
|
+
Array.from(remotePlayers.values()).filter((p) => p.mode !== "spectating"),
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
sync();
|
|
33
|
+
const interval = window.setInterval(sync, 120);
|
|
34
|
+
return () => window.clearInterval(interval);
|
|
35
|
+
}, [active, remotePlayers]);
|
|
36
|
+
|
|
37
|
+
if (!active || players.length === 0) return null;
|
|
38
|
+
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
{players.map((p) => {
|
|
44
|
+
const isCar = p.mode === "car";
|
|
45
|
+
const color = isCar ? "#38bdf8" : "#ec4899";
|
|
46
|
+
const yOffset = isCar ? 2.3 : 4.0;
|
|
47
|
+
const stale = now - p.lastSeen > 3000;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<group key={p.wallet} position={[p.x, p.y + yOffset, p.z]}>
|
|
51
|
+
{/* Floating marker core */}
|
|
52
|
+
<mesh>
|
|
53
|
+
<sphereGeometry args={[0.18, 12, 12]} />
|
|
54
|
+
<meshBasicMaterial color={color} transparent opacity={stale ? 0.35 : 0.95} />
|
|
55
|
+
</mesh>
|
|
56
|
+
|
|
57
|
+
{/* Ring helps visibility from distance */}
|
|
58
|
+
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.3, 0]}>
|
|
59
|
+
<ringGeometry args={[0.3, 0.42, 20]} />
|
|
60
|
+
<meshBasicMaterial
|
|
61
|
+
color={color}
|
|
62
|
+
side={THREE.DoubleSide}
|
|
63
|
+
transparent
|
|
64
|
+
opacity={stale ? 0.2 : 0.6}
|
|
65
|
+
/>
|
|
66
|
+
</mesh>
|
|
67
|
+
|
|
68
|
+
{/* Floating billboard label */}
|
|
69
|
+
<Html center position={[0, 0.55, 0]} distanceFactor={14} style={{ pointerEvents: "none" }}>
|
|
70
|
+
<div className="px-2 py-1 rounded-md border border-white/20 bg-black/75 backdrop-blur-sm text-center leading-tight shadow-lg">
|
|
71
|
+
<div className="text-[9px] font-bold tracking-wide text-white">REAL PLAYER</div>
|
|
72
|
+
<div className={`text-[9px] font-mono ${isCar ? "text-sky-300" : "text-pink-300"}`}>
|
|
73
|
+
{isCar ? "🚗 DRIVER" : "✈ PILOT"} · {shortWallet(p.wallet)}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</Html>
|
|
77
|
+
</group>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
}
|