t2-mapper/src/components/InspectorControls.tsx

225 lines
6.2 KiB
TypeScript
Raw Normal View History

import { Fragment, useMemo } from "react";
import { getMissionInfo, getMissionList, getSourceAndPath } from "../manifest";
2025-11-25 23:44:37 -08:00
import { useControls, useDebug, useSettings } from "./SettingsProvider";
2025-11-26 06:04:45 -08:00
import orderBy from "lodash.orderby";
2025-11-13 22:55:58 -08:00
const excludeMissions = new Set([
"SkiFree",
"SkiFree_Daily",
"SkiFree_Randomizer",
]);
2025-11-26 14:37:49 -08:00
const sourceGroupNames = {
"missions.vl2": "Official",
"TR2final105-client.vl2": "Team Rabbit 2",
2025-12-01 22:33:12 -08:00
"z_mappacks/CTF/Classic_maps_v1.vl2": "Classic",
"z_mappacks/CTF/DynamixFinalPack.vl2": "Official",
"z_mappacks/CTF/KryMapPack_b3EDIT.vl2": "KryMapPack",
"z_mappacks/CTF/S5maps.vl2": "S5",
"z_mappacks/CTF/S8maps.vl2": "S8",
"z_mappacks/CTF/TWL-MapPack.vl2": "TWL",
"z_mappacks/CTF/TWL-MapPackEDIT.vl2": "TWL",
"z_mappacks/CTF/TWL2-MapPack.vl2": "TWL2",
"z_mappacks/CTF/TWL2-MapPackEDIT.vl2": "TWL2",
"z_mappacks/TWL_T2arenaOfficialMaps.vl2": "Arena",
"z_mappacks/z_DMP2-V0.6.vl2": "DMP2 (Discord Map Pack)",
"z_mappacks/zDMP-4.7.3DX.vl2": "DMP (Discord Map Pack)",
// "SkiFreeGameType.vl2": "SkiFree",
};
const dirGroupNames = {
"z_mappacks/DM": "DM",
"z_mappacks/LCTF": "LCTF",
"z_mappacks/Lak": "LakRabbit",
};
const getDirName = (sourcePath: string) => {
const match = sourcePath.match(/^(.*)(\/[^/]+)$/);
return match ? match[1] : "";
};
const groupedMissions = getMissionList().reduce(
(groupMap, missionName) => {
const missionInfo = getMissionInfo(missionName);
const [sourcePath] = getSourceAndPath(missionInfo.resourcePath);
2025-12-01 22:33:12 -08:00
const sourceDir = getDirName(sourcePath);
const groupName =
sourceGroupNames[sourcePath] ?? dirGroupNames[sourceDir] ?? null;
const groupMissions = groupMap.get(groupName) ?? [];
if (!excludeMissions.has(missionName)) {
groupMissions.push({
resourcePath: missionInfo.resourcePath,
missionName,
displayName: missionInfo.displayName,
2025-12-01 22:33:12 -08:00
sourcePath,
});
groupMap.set(groupName, groupMissions);
}
return groupMap;
},
new Map<
string | null,
Array<{
resourcePath: string;
missionName: string;
displayName: string;
2025-12-01 22:33:12 -08:00
sourcePath: string;
}>
2025-11-29 09:08:20 -08:00
>(),
2025-11-26 06:04:45 -08:00
);
2025-11-13 22:55:58 -08:00
groupedMissions.forEach((groupMissions, groupName) => {
groupedMissions.set(
groupName,
orderBy(
groupMissions,
[
(missionInfo) =>
(missionInfo.displayName || missionInfo.missionName).toLowerCase(),
],
2025-11-29 09:08:20 -08:00
["asc"],
),
);
});
2025-11-13 22:55:58 -08:00
export function InspectorControls({
missionName,
onChangeMission,
}: {
missionName: string;
onChangeMission: (name: string) => void;
}) {
const {
fogEnabled,
setFogEnabled,
fov,
setFov,
2025-11-15 16:33:18 -08:00
audioEnabled,
setAudioEnabled,
2025-12-01 22:33:12 -08:00
animationEnabled,
setAnimationEnabled,
} = useSettings();
2025-11-25 23:44:37 -08:00
const { speedMultiplier, setSpeedMultiplier } = useControls();
const { debugMode, setDebugMode } = useDebug();
const groupedMissionOptions = useMemo(() => {
const groups = orderBy(
Array.from(groupedMissions.entries()),
[
([groupName]) =>
groupName === "Official" ? 0 : groupName == null ? 2 : 1,
([groupName]) => (groupName ? groupName.toLowerCase() : ""),
],
2025-11-29 09:08:20 -08:00
["asc", "asc"],
);
return groups;
}, []);
2025-11-13 22:55:58 -08:00
return (
2025-11-14 00:15:28 -08:00
<div
id="controls"
2025-11-14 22:46:58 -08:00
onKeyDown={(e) => e.stopPropagation()}
2025-11-14 00:15:28 -08:00
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
2025-11-13 22:55:58 -08:00
<select
id="missionList"
value={missionName}
onChange={(event) => onChangeMission(event.target.value)}
>
{groupedMissionOptions.map(([groupName, groupMissions]) =>
groupName ? (
<optgroup key={groupName} label={groupName}>
{groupMissions.map((mission) => (
<option key={mission.missionName} value={mission.missionName}>
{mission.displayName || mission.missionName}
</option>
))}
</optgroup>
) : (
<Fragment key="null">
<hr />
{groupMissions.map((mission) => (
<option key={mission.missionName} value={mission.missionName}>
{mission.displayName || mission.missionName}
</option>
))}
</Fragment>
2025-11-29 09:08:20 -08:00
),
)}
2025-11-13 22:55:58 -08:00
</select>
<div className="CheckboxField">
<input
id="fogInput"
type="checkbox"
checked={fogEnabled}
onChange={(event) => {
setFogEnabled(event.target.checked);
2025-11-13 22:55:58 -08:00
}}
/>
<label htmlFor="fogInput">Fog?</label>
</div>
2025-11-15 16:33:18 -08:00
<div className="CheckboxField">
<input
id="audioInput"
type="checkbox"
checked={audioEnabled}
onChange={(event) => {
setAudioEnabled(event.target.checked);
}}
/>
<label htmlFor="audioInput">Audio?</label>
</div>
2025-12-01 22:33:12 -08:00
<div className="CheckboxField">
<input
id="animationInput"
type="checkbox"
checked={animationEnabled}
onChange={(event) => {
setAnimationEnabled(event.target.checked);
}}
/>
<label htmlFor="animationInput">Animation?</label>
</div>
2025-11-19 02:21:55 -05:00
<div className="CheckboxField">
<input
id="debugInput"
type="checkbox"
checked={debugMode}
onChange={(event) => {
setDebugMode(event.target.checked);
}}
/>
<label htmlFor="debugInput">Debug?</label>
</div>
<div className="Field">
<label htmlFor="fovInput">FOV</label>
<input
2025-12-01 22:33:12 -08:00
id="fovInput"
type="range"
min={75}
max={120}
step={5}
value={fov}
onChange={(event) => setFov(parseInt(event.target.value))}
/>
<output htmlFor="speedInput">{fov}</output>
</div>
<div className="Field">
<label htmlFor="speedInput">Speed</label>
<input
id="speedInput"
type="range"
min={0.1}
max={5}
step={0.05}
value={speedMultiplier}
onChange={(event) =>
setSpeedMultiplier(parseFloat(event.target.value))
}
/>
</div>
2025-11-13 22:55:58 -08:00
</div>
);
}