2025-11-26 12:58:31 -08:00
|
|
|
import { Fragment, useMemo } from "react";
|
2025-12-01 00:17:27 -08:00
|
|
|
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 = {
|
2025-11-26 12:58:31 -08:00
|
|
|
"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] : "";
|
2025-11-26 12:58:31 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const groupedMissions = getMissionList().reduce(
|
|
|
|
|
(groupMap, missionName) => {
|
|
|
|
|
const missionInfo = getMissionInfo(missionName);
|
2025-12-01 00:17:27 -08:00
|
|
|
const [sourcePath] = getSourceAndPath(missionInfo.resourcePath);
|
2025-12-01 22:33:12 -08:00
|
|
|
const sourceDir = getDirName(sourcePath);
|
|
|
|
|
const groupName =
|
|
|
|
|
sourceGroupNames[sourcePath] ?? dirGroupNames[sourceDir] ?? null;
|
2025-11-26 12:58:31 -08:00
|
|
|
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,
|
2025-11-26 12:58:31 -08:00
|
|
|
});
|
|
|
|
|
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-26 12:58:31 -08:00
|
|
|
}>
|
2025-11-29 09:08:20 -08:00
|
|
|
>(),
|
2025-11-26 06:04:45 -08:00
|
|
|
);
|
2025-11-13 22:55:58 -08:00
|
|
|
|
2025-11-26 12:58:31 -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-26 12:58:31 -08:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-13 22:55:58 -08:00
|
|
|
export function InspectorControls({
|
|
|
|
|
missionName,
|
|
|
|
|
onChangeMission,
|
|
|
|
|
}: {
|
|
|
|
|
missionName: string;
|
|
|
|
|
onChangeMission: (name: string) => void;
|
|
|
|
|
}) {
|
2025-11-13 23:41:10 -08:00
|
|
|
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,
|
2025-11-13 23:41:10 -08:00
|
|
|
} = useSettings();
|
2025-11-25 23:44:37 -08:00
|
|
|
const { speedMultiplier, setSpeedMultiplier } = useControls();
|
|
|
|
|
const { debugMode, setDebugMode } = useDebug();
|
2025-11-13 23:41:10 -08:00
|
|
|
|
2025-11-26 12:58:31 -08:00
|
|
|
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"],
|
2025-11-26 12:58:31 -08:00
|
|
|
);
|
|
|
|
|
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)}
|
|
|
|
|
>
|
2025-11-26 12:58:31 -08:00
|
|
|
{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-26 12:58:31 -08:00
|
|
|
)}
|
2025-11-13 22:55:58 -08:00
|
|
|
</select>
|
|
|
|
|
<div className="CheckboxField">
|
|
|
|
|
<input
|
|
|
|
|
id="fogInput"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={fogEnabled}
|
|
|
|
|
onChange={(event) => {
|
2025-11-13 23:41:10 -08:00
|
|
|
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>
|
2025-11-13 23:41:10 -08:00
|
|
|
<div className="Field">
|
|
|
|
|
<label htmlFor="fovInput">FOV</label>
|
|
|
|
|
<input
|
2025-12-01 22:33:12 -08:00
|
|
|
id="fovInput"
|
2025-11-13 23:41:10 -08:00
|
|
|
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>
|
|
|
|
|
);
|
|
|
|
|
}
|