Added explosions, protection screen and delete unit

This commit is contained in:
Davide Passoni
2024-10-12 17:13:41 +02:00
parent 44bd054a3d
commit 4947997a0c
24 changed files with 1083 additions and 953 deletions

View 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>
);
}

View File

@@ -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>

View File

@@ -84,6 +84,7 @@ export function MapContextMenu(props: {}) {
getApp()
.getUnitsManager()
.getSelectedUnits()
.filter(unit => !unit.getHuman())
.forEach((unit: Unit) => {
unit.appendContextActions(newContextActionSet);
});

View 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>
);
}

View File

@@ -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>

View File

@@ -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
`}
/>

View 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>
);
}

View File

@@ -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={() => {

View File

@@ -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}

View File

@@ -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>
);

View File

@@ -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
`}

View 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>
);
}

View File

@@ -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 />