mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Readded command mode options
This commit is contained in:
@@ -6,6 +6,7 @@ import { OlTooltip } from "./oltooltip";
|
||||
|
||||
export function OlStateButton(props: {
|
||||
className?: string;
|
||||
borderColor?: string | null;
|
||||
checked: boolean;
|
||||
icon: IconProp;
|
||||
tooltip: string;
|
||||
@@ -35,6 +36,9 @@ export function OlStateButton(props: {
|
||||
data-checked={props.checked}
|
||||
type="button"
|
||||
className={className}
|
||||
style={{
|
||||
border: props.borderColor ? "2px solid " + props.borderColor : "0px solid transparent"
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setHover(true);
|
||||
}}
|
||||
|
||||
@@ -4,8 +4,10 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
|
||||
|
||||
export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprint; onClick: () => void }) {
|
||||
const pillString = !["aircraft", "helicopter"].includes(props.blueprint.category) ? props.blueprint.type : props.blueprint.abilities;
|
||||
export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprint; showCost: boolean; cost: number; onClick: () => void }) {
|
||||
let pillString = "" as string | undefined
|
||||
if (props.showCost) pillString = `${props.cost} points`
|
||||
else pillString = !["aircraft", "helicopter"].includes(props.blueprint.category) ? props.blueprint.type : props.blueprint.abilities
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
@@ -24,7 +26,7 @@ export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprin
|
||||
dark:text-olympus-50
|
||||
`}
|
||||
>
|
||||
{!["aircraft", "helicopter"].includes(props.blueprint.category) ? props.blueprint.type : props.blueprint.abilities}
|
||||
{pillString}
|
||||
</div>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
@@ -9,15 +9,59 @@ import { OlAccordion } from "../components/olaccordion";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
import { AirbaseSelectedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
|
||||
export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase: Airbase | null; children?: JSX.Element | JSX.Element[] }) {
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
AIRCRAFT,
|
||||
HELICOPTER,
|
||||
}
|
||||
|
||||
export function AirbaseMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [airbase, setAirbase] = useState(null as null | Airbase);
|
||||
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const [selectedRole, setSelectedRole] = useState(null as null | string);
|
||||
const [runwaysAccordionOpen, setRunwaysAccordionOpen] = useState(false);
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE);
|
||||
|
||||
const [filteredAircraft, filteredHelicopters] = [{}, {}] // TODOgetUnitsByLabel(filterString);
|
||||
useEffect(() => {
|
||||
AirbaseSelectedEvent.on((airbase) => {
|
||||
setAirbase(airbase);
|
||||
});
|
||||
|
||||
UnitDatabaseLoadedEvent.on(() => {
|
||||
setRoles({
|
||||
aircraft: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getRoles((unit) => unit.category === "aircraft"),
|
||||
helicopter: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getRoles((unit) => unit.category === "helicopter"),
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
else setBlueprints(getApp()?.getUnitsManager().getDatabase().getBlueprints());
|
||||
}, [selectedRole, openAccordion]);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
const filteredBlueprints: UnitBlueprint[] = [];
|
||||
if (blueprints) {
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.enabled && (filterString === "" || blueprint.label.toLowerCase().includes(filterString.toLowerCase()))) filteredBlueprints.push(blueprint);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu title={props.airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
|
||||
<Menu title={airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-2 font-normal text-gray-800
|
||||
@@ -25,7 +69,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
`}
|
||||
>
|
||||
<div
|
||||
data-coalition={props.airbase?.getCoalition()}
|
||||
data-coalition={airbase?.getCoalition()}
|
||||
className={`
|
||||
flex flex-col content-center justify-between gap-2 border-l-4
|
||||
bg-olympus-200/30 py-3 pl-4 pr-5
|
||||
@@ -36,29 +80,35 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-gray-400">ICAO name</span>
|
||||
<span>{props.airbase?.getChartData().ICAO !== "" ? props.airbase?.getChartData().ICAO : "N/A"}</span>
|
||||
<span>{airbase?.getChartData().ICAO !== "" ? airbase?.getChartData().ICAO : "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-gray-400">TACAN</span>
|
||||
<span>{props.airbase?.getChartData().TACAN !== "" ? props.airbase?.getChartData().TACAN : "None"}</span>
|
||||
<span>{airbase?.getChartData().TACAN !== "" ? airbase?.getChartData().TACAN : "None"}</span>
|
||||
</div>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-gray-400">Elevation</span>
|
||||
<span>{props.airbase?.getChartData().elevation !== "" ? props.airbase?.getChartData().elevation : "N/A"}ft</span>
|
||||
<span>{airbase?.getChartData().elevation !== "" ? airbase?.getChartData().elevation : "N/A"}ft</span>
|
||||
</div>
|
||||
{
|
||||
// TODO
|
||||
// TODO I can't remember what tho
|
||||
}
|
||||
<OlAccordion title={`Runways`} className="!p-0 !text-gray-400">
|
||||
<OlAccordion
|
||||
title={`Runways`}
|
||||
className="!p-0 !text-gray-400"
|
||||
onClick={() => setRunwaysAccordionOpen(!runwaysAccordionOpen)}
|
||||
open={runwaysAccordionOpen}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
{props.airbase?.getChartData().runways.map((runway, idx) => {
|
||||
{airbase?.getChartData().runways.map((runway, idx) => {
|
||||
return (
|
||||
<>
|
||||
{Object.keys(runway.headings[0]).map((runwayName) => {
|
||||
return (
|
||||
<div key={`${idx}-${runwayName}`} className={`
|
||||
flex w-full justify-between
|
||||
`}>
|
||||
<div
|
||||
key={`${idx}-${runwayName}`}
|
||||
className={`flex w-full justify-between`}
|
||||
>
|
||||
<span>
|
||||
{" "}
|
||||
<span className="text-gray-400">RWY</span> {runwayName}
|
||||
@@ -82,7 +132,6 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex gap-2 px-5 text-white bold">
|
||||
{blueprint && (
|
||||
<FaArrowLeft
|
||||
@@ -95,42 +144,98 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
)}
|
||||
<span className="my-auto">Spawn units at airbase</span>
|
||||
</div>
|
||||
{blueprint === null && (
|
||||
<div className="p-5">
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion
|
||||
title={`Aircraft`}
|
||||
open={openAccordion == CategoryAccordion.AIRCRAFT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.AIRCRAFT ? CategoryAccordion.NONE : CategoryAccordion.AIRCRAFT);
|
||||
setSelectedRole(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{roles.aircraft.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Helicopters`}
|
||||
open={openAccordion == CategoryAccordion.HELICOPTER}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.HELICOPTER ? CategoryAccordion.NONE : CategoryAccordion.HELICOPTER);
|
||||
setSelectedRole(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{roles.helicopter.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
<>
|
||||
{blueprint === null && (
|
||||
<div className="p-5">
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion title={`Aircraft`}>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAircraft).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Helicopters`}>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredHelicopters).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!(blueprint === null) && (
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
spawnAtLocation={false}
|
||||
airbase={props.airbase}
|
||||
coalition={(props.airbase?.getCoalition() ?? "blue") as Coalition}
|
||||
/>
|
||||
<UnitSpawnMenu blueprint={blueprint} spawnAtLocation={false} airbase={airbase} coalition={(airbase?.getCoalition() ?? "blue") as Coalition} />
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
|
||||
@@ -1,45 +1,74 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function EffectSpawnMenu(props: { effect: string }) {
|
||||
const [explosionType, setExplosionType] = useState("High explosive");
|
||||
const [smokeColor, setSmokeColor] = useState("white");
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
// TODO
|
||||
//if (props.effect !== null) {
|
||||
// getApp()
|
||||
// ?.getMap()
|
||||
// ?.setState(SPAWN_EFFECT, {
|
||||
// effectRequestTable: {
|
||||
// type: props.effect,
|
||||
// }
|
||||
// });
|
||||
//} else {
|
||||
// if (getApp().getState() === SPAWN_EFFECT) getApp().setState(OlympusState.IDLE);
|
||||
//}
|
||||
|
||||
if (props.effect !== null) {
|
||||
if (props.effect === "explosion")
|
||||
getApp()?.getMap()?.setEffectRequestTable({
|
||||
type: props.effect,
|
||||
explosionType,
|
||||
});
|
||||
else if (props.effect === "smoke")
|
||||
getApp()?.getMap()?.setEffectRequestTable({
|
||||
type: props.effect,
|
||||
smokeColor,
|
||||
});
|
||||
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_EFFECT);
|
||||
} else {
|
||||
if (getApp().getState() === OlympusState.SPAWN && getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
<span className="text-white">Explosion type</span>
|
||||
|
||||
<OlDropdown label={explosionType} className="w-full">
|
||||
{["High explosive", "Napalm", "White phosphorous"].map((optionExplosionType) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={optionExplosionType}
|
||||
onClick={() => {
|
||||
setExplosionType(optionExplosionType);
|
||||
}}
|
||||
>
|
||||
{optionExplosionType}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
{props.effect === "explosion" && (
|
||||
<>
|
||||
<span className="text-white">Explosion type</span>
|
||||
<OlDropdown label={explosionType} className="w-full">
|
||||
{["High explosive", "Napalm", "White phosphorous"].map((optionExplosionType) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={optionExplosionType}
|
||||
onClick={() => {
|
||||
setExplosionType(optionExplosionType);
|
||||
}}
|
||||
>
|
||||
{optionExplosionType}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</>
|
||||
)}
|
||||
{props.effect === "smoke" && (
|
||||
<>
|
||||
<span className="text-white">Smoke color</span>
|
||||
<div className="flex w-full gap-2">
|
||||
{["white", "blue", "red", "green", "orange"].map((optionSmokeColor) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
checked={smokeColor === optionSmokeColor}
|
||||
icon={faSmog}
|
||||
onClick={() => {
|
||||
setSmokeColor(optionSmokeColor);
|
||||
}}
|
||||
tooltip=""
|
||||
borderColor={optionSmokeColor}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
296
frontend/react/src/ui/panels/gamemastermenu.tsx
Normal file
296
frontend/react/src/ui/panels/gamemastermenu.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { MapOptions } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { CommandModeOptions, ServerStatus } from "../../interfaces";
|
||||
import { CommandModeOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ERAS, GAME_MASTER, RED_COMMANDER } from "../../constants/constants";
|
||||
|
||||
export function GameMasterMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [currentSetupTime, setCurrentSetupTime] = useState(300);
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
|
||||
useEffect(() => {
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
setCurrentSetupTime(commandModeOptions.setupTime);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Menu title="Game Master options" open={props.open} showBackButton={false} onClose={props.onClose}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-2 p-5 font-normal text-gray-800
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
You are operating as:
|
||||
{commandModeOptions.commandMode === GAME_MASTER && (
|
||||
<div
|
||||
className={`
|
||||
w-full rounded-md bg-olympus-400 p-2 text-center font-bold
|
||||
`}
|
||||
>
|
||||
GAME MASTER
|
||||
</div>
|
||||
)}
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && <div className={`
|
||||
w-full rounded-md bg-blue-600 p-2 text-center font-bold
|
||||
`}>BLUE COMMANDER</div>}
|
||||
{commandModeOptions.commandMode === RED_COMMANDER && <div className={`
|
||||
w-full rounded-md bg-red-700 p-2 text-center font-bold
|
||||
`}>RED COMMANDER</div>}
|
||||
{serverStatus.elapsedTime > currentSetupTime && (
|
||||
<div
|
||||
className={`
|
||||
w-full rounded-md bg-orange-600 p-2 text-center font-bold
|
||||
`}
|
||||
>
|
||||
Setup time has ended
|
||||
</div>
|
||||
)}
|
||||
{serverStatus.elapsedTime <= currentSetupTime && (
|
||||
<div
|
||||
className={`
|
||||
w-full rounded-md bg-green-700 p-2 text-center font-bold
|
||||
`}
|
||||
>
|
||||
SETUP ends in {(currentSetupTime - serverStatus.elapsedTime)?.toFixed()} seconds
|
||||
</div>
|
||||
)}
|
||||
<span className="mt-5">Options: </span>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
gap-4 p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
if (commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.restrictSpawns = !commandModeOptions.restrictSpawns;
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox checked={commandModeOptions.restrictSpawns} onChange={() => {}} disabled={commandModeOptions.commandMode !== GAME_MASTER} />
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`data-[disabled='true']:text-gray-400`}
|
||||
>
|
||||
Restrict unit spanws
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
gap-4 p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.restrictToCoalition = !commandModeOptions.restrictToCoalition;
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={commandModeOptions.restrictToCoalition}
|
||||
onChange={() => {}}
|
||||
disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
/>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`data-[disabled='true']:text-gray-400`}
|
||||
>
|
||||
Restrict spawns to coalition
|
||||
</span>
|
||||
</div>
|
||||
{ERAS.sort((a, b) => (a.chronologicalOrder > b.chronologicalOrder ? 1 : -1)).map((era) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
gap-4 p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
if (commandModeOptions.eras.includes(era.name)) newCommandModeOptions.eras.splice(newCommandModeOptions.eras.indexOf(era.name));
|
||||
else newCommandModeOptions.eras.push(era.name);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={commandModeOptions.eras.includes(era.name)}
|
||||
onChange={() => {}}
|
||||
disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
/>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`data-[disabled='true']:text-gray-400`}
|
||||
>
|
||||
Allow {era.name} units
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4
|
||||
bg-blue-600/40 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`
|
||||
my-auto mr-auto
|
||||
data-[disabled='true']:text-gray-400
|
||||
`}
|
||||
>
|
||||
Blue spawn points
|
||||
</span>
|
||||
<OlNumberInput
|
||||
min={0}
|
||||
max={1e6}
|
||||
value={commandModeOptions.spawnPoints.blue}
|
||||
onChange={(e) => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.blue = parseInt(e.target.value);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onIncrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.blue = Math.min(newCommandModeOptions.spawnPoints.blue + 10, 1000000);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onDecrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.blue = Math.max(newCommandModeOptions.spawnPoints.blue - 10, 0);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
></OlNumberInput>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4 bg-red-600/40
|
||||
px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`
|
||||
my-auto mr-auto
|
||||
data-[disabled='true']:text-gray-400
|
||||
`}
|
||||
>
|
||||
Red spawn points
|
||||
</span>
|
||||
<OlNumberInput
|
||||
min={0}
|
||||
max={1e6}
|
||||
value={commandModeOptions.spawnPoints.red}
|
||||
onChange={(e) => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.red = parseInt(e.target.value);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onIncrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.red = Math.min(newCommandModeOptions.spawnPoints.red + 15, 6000);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onDecrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.red = Math.max(newCommandModeOptions.spawnPoints.red - 15, 0);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
></OlNumberInput>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`
|
||||
my-auto mr-auto
|
||||
data-[disabled='true']:text-gray-400
|
||||
`}
|
||||
>
|
||||
Setup time (seconds)
|
||||
</span>
|
||||
<OlNumberInput
|
||||
min={0}
|
||||
max={6000}
|
||||
value={commandModeOptions.setupTime}
|
||||
onChange={(e) => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.setupTime = parseInt(e.target.value);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onIncrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.setupTime = Math.min(newCommandModeOptions.setupTime + 10, 6000);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onDecrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.setupTime = Math.max(newCommandModeOptions.setupTime - 10, 0);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
></OlNumberInput>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span className="mr-auto">Elapsed time (seconds)</span>{" "}
|
||||
<span
|
||||
className={`w-32 text-center`}
|
||||
>
|
||||
{serverStatus.elapsedTime?.toFixed()}
|
||||
</span>
|
||||
</div>
|
||||
{commandModeOptions.commandMode === GAME_MASTER && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
getApp().getServerManager().setCommandModeOptions(commandModeOptions);
|
||||
}}
|
||||
className={`
|
||||
w-full rounded-lg bg-blue-700 px-5 py-2.5 text-md font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
|
||||
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP } from "../../olympusapp";
|
||||
@@ -17,9 +16,9 @@ import {
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { CommandModeOptions, OlympusConfig } from "../../interfaces";
|
||||
|
||||
export function Header() {
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
@@ -29,6 +28,7 @@ export function Header() {
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
const [audioEnabled, setAudioEnabled] = useState(false);
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
|
||||
useEffect(() => {
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
@@ -38,6 +38,9 @@ export function Header() {
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
});
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
@@ -111,6 +114,9 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && <div className={`
|
||||
flex h-full rounded-md bg-blue-600 px-4 text-white
|
||||
`}><span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span></div>}
|
||||
<div
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
|
||||
132
frontend/react/src/ui/panels/infobar.tsx
Normal file
132
frontend/react/src/ui/panels/infobar.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent } from "../../events";
|
||||
|
||||
export function InfoBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) onScroll(scrollRef.current);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollLeft;
|
||||
const sr = el.scrollWidth - el.scrollLeft - el.clientWidth;
|
||||
|
||||
sl < 1 && !scrolledLeft && setScrolledLeft(true);
|
||||
sl > 1 && scrolledLeft && setScrolledLeft(false);
|
||||
|
||||
sr < 1 && !scrolledRight && setScrolledRight(true);
|
||||
sr > 1 && scrolledRight && setScrolledRight(false);
|
||||
}
|
||||
|
||||
let reorderedActions: ContextAction[] = [];
|
||||
CONTEXT_ACTION_COLORS.forEach((color) => {
|
||||
if (contextActionSet) {
|
||||
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
|
||||
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-16 flex max-w-[80%]
|
||||
translate-x-[calc(-50%+2rem)] gap-2 rounded-md bg-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
{!scrolledLeft && (
|
||||
<FaChevronLeft
|
||||
className={`
|
||||
absolute left-0 h-full w-6 rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
borderColor={contextActionIt.getOptions().buttonColor}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!scrolledRight && (
|
||||
<FaChevronRight
|
||||
className={`
|
||||
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-32 flex min-w-[300px]
|
||||
translate-x-[calc(-50%+2rem)] items-center gap-2 rounded-md
|
||||
bg-gray-200 p-4
|
||||
dark:bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<FaInfoCircle
|
||||
className={`
|
||||
mr-2 hidden min-w-8 text-sm text-blue-500
|
||||
md:block
|
||||
`}
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
px-2
|
||||
dark:text-gray-400
|
||||
md:border-l-[1px] md:px-5
|
||||
`}
|
||||
>
|
||||
{contextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,18 +2,17 @@ import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import {
|
||||
faGamepad,
|
||||
faRuler,
|
||||
faPencil,
|
||||
faEllipsisV,
|
||||
faCog,
|
||||
faQuestionCircle,
|
||||
faPlusSquare,
|
||||
faMagnifyingGlass,
|
||||
faVolumeHigh,
|
||||
faJ,
|
||||
faCrown,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function SideBar() {
|
||||
@@ -80,6 +79,14 @@ export function SideBar() {
|
||||
icon={faJ}
|
||||
tooltip="Hide/show JTAC menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.GAME_MASTER ? OlympusState.GAME_MASTER : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.GAME_MASTER}
|
||||
icon={faCrown}
|
||||
tooltip="Hide/show Game Master menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
import { faExplosion, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import { EffectSpawnMenu } from "./effectspawnmenu";
|
||||
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, CommandModeOptionsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
@@ -40,6 +40,8 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [types, setTypes] = useState({ groundunit: [] as string[], navyunit: [] as string[] });
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [showCost, setShowCost] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
@@ -71,6 +73,19 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
.getTypes((unit) => unit.category === "navyunit"),
|
||||
});
|
||||
});
|
||||
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
if (subState === NO_SUBSTATE) {
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
}
|
||||
});
|
||||
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
setShowCost(!(commandModeOptions.commandMode == GAME_MASTER || !commandModeOptions.restrictSpawns));
|
||||
setOpenAccordion(CategoryAccordion.NONE);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
@@ -90,15 +105,6 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
if (subState === NO_SUBSTATE) {
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
{...props}
|
||||
@@ -153,8 +159,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityAircraft}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -196,8 +211,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityHelicopter}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -218,8 +242,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit" && blueprint.type === "SAM Site")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunitSam}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -240,8 +273,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.canAAA)
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunitSam}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -286,8 +328,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit" && blueprint.type !== "SAM Site")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -329,8 +380,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "navyunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityNavyunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityNavyunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -370,7 +430,13 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} spawnAtLocation={true} />}
|
||||
{!(blueprint === null) && (
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
spawnAtLocation={true}
|
||||
coalition={commandModeOptions.commandMode !== GAME_MASTER ? (commandModeOptions.commandMode === BLUE_COMMANDER ? "blue" : "red") : undefined}
|
||||
/>
|
||||
)}
|
||||
{!(effect === null) && <EffectSpawnMenu effect={effect} />}
|
||||
</>
|
||||
</Menu>
|
||||
|
||||
@@ -77,14 +77,7 @@ export function UnitControlBar(props: {}) {
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
className={
|
||||
contextActionIt.getOptions().buttonColor
|
||||
? `
|
||||
border-2
|
||||
border-${contextActionIt.getOptions().buttonColor}-500
|
||||
`
|
||||
: ""
|
||||
}
|
||||
borderColor={contextActionIt.getOptions().buttonColor}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlButtonGroup, OlButtonGroupItem } from "../components/olbuttongroup";
|
||||
import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { ROEs, altitudeIncrements, emissionsCountermeasures, maxAltitudeValues, minAltitudeValues, reactionsToThreat, speedIncrements } from "../../constants/constants";
|
||||
import { ROEs, altitudeIncrements, emissionsCountermeasures, maxAltitudeValues, maxSpeedValues, minAltitudeValues, reactionsToThreat, speedIncrements } from "../../constants/constants";
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import {
|
||||
@@ -207,22 +207,22 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const minSpeed = 0;
|
||||
|
||||
let maxAltitude = maxAltitudeValues.aircraft;
|
||||
let maxSpeed = minAltitudeValues.aircraft;
|
||||
let maxSpeed = maxSpeedValues.aircraft;
|
||||
let speedStep = speedIncrements.aircraft;
|
||||
let altitudeStep = altitudeIncrements.aircraft;
|
||||
|
||||
if (everyUnitIsHelicopter) {
|
||||
maxAltitude = maxAltitudeValues.helicopter;
|
||||
maxSpeed = minAltitudeValues.helicopter;
|
||||
maxSpeed = maxSpeedValues.helicopter;
|
||||
speedStep = speedIncrements.helicopter;
|
||||
altitudeStep = altitudeIncrements.helicopter;
|
||||
}
|
||||
else if (everyUnitIsGround) {
|
||||
maxSpeed = minAltitudeValues.groundunit;
|
||||
maxSpeed = maxSpeedValues.groundunit;
|
||||
speedStep = speedIncrements.groundunit;
|
||||
}
|
||||
else if (everyUnitIsNavy) {
|
||||
maxSpeed = minAltitudeValues.navyunit;
|
||||
maxSpeed = maxSpeedValues.navyunit;
|
||||
speedStep = speedIncrements.navyunit;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,38 +143,40 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
</div>
|
||||
{["aircraft", "helicopter"].includes(props.blueprint.category) && (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-row content-center items-center justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
{!props.airbase && (
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-row content-center items-center justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
</div>
|
||||
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
|
||||
</div>
|
||||
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
|
||||
value={spawnAltitude}
|
||||
min={minAltitude}
|
||||
max={maxAltitude}
|
||||
step={altitudeStep}
|
||||
/>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
|
||||
value={spawnAltitude}
|
||||
min={minAltitude}
|
||||
max={maxAltitude}
|
||||
step={altitudeStep}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="flex flex-row content-center justify-between">
|
||||
<span
|
||||
|
||||
@@ -35,6 +35,7 @@ import { ProtectionPrompt } from "./modals/protectionprompt";
|
||||
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
|
||||
import { GameMasterMenu } from "./panels/gamemastermenu";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@@ -57,8 +58,6 @@ export function UI() {
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
const [commandMode, setCommandMode] = useState(null as null | string);
|
||||
|
||||
const [airbase, setAirbase] = useState(null as null | Airbase);
|
||||
|
||||
const [formationLeader, setFormationLeader] = useState(null as null | Unit);
|
||||
const [formationWingmen, setFormationWingmen] = useState(null as null | Unit[]);
|
||||
|
||||
@@ -157,8 +156,9 @@ export function UI() {
|
||||
/>
|
||||
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} /* TODO remove */ />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)}/>
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<GameMasterMenu open={appState === OlympusState.GAME_MASTER} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
Reference in New Issue
Block a user