mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-07-12 15:04:47 +00:00
migrate to vite (#16)
This commit is contained in:
parent
1373f12ab4
commit
5025065188
165 changed files with 56708 additions and 8931 deletions
27
src/components/App.tsx
Normal file
27
src/components/App.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"use client";
|
||||
import { Suspense } from "react";
|
||||
import { NuqsAdapter } from "nuqs/adapters/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { FeaturesProvider } from "@/src/components/FeaturesProvider";
|
||||
import { MapInspector } from "@/src/components/MapInspector";
|
||||
import { SettingsProvider } from "@/src/components/SettingsProvider";
|
||||
|
||||
// Three.js has its own loaders for textures and models, but we need to load other
|
||||
// stuff too, e.g. missions, terrains, and more. This client is used for those.
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<Suspense>
|
||||
<NuqsAdapter>
|
||||
<FeaturesProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SettingsProvider>
|
||||
<MapInspector />
|
||||
</SettingsProvider>
|
||||
</QueryClientProvider>
|
||||
</FeaturesProvider>
|
||||
</NuqsAdapter>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
@ -133,11 +133,7 @@ function FlagMarkerSlot({ entity }: { entity: GameEntity }) {
|
|||
return <FlagMarker entity={entity} />;
|
||||
}
|
||||
|
||||
function PositionedEntityWrapper({
|
||||
entity,
|
||||
}: {
|
||||
entity: PositionedEntity;
|
||||
}) {
|
||||
function PositionedEntityWrapper({ entity }: { entity: PositionedEntity }) {
|
||||
const position = entity.position;
|
||||
const scale = entity.scale;
|
||||
const quaternion = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ import { setupTexture } from "../textureUtils";
|
|||
import { useAnisotropy } from "./useAnisotropy";
|
||||
import { useDebug, useSettings } from "./SettingsProvider";
|
||||
import { useShapeInfo, isOrganicShape } from "./ShapeInfoProvider";
|
||||
import { useEngineSelector, effectNow, engineStore } from "../state/engineStore";
|
||||
import {
|
||||
useEngineSelector,
|
||||
effectNow,
|
||||
engineStore,
|
||||
} from "../state/engineStore";
|
||||
import { FloatingLabel } from "./FloatingLabel";
|
||||
import {
|
||||
useIflTexture,
|
||||
|
|
@ -296,7 +300,10 @@ const StaticTexture = memo(function StaticTexture({
|
|||
|
||||
const url = useMemo(() => {
|
||||
if (!resourcePath) {
|
||||
log.warn("No resource_path found on \"%s\" — rendering fallback", shapeName);
|
||||
log.warn(
|
||||
'No resource_path found on "%s" — rendering fallback',
|
||||
shapeName,
|
||||
);
|
||||
}
|
||||
return resourcePath ? textureToUrl(resourcePath) : FALLBACK_TEXTURE_URL;
|
||||
}, [resourcePath, shapeName]);
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ function InteriorTexture({
|
|||
const debugMode = debugContext?.debugMode ?? false;
|
||||
const anisotropy = useAnisotropy();
|
||||
const url = textureToUrl(materialName);
|
||||
const texture = useTexture(url, (texture) => setupTexture(texture, { anisotropy }));
|
||||
const texture = useTexture(url, (texture) =>
|
||||
setupTexture(texture, { anisotropy }),
|
||||
);
|
||||
// Check for self-illuminating flag in material userData
|
||||
// Note: The io_dif Blender add-on needs to be updated to export material flags
|
||||
const flagNames = new Set<string>(material?.userData?.flag_names ?? []);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import {
|
|||
useRef,
|
||||
lazy,
|
||||
Activity,
|
||||
ViewTransition,
|
||||
ReactNode,
|
||||
// ViewTransition,
|
||||
} from "react";
|
||||
import { Camera } from "three";
|
||||
import { InspectorControls } from "@/src/components/InspectorControls";
|
||||
|
|
@ -59,6 +60,10 @@ import {
|
|||
import styles from "./MapInspector.module.css";
|
||||
import { useTouchDevice } from "./useTouchDevice";
|
||||
|
||||
function ViewTransition({ children }: { children: ReactNode }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
function createLazy(
|
||||
name: string,
|
||||
loader: () => Promise<{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ export function Turret({ object }: { object: TorqueObject }) {
|
|||
// `initialBarrel` is optional - turrets can exist without a barrel mounted.
|
||||
// But if we do have one, it needs a shape name.
|
||||
if (barrelDatablockName && !barrelShapeName) {
|
||||
log.error("Turret missing shape for barrel datablock: %s", barrelDatablockName);
|
||||
log.error(
|
||||
"Turret missing shape for barrel datablock: %s",
|
||||
barrelDatablockName,
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ShapeInfoProvider type="Turret" object={object} shapeName={shapeName}>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ export function WaterMaterial({
|
|||
}) {
|
||||
const url = textureToUrl(surfaceTexture);
|
||||
const anisotropy = useAnisotropy();
|
||||
const texture = useTexture(url, (texture) => setupTexture(texture, { anisotropy }));
|
||||
const texture = useTexture(url, (texture) =>
|
||||
setupTexture(texture, { anisotropy }),
|
||||
);
|
||||
|
||||
return (
|
||||
<meshStandardMaterial
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function parseLogConfig(): {
|
|||
globalLevel: string;
|
||||
modules: Map<string, string>;
|
||||
} {
|
||||
const raw = process.env.NEXT_PUBLIC_LOG?.trim();
|
||||
const raw = import.meta.env.NEXT_PUBLIC_LOG?.trim();
|
||||
if (!raw) return { globalLevel: "info", modules: new Map() };
|
||||
|
||||
let globalLevel: string | null = null;
|
||||
|
|
|
|||
44
src/main.css
Normal file
44
src/main.css
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: black;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Open Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
max-width: 80px;
|
||||
}
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./main.css";
|
||||
import App from "./components/App.tsx";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
49549
src/manifest.json
Normal file
49549
src/manifest.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
import untypedManifest from "@/public/manifest.json";
|
||||
import untypedManifest from "./manifest.json";
|
||||
import { normalizePath } from "./stringUtils";
|
||||
|
||||
// Source tuple: [sourcePath] or [sourcePath, actualPath] if casing differs
|
||||
|
|
|
|||
|
|
@ -234,11 +234,19 @@ export function ghostToSceneObject(
|
|||
switch (className) {
|
||||
case "TerrainBlock":
|
||||
result = terrainFromGhost(ghostIndex, data);
|
||||
log.debug("TerrainBlock #%d: terrFileName=%s", ghostIndex, (result as SceneTerrainBlock).terrFileName);
|
||||
log.debug(
|
||||
"TerrainBlock #%d: terrFileName=%s",
|
||||
ghostIndex,
|
||||
(result as SceneTerrainBlock).terrFileName,
|
||||
);
|
||||
return result;
|
||||
case "InteriorInstance":
|
||||
result = interiorFromGhost(ghostIndex, data);
|
||||
log.debug("InteriorInstance #%d: interiorFile=%s", ghostIndex, (result as SceneInteriorInstance).interiorFile);
|
||||
log.debug(
|
||||
"InteriorInstance #%d: interiorFile=%s",
|
||||
ghostIndex,
|
||||
(result as SceneInteriorInstance).interiorFile,
|
||||
);
|
||||
return result;
|
||||
case "TSStatic":
|
||||
return tsStaticFromGhost(ghostIndex, data);
|
||||
|
|
@ -247,9 +255,14 @@ export function ghostToSceneObject(
|
|||
const sky = result as SceneSky;
|
||||
log.debug(
|
||||
"Sky #%d: materialList=%s fogColor=(%s, %s, %s) visibleDist=%d fogDist=%d useSkyTextures=%s",
|
||||
ghostIndex, sky.materialList,
|
||||
sky.fogColor.r.toFixed(3), sky.fogColor.g.toFixed(3), sky.fogColor.b.toFixed(3),
|
||||
sky.visibleDistance, sky.fogDistance, sky.useSkyTextures,
|
||||
ghostIndex,
|
||||
sky.materialList,
|
||||
sky.fogColor.r.toFixed(3),
|
||||
sky.fogColor.g.toFixed(3),
|
||||
sky.fogColor.b.toFixed(3),
|
||||
sky.visibleDistance,
|
||||
sky.fogDistance,
|
||||
sky.useSkyTextures,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -259,9 +272,15 @@ export function ghostToSceneObject(
|
|||
log.debug(
|
||||
"Sun #%d: dir=(%s, %s, %s) color=(%s, %s, %s) ambient=(%s, %s, %s)",
|
||||
ghostIndex,
|
||||
sun.direction.x.toFixed(3), sun.direction.y.toFixed(3), sun.direction.z.toFixed(3),
|
||||
sun.color.r.toFixed(3), sun.color.g.toFixed(3), sun.color.b.toFixed(3),
|
||||
sun.ambient.r.toFixed(3), sun.ambient.g.toFixed(3), sun.ambient.b.toFixed(3),
|
||||
sun.direction.x.toFixed(3),
|
||||
sun.direction.y.toFixed(3),
|
||||
sun.direction.z.toFixed(3),
|
||||
sun.color.r.toFixed(3),
|
||||
sun.color.g.toFixed(3),
|
||||
sun.color.b.toFixed(3),
|
||||
sun.ambient.r.toFixed(3),
|
||||
sun.ambient.g.toFixed(3),
|
||||
sun.ambient.b.toFixed(3),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,9 +262,7 @@ export const engineStore = createStore<EngineStoreState>()(
|
|||
rate: recording ? 1 : state.playback.rate,
|
||||
durationMs,
|
||||
// Preserve the last snapshot so HUD/chat persist after unload.
|
||||
streamSnapshot: recording
|
||||
? null
|
||||
: state.playback.streamSnapshot,
|
||||
streamSnapshot: recording ? null : state.playback.streamSnapshot,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -424,26 +424,17 @@ export function useSceneMissionArea(): SceneMissionArea | null {
|
|||
|
||||
/** Hook returning which data source is currently populating entities. */
|
||||
export function useDataSource(): DataSource | null {
|
||||
return useStoreWithEqualityFn(
|
||||
gameEntityStore,
|
||||
(state) => state.dataSource,
|
||||
);
|
||||
return useStoreWithEqualityFn(gameEntityStore, (state) => state.dataSource);
|
||||
}
|
||||
|
||||
/** Hook returning the current mission name. */
|
||||
export function useMissionName(): string | null {
|
||||
return useStoreWithEqualityFn(
|
||||
gameEntityStore,
|
||||
(state) => state.missionName,
|
||||
);
|
||||
return useStoreWithEqualityFn(gameEntityStore, (state) => state.missionName);
|
||||
}
|
||||
|
||||
/** Hook returning the mission type short code (e.g. "CTF"). */
|
||||
export function useMissionType(): string | null {
|
||||
return useStoreWithEqualityFn(
|
||||
gameEntityStore,
|
||||
(state) => state.missionType,
|
||||
);
|
||||
return useStoreWithEqualityFn(gameEntityStore, (state) => state.missionType);
|
||||
}
|
||||
|
||||
/** Hook returning the mission type display name (e.g. "Capture the Flag"). */
|
||||
|
|
@ -480,10 +471,7 @@ export function useServerDisplayName(): string | null {
|
|||
|
||||
/** Hook returning the name of the player who recorded the demo / connected. */
|
||||
export function useRecorderName(): string | null {
|
||||
return useStoreWithEqualityFn(
|
||||
gameEntityStore,
|
||||
(state) => state.recorderName,
|
||||
);
|
||||
return useStoreWithEqualityFn(gameEntityStore, (state) => state.recorderName);
|
||||
}
|
||||
|
||||
/** Hook returning the demo recording date string. */
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export interface LiveConnectionStore extends LiveConnectionState {
|
|||
}
|
||||
|
||||
const DEFAULT_RELAY_URL =
|
||||
process.env.NEXT_PUBLIC_RELAY_URL || "ws://localhost:8765";
|
||||
import.meta.env.NEXT_PUBLIC_RELAY_URL || "ws://localhost:8765";
|
||||
|
||||
export const liveConnectionStore = createStore<LiveConnectionStore>(
|
||||
(set, get) => ({
|
||||
|
|
|
|||
|
|
@ -482,15 +482,19 @@ export abstract class StreamEngine implements StreamingPlayback {
|
|||
const classId = data.classId as number | undefined;
|
||||
const objectData = data.objectData as Record<string, unknown> | undefined;
|
||||
const hasData = data._hasObjectData as boolean | undefined;
|
||||
const className = typeof classId === "number"
|
||||
? this.registry.getGhostParser(classId)?.name ?? `classId=${classId}`
|
||||
: "?";
|
||||
const className =
|
||||
typeof classId === "number"
|
||||
? (this.registry.getGhostParser(classId)?.name ??
|
||||
`classId=${classId}`)
|
||||
: "?";
|
||||
log.debug(
|
||||
"GhostAlwaysObjectEvent: ghost=%d class=%s hasData=%s %s",
|
||||
ghostIndex,
|
||||
className,
|
||||
hasData,
|
||||
objectData ? `keys=[${Object.keys(objectData).join(",")}]` : "(no data)",
|
||||
objectData
|
||||
? `keys=[${Object.keys(objectData).join(",")}]`
|
||||
: "(no data)",
|
||||
);
|
||||
if (ghostIndex != null && classId != null) {
|
||||
this.processGhostUpdate({
|
||||
|
|
|
|||
|
|
@ -94,7 +94,15 @@ function extractMissionInfo(demoValues: string[]): DemoMissionInfo {
|
|||
}
|
||||
}
|
||||
|
||||
return { missionDisplayName, missionType, gameClassName, serverDisplayName, mod, recorderName, recordingDate };
|
||||
return {
|
||||
missionDisplayName,
|
||||
missionType,
|
||||
gameClassName,
|
||||
serverDisplayName,
|
||||
mod,
|
||||
recorderName,
|
||||
recordingDate,
|
||||
};
|
||||
}
|
||||
|
||||
interface ParsedDemoValues {
|
||||
|
|
|
|||
|
|
@ -357,7 +357,10 @@ export function replaceWithShapeMaterial(
|
|||
const texture = loadTexture(url);
|
||||
const isTranslucent = flagNames.has("Translucent");
|
||||
if (isOrganic || isTranslucent) {
|
||||
setupTexture(texture, { disableMipmaps: true, anisotropy: options.anisotropy });
|
||||
setupTexture(texture, {
|
||||
disableMipmaps: true,
|
||||
anisotropy: options.anisotropy,
|
||||
});
|
||||
} else {
|
||||
setupTexture(texture, { anisotropy: options.anisotropy });
|
||||
}
|
||||
|
|
@ -474,7 +477,12 @@ export function processShapeScene(
|
|||
return result.material;
|
||||
});
|
||||
} else if (node.material) {
|
||||
const result = replaceWithShapeMaterial(node.material, vis, isOrganic, options);
|
||||
const result = replaceWithShapeMaterial(
|
||||
node.material,
|
||||
vis,
|
||||
isOrganic,
|
||||
options,
|
||||
);
|
||||
if (result.initialize) {
|
||||
iflInitializers.push({ mesh: node, initialize: result.initialize });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,9 @@ export function registerEngineStubs(runtime: TorqueRuntime): void {
|
|||
} catch (err) {
|
||||
log.error(
|
||||
"schedule: error calling %s on %s: %o",
|
||||
methodName, this_._id, err,
|
||||
methodName,
|
||||
this_._id,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}, ms);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import TorqueScript from "@/generated/TorqueScript.cjs";
|
||||
import { parse as torqueScriptParse } from "@/generated/TorqueScript.js";
|
||||
import { generate, type GeneratorOptions } from "./codegen";
|
||||
import type { Program } from "./ast";
|
||||
import { createRuntime } from "./runtime";
|
||||
|
|
@ -40,7 +40,7 @@ export type TranspileOptions = ParseOptions & GeneratorOptions;
|
|||
|
||||
export function parse(source: string, options?: ParseOptions): Program {
|
||||
try {
|
||||
return TorqueScript.parse(source);
|
||||
return torqueScriptParse(source);
|
||||
} catch (error: any) {
|
||||
if (options?.filename && error.location) {
|
||||
throw new Error(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue