mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Added explosions, protection screen and delete unit
This commit is contained in:
28
frontend/react/src/ui/components/oleffectlistentry.tsx
Normal file
28
frontend/react/src/ui/components/oleffectlistentry.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
|
||||
|
||||
export function OlEffectListEntry(props: { icon: IconProp; label: string, onClick: () => void }) {
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
className={`
|
||||
group relative mr-2 flex cursor-pointer select-none items-center
|
||||
justify-between rounded-sm px-2 py-2 text-sm
|
||||
dark:text-gray-300 dark:hover:bg-olympus-500
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
|
||||
<div className="flex-1 px-2 text-left font-normal">{props.label}</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className={`
|
||||
px-1 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-1
|
||||
`}
|
||||
></FontAwesomeIcon>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,17 +2,16 @@ import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { faArrowRightLong, faCaretRight, faCircleArrowRight, faLongArrowAltRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
|
||||
|
||||
export function OlUnitEntryList(props: { icon: IconProp; blueprint: UnitBlueprint; onClick: () => void }) {
|
||||
export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprint; onClick: () => void }) {
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
className={`
|
||||
group relative mr-2 flex cursor-pointer select-none items-center
|
||||
justify-between rounded-sm px-2 py-2 text-sm
|
||||
dark:text-gray-300 dark:hover:bg-olympus-500 dark:hover:bg-white
|
||||
dark:text-gray-300 dark:hover:bg-olympus-500
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
|
||||
|
||||
@@ -84,6 +84,7 @@ export function MapContextMenu(props: {}) {
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.getSelectedUnits()
|
||||
.filter(unit => !unit.getHuman())
|
||||
.forEach((unit: Unit) => {
|
||||
unit.appendContextActions(newContextActionSet);
|
||||
});
|
||||
|
||||
90
frontend/react/src/ui/modals/protectionprompt.tsx
Normal file
90
frontend/react/src/ui/modals/protectionprompt.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { FaLock } from "react-icons/fa6";
|
||||
|
||||
export function ProtectionPrompt(props: {onContinue: (units: Unit[]) => void, onBack: () => void, units: Unit[] }) {
|
||||
return (
|
||||
<Modal
|
||||
className={`
|
||||
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-12">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Your selection contains protected units, are you sure you want to continue?
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
Pressing "Continue" will cause all DCS controlled units in the current selection to abort their mission and start following Olympus commands only.
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
If you are trying to delete a human player unit, they will be killed and de-slotted. Be careful!
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
To disable this warning, press on the <span className={`
|
||||
inline-block translate-y-3 rounded-full border-[1px]
|
||||
border-gray-900 bg-red-500 p-2 text-olympus-900
|
||||
`}><FaLock/></span> button
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {props.onContinue(props.units);}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm 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
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onBack}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { getUnitsByLabel } from "../../other/utils";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { OlUnitEntryList } from "../components/olunitlistentry";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
|
||||
@@ -103,7 +103,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAircraft).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -114,7 +114,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredHelicopters).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
|
||||
@@ -51,7 +51,7 @@ export function Menu(props: {
|
||||
onClick={props.onBack ?? (() => {})}
|
||||
icon={faArrowLeft}
|
||||
className={`
|
||||
mr-1 h-8 cursor-pointer rounded-md p-2
|
||||
mr-1 h-4 cursor-pointer rounded-md p-2
|
||||
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
|
||||
`}
|
||||
/>
|
||||
|
||||
45
frontend/react/src/ui/panels/effectspawnmenu.tsx
Normal file
45
frontend/react/src/ui/panels/effectspawnmenu.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { IDLE, SPAWN_EFFECT } from "../../constants/constants";
|
||||
|
||||
export function EffectSpawnMenu(props: { effect: string }) {
|
||||
const [explosionType, setExplosionType] = useState("High explosive");
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (props.effect !== null) {
|
||||
getApp()
|
||||
?.getMap()
|
||||
?.setState(SPAWN_EFFECT, {
|
||||
effectRequestTable: {
|
||||
type: props.effect,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (getApp().getMap().getState() === SPAWN_EFFECT) getApp().getMap().setState(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>
|
||||
);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export function Header() {
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
>
|
||||
<OlLockStateButton checked={false} onClick={() => {}} tooltip="Lock/unlock protected units (from scripted mission)" />
|
||||
<OlLockStateButton checked={!appState.mapOptions.protectDCSUnits} onClick={() => {getApp().getMap().setOption("protectDCSUnits", !appState.mapOptions.protectDCSUnits)}} tooltip="Lock/unlock protected units (from scripted mission)" />
|
||||
<OlRoundStateButton
|
||||
checked={audioEnabled}
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faMagnifyingGlass, faPlaneDeparture, faRadio, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faMagnifyingGlass, faRadio, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { EventsConsumer } from "../../eventscontext";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { IDLE } from "../../constants/constants";
|
||||
import { faSpeakerDeck } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
export function SideBar() {
|
||||
return (
|
||||
@@ -53,12 +52,6 @@ export function SideBar() {
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleAirbaseMenuVisible}
|
||||
checked={appState.airbaseMenuVisible}
|
||||
icon={faPlaneDeparture}
|
||||
tooltip="Hide/show airbase menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleRadioMenuVisible}
|
||||
checked={appState.radioMenuVisible}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Menu } from "./components/menu";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlUnitEntryList } from "../components/olunitlistentry";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import {
|
||||
@@ -13,11 +13,15 @@ import {
|
||||
olButtonsVisibilityHelicopter,
|
||||
olButtonsVisibilityNavyunit,
|
||||
} from "../components/olicons";
|
||||
import { IDLE, SPAWN_UNIT } from "../../constants/constants";
|
||||
import { IDLE, SPAWN_EFFECT, SPAWN_UNIT } from "../../constants/constants";
|
||||
import { getUnitsByLabel } from "../../other/utils";
|
||||
import { faExplosion, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import { EffectSpawnMenu } from "./effectspawnmenu";
|
||||
|
||||
export function SpawnMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
const [effect, setEffect] = useState(null as null | string);
|
||||
const [filterString, setFilterString] = useState("");
|
||||
|
||||
const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = getUnitsByLabel(filterString);
|
||||
@@ -25,7 +29,10 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
useEffect(() => {
|
||||
if (!props.open && getApp()) {
|
||||
if (getApp().getMap().getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
|
||||
else if (getApp().getMap().getState() === SPAWN_EFFECT) getApp().getMap().setState(IDLE);
|
||||
|
||||
if (blueprint !== null) setBlueprint(null);
|
||||
if (effect !== null) setEffect(null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -33,15 +40,16 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
<Menu
|
||||
{...props}
|
||||
title="Spawn menu"
|
||||
showBackButton={blueprint !== null}
|
||||
showBackButton={blueprint !== null || effect !== null}
|
||||
canBeHidden={true}
|
||||
onBack={() => {
|
||||
getApp().getMap().setState(IDLE);
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{blueprint === null && (
|
||||
{blueprint === null && effect === null && (
|
||||
<div className="p-5">
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion title={`Aircraft`}>
|
||||
@@ -51,7 +59,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAircraft).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -62,7 +70,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredHelicopters).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -73,7 +81,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAirDefense).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -84,7 +92,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredGroundUnits).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityGroundunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@@ -95,15 +103,39 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredNavyUnits).map((entry) => {
|
||||
return <OlUnitEntryList key={entry[0]} icon={olButtonsVisibilityNavyunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityNavyunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Effects (smokes, explosions etc)"></OlAccordion>
|
||||
<OlAccordion title="Effects (smokes, explosions etc)">
|
||||
<div
|
||||
className={`
|
||||
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
<OlEffectListEntry
|
||||
key={"explosion"}
|
||||
icon={faExplosion}
|
||||
label={"Explosion"}
|
||||
onClick={() => {
|
||||
setEffect("explosion");
|
||||
}}
|
||||
/>
|
||||
<OlEffectListEntry
|
||||
key={"smoke"}
|
||||
icon={faSmog}
|
||||
label={"Smoke"}
|
||||
onClick={() => {
|
||||
setEffect("smoke");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} spawnAtLocation={true} />}
|
||||
{!(effect === null) && <EffectSpawnMenu effect={effect} />}
|
||||
</>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -61,6 +61,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
ROE: undefined as undefined | string,
|
||||
reactionToThreat: undefined as undefined | string,
|
||||
emissionsCountermeasures: undefined as undefined | string,
|
||||
scenicAAA: undefined as undefined | boolean,
|
||||
missOnPurpose: undefined as undefined | boolean,
|
||||
shotsScatter: undefined as undefined | number,
|
||||
shotsIntensity: undefined as undefined | number,
|
||||
operateAs: undefined as undefined | Coalition,
|
||||
@@ -112,42 +114,6 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
if (!props.open && filterString !== "") setFilterString("");
|
||||
});
|
||||
|
||||
/* */
|
||||
const minAltitude = 0;
|
||||
const maxAltitude = getApp()
|
||||
?.getUnitsManager()
|
||||
?.getSelectedUnitsCategories()
|
||||
.every((category) => {
|
||||
return category === "Helicopter";
|
||||
})
|
||||
? 20000
|
||||
: 60000;
|
||||
const altitudeStep = getApp()
|
||||
?.getUnitsManager()
|
||||
?.getSelectedUnitsCategories()
|
||||
.every((category) => {
|
||||
return category === "Helicopter";
|
||||
})
|
||||
? 100
|
||||
: 500;
|
||||
const minSpeed = 0;
|
||||
const maxSpeed = getApp()
|
||||
?.getUnitsManager()
|
||||
?.getSelectedUnitsCategories()
|
||||
.every((category) => {
|
||||
return category === "Helicopter";
|
||||
})
|
||||
? 200
|
||||
: 800;
|
||||
const speedStep = getApp()
|
||||
?.getUnitsManager()
|
||||
?.getSelectedUnitsCategories()
|
||||
.every((category) => {
|
||||
return category === "Helicopter";
|
||||
})
|
||||
? 5
|
||||
: 10;
|
||||
|
||||
useEffect(() => {
|
||||
/* When a unit is selected, update the data */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
@@ -190,6 +156,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
emissionsCountermeasures: (unit: Unit) => {
|
||||
return unit.getEmissionsCountermeasures();
|
||||
},
|
||||
scenicAAA: (unit: Unit) => {
|
||||
return unit.getState() === "scenic-aaa";
|
||||
},
|
||||
missOnPurpose: (unit: Unit) => {
|
||||
return unit.getState() === "miss-on-purpose";
|
||||
},
|
||||
shotsScatter: (unit: Unit) => {
|
||||
return unit.getShotsScatter();
|
||||
},
|
||||
@@ -213,8 +185,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
},
|
||||
isAudioSink: (unit: Unit) => {
|
||||
return (
|
||||
getApp()?.getAudioManager().getSinks().filter((sink) => {
|
||||
return sink instanceof UnitSink}).length > 0 &&
|
||||
getApp()
|
||||
?.getAudioManager()
|
||||
.getSinks()
|
||||
.filter((sink) => {
|
||||
return sink instanceof UnitSink;
|
||||
}).length > 0 &&
|
||||
getApp()
|
||||
?.getAudioManager()
|
||||
.getSinks()
|
||||
@@ -257,6 +233,37 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
[key: string]: UnitBlueprint;
|
||||
};
|
||||
|
||||
const everyUnitIsGround = selectedCategories.every((category) => {
|
||||
return category === "GroundUnit";
|
||||
});
|
||||
const everyUnitIsNavy = selectedCategories.every((category) => {
|
||||
return category === "NavyUnit";
|
||||
});
|
||||
const everyUnitIsHelicopter = selectedCategories.every((category) => {
|
||||
return category === "Helicopter";
|
||||
});
|
||||
|
||||
const minAltitude = 0;
|
||||
const minSpeed = 0;
|
||||
|
||||
let maxAltitude = 60000;
|
||||
let maxSpeed = 800;
|
||||
|
||||
let altitudeStep = 500;
|
||||
let speedStep = 10;
|
||||
|
||||
if (everyUnitIsHelicopter) {
|
||||
maxAltitude = 20000;
|
||||
maxSpeed = 200;
|
||||
speedStep = 5;
|
||||
altitudeStep = 100;
|
||||
}
|
||||
|
||||
if (everyUnitIsGround || everyUnitIsNavy) {
|
||||
maxSpeed = 60;
|
||||
speedStep = 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
@@ -618,20 +625,22 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{selectedUnitsData.desiredSpeed !== undefined ? selectedUnitsData.desiredSpeed + " KTS" : "Different values"}
|
||||
</span>
|
||||
</div>
|
||||
<OlLabelToggle
|
||||
toggled={selectedUnitsData.desiredSpeedType === undefined ? undefined : selectedUnitsData.desiredSpeedType === "GS"}
|
||||
leftLabel={"GS"}
|
||||
rightLabel={"CAS"}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setSpeedType(selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
desiredSpeedType: selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS",
|
||||
{!(everyUnitIsGround || everyUnitIsNavy) && (
|
||||
<OlLabelToggle
|
||||
toggled={selectedUnitsData.desiredSpeedType === undefined ? undefined : selectedUnitsData.desiredSpeedType === "GS"}
|
||||
leftLabel={"GS"}
|
||||
rightLabel={"CAS"}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setSpeedType(selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
desiredSpeedType: selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS",
|
||||
});
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
@@ -853,94 +862,152 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
return ["GroundUnit", "NavyUnit"].includes(category);
|
||||
}) && (
|
||||
<>
|
||||
{/* ============== Shots scatter START ============== */}
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Shots scatter
|
||||
</span>
|
||||
<OlButtonGroup>
|
||||
{[olButtonsScatter1, olButtonsScatter2, olButtonsScatter3].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsScatter(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
shotsScatter: idx + 1,
|
||||
});
|
||||
});
|
||||
}}
|
||||
active={selectedUnitsData.shotsScatter === idx + 1}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
{/* ============== Shots scatter END ============== */}
|
||||
{/* ============== Shots intensity START ============== */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Shots intensity
|
||||
</span>
|
||||
<OlButtonGroup>
|
||||
{[olButtonsIntensity1, olButtonsIntensity2, olButtonsIntensity3].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsIntensity(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
shotsIntensity: idx + 1,
|
||||
});
|
||||
});
|
||||
}}
|
||||
active={selectedUnitsData.shotsIntensity === idx + 1}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
{/* ============== Shots intensity END ============== */}
|
||||
{/* ============== Operate as toggle START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Operate as
|
||||
</span>
|
||||
<OlCoalitionToggle
|
||||
coalition={selectedUnitsData.operateAs as Coalition}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setOperateAs(selectedUnitsData.operateAs === "blue" ? "red" : "blue");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
operateAs: selectedUnitsData.operateAs === "blue" ? "red" : "blue",
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-4 rounded-md bg-olympus-200/30 p-4
|
||||
`}
|
||||
>
|
||||
{/* ============== Scenic AAA toggle START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Scenic AAA mode
|
||||
</span>
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.scenicAAA}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.scenicAAA ? unit.changeSpeed("stop") : unit.scenicAAA();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
scenicAAA: !selectedUnitsData.scenicAAA,
|
||||
missOnPurpose: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* ============== Scenic AAA toggle END ============== */}
|
||||
{/* ============== Miss on purpose toggle START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Miss on purpose mode
|
||||
</span>
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.missOnPurpose}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.missOnPurpose ? unit.changeSpeed("stop") : unit.missOnPurpose();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
scenicAAA: false,
|
||||
missOnPurpose: !selectedUnitsData.missOnPurpose,
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* ============== Miss on purpose toggle END ============== */}
|
||||
<div className="flex gap-4">
|
||||
{/* ============== Shots scatter START ============== */}
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Shots scatter
|
||||
</span>
|
||||
<OlButtonGroup>
|
||||
{[olButtonsScatter1, olButtonsScatter2, olButtonsScatter3].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsScatter(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
shotsScatter: idx + 1,
|
||||
});
|
||||
});
|
||||
}}
|
||||
active={selectedUnitsData.shotsScatter === idx + 1}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
{/* ============== Shots scatter END ============== */}
|
||||
{/* ============== Shots intensity START ============== */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Shots intensity
|
||||
</span>
|
||||
<OlButtonGroup>
|
||||
{[olButtonsIntensity1, olButtonsIntensity2, olButtonsIntensity3].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsIntensity(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
shotsIntensity: idx + 1,
|
||||
});
|
||||
});
|
||||
}}
|
||||
active={selectedUnitsData.shotsIntensity === idx + 1}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
{/* ============== Shots intensity END ============== */}
|
||||
</div>
|
||||
{/* ============== Operate as toggle START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Operate as
|
||||
</span>
|
||||
<OlCoalitionToggle
|
||||
coalition={selectedUnitsData.operateAs as Coalition}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setOperateAs(selectedUnitsData.operateAs === "blue" ? "red" : "blue");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
operateAs: selectedUnitsData.operateAs === "blue" ? "red" : "blue",
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* ============== Operate as toggle END ============== */}
|
||||
</div>
|
||||
{/* ============== Operate as toggle END ============== */}
|
||||
{/* ============== Follow roads toggle START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
<span
|
||||
@@ -1129,9 +1196,10 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
value={activeAdvancedSettings ? activeAdvancedSettings.TACAN.channel : 1}
|
||||
></OlNumberInput>
|
||||
|
||||
<OlDropdown label={activeAdvancedSettings ? activeAdvancedSettings.TACAN.XY : "X"} className={`
|
||||
my-auto w-20
|
||||
`}>
|
||||
<OlDropdown
|
||||
label={activeAdvancedSettings ? activeAdvancedSettings.TACAN.XY : "X"}
|
||||
className={`my-auto w-20`}
|
||||
>
|
||||
<OlDropdownItem
|
||||
key={"X"}
|
||||
onClick={() => {
|
||||
@@ -1250,9 +1318,11 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
className={`
|
||||
flex content-center gap-2 rounded-full
|
||||
${selectedUnits[0].getFuel() > 40 && `bg-green-700`}
|
||||
${selectedUnits[0].getFuel() > 10 && selectedUnits[0].getFuel() <= 40 && `
|
||||
bg-yellow-700
|
||||
`}
|
||||
${
|
||||
selectedUnits[0].getFuel() > 10 &&
|
||||
selectedUnits[0].getFuel() <= 40 &&
|
||||
`bg-yellow-700`
|
||||
}
|
||||
${selectedUnits[0].getFuel() <= 10 && `bg-red-700`}
|
||||
px-2 py-1 text-sm font-bold text-white
|
||||
`}
|
||||
|
||||
56
frontend/react/src/ui/panels/unitexplosionmenu.tsx
Normal file
56
frontend/react/src/ui/panels/unitexplosionmenu.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, { useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { getApp } from "../../olympusapp";
|
||||
|
||||
export function UnitExplosionMenu(props: { open: boolean; onClose: () => void; units: Unit[] | null; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [explosionType, setExplosionType] = useState("High explosive");
|
||||
|
||||
return (
|
||||
<Menu title="Unit explosion menu" open={props.open} showBackButton={false} onClose={props.onClose}>
|
||||
<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>
|
||||
{props.units !== null && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (explosionType === "High explosive") {
|
||||
getApp()?.getUnitsManager().delete(true, "normal", props.units);
|
||||
} else if (explosionType === "Napalm") {
|
||||
getApp()?.getUnitsManager().delete(true, "napalm", props.units);
|
||||
} else if (explosionType === "White phosphorous") {
|
||||
getApp()?.getUnitsManager().delete(true, "phosphorous", props.units);
|
||||
}
|
||||
props.onClose();
|
||||
}}
|
||||
className={`
|
||||
mb-2 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>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import { RadioMenu } from "./panels/radiomenu";
|
||||
import { AudioMenu } from "./panels/audiomenu";
|
||||
import { FormationMenu } from "./panels/formationmenu";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { ProtectionPrompt } from "./modals/protectionprompt";
|
||||
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@@ -51,6 +53,7 @@ export function UI() {
|
||||
const [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
|
||||
const [airbaseMenuVisible, setAirbaseMenuVisible] = useState(false);
|
||||
const [formationMenuVisible, setFormationMenuVisible] = useState(false);
|
||||
const [unitExplosionMenuVisible, setUnitExplosionMenuVisible] = useState(false);
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [checkingPassword, setCheckingPassword] = useState(false);
|
||||
@@ -62,6 +65,10 @@ export function UI() {
|
||||
const [airbase, setAirbase] = useState(null as null | Airbase);
|
||||
const [formationLeader, setFormationLeader] = useState(null as null | Unit);
|
||||
const [formationWingmen, setFormationWingmen] = useState(null as null | Unit[]);
|
||||
const [protectionPromptVisible, setProtectionPromptVisible] = useState(false);
|
||||
const [protectionCallback, setProtectionCallback] = useState(null as any);
|
||||
const [protectionUnits, setProtectionUnits] = useState([] as Unit[]);
|
||||
const [unitExplosionUnits, setUnitExplosionUnits] = useState([] as Unit[]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("hiddenTypesChanged", (ev) => {
|
||||
@@ -73,11 +80,15 @@ export function UI() {
|
||||
});
|
||||
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
if ((ev as CustomEvent).detail === IDLE) hideAllMenus();
|
||||
else if ((ev as CustomEvent).detail === CONTEXT_ACTION && window.innerWidth > 1000) setUnitControlMenuVisible(true);
|
||||
//if ((ev as CustomEvent).detail === IDLE) hideAllMenus();
|
||||
/*else*/ if ((ev as CustomEvent).detail === CONTEXT_ACTION && window.innerWidth > 1000) setUnitControlMenuVisible(true);
|
||||
setMapState(String((ev as CustomEvent).detail));
|
||||
});
|
||||
|
||||
document.addEventListener("hideAllMenus", (ev) => {
|
||||
hideAllMenus();
|
||||
});
|
||||
|
||||
document.addEventListener("mapSourceChanged", (ev) => {
|
||||
var source = (ev as CustomEvent).detail;
|
||||
setActiveMapSource(source);
|
||||
@@ -90,18 +101,29 @@ export function UI() {
|
||||
setActiveMapSource(sources[0]);
|
||||
});
|
||||
|
||||
document.addEventListener("airbaseclick", (ev) => {
|
||||
document.addEventListener("airbaseClick", (ev) => {
|
||||
hideAllMenus();
|
||||
getApp().getMap().setState(IDLE);
|
||||
setAirbase((ev as CustomEvent).detail);
|
||||
setAirbaseMenuVisible(true);
|
||||
});
|
||||
|
||||
document.addEventListener("createFormation", (ev) => {
|
||||
document.addEventListener("showFormationMenu", (ev) => {
|
||||
setFormationMenuVisible(true);
|
||||
setFormationLeader((ev as CustomEvent).detail.leader);
|
||||
setFormationWingmen((ev as CustomEvent).detail.wingmen);
|
||||
});
|
||||
|
||||
document.addEventListener("showProtectionPrompt", (ev: CustomEventInit) => {
|
||||
setProtectionPromptVisible(true);
|
||||
setProtectionCallback(() => {return ev.detail.callback});
|
||||
setProtectionUnits(ev.detail.units);
|
||||
});
|
||||
|
||||
document.addEventListener("showUnitExplosionMenu", (ev) => {
|
||||
setUnitExplosionMenuVisible(true);
|
||||
setUnitExplosionUnits((ev as CustomEvent).detail.units);
|
||||
})
|
||||
}, []);
|
||||
|
||||
function hideAllMenus() {
|
||||
@@ -115,6 +137,7 @@ export function UI() {
|
||||
setRadioMenuVisible(false);
|
||||
setAudioMenuVisible(false);
|
||||
setFormationMenuVisible(false);
|
||||
setUnitExplosionMenuVisible(false);
|
||||
}
|
||||
|
||||
function checkPassword(password: string) {
|
||||
@@ -246,16 +269,36 @@ export function UI() {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={mainMenuVisible} onClose={() => setMainMenuVisible(false)} />
|
||||
<SpawnMenu open={spawnMenuVisible} onClose={() => setSpawnMenuVisible(false)} />
|
||||
<OptionsMenu open={optionsMenuVisible} onClose={() => setOptionsMenuVisible(false)} options={mapOptions} />
|
||||
<UnitControlMenu open={unitControlMenuVisible} onClose={() => setUnitControlMenuVisible(false)} />
|
||||
<DrawingMenu open={drawingMenuVisible} onClose={() => setDrawingMenuVisible(false)} />
|
||||
<AirbaseMenu open={airbaseMenuVisible} onClose={() => setAirbaseMenuVisible(false)} airbase={airbase}/>
|
||||
<AirbaseMenu open={airbaseMenuVisible} onClose={() => setAirbaseMenuVisible(false)} airbase={airbase} />
|
||||
<RadioMenu open={radioMenuVisible} onClose={() => setRadioMenuVisible(false)} />
|
||||
<AudioMenu open={audioMenuVisible} onClose={() => setAudioMenuVisible(false)} />
|
||||
<FormationMenu open={formationMenuVisible} leader={formationLeader} wingmen={formationWingmen} onClose={() => setFormationMenuVisible(false)} />
|
||||
<UnitExplosionMenu open={unitExplosionMenuVisible} units={unitExplosionUnits} onClose={() => setUnitExplosionMenuVisible(false)} />
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
|
||||
Reference in New Issue
Block a user