mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Added starred spawns
This commit is contained in:
@@ -271,6 +271,7 @@ export enum OlympusState {
|
||||
MAIN_MENU = "Main menu",
|
||||
UNIT_CONTROL = "Unit control",
|
||||
SPAWN = "Spawn",
|
||||
STARRED_SPAWN = "Starred spawn",
|
||||
DRAW = "Draw",
|
||||
JTAC = "JTAC",
|
||||
OPTIONS = "Options",
|
||||
@@ -307,7 +308,7 @@ export enum JTACSubState {
|
||||
export enum SpawnSubState {
|
||||
NO_SUBSTATE = "No substate",
|
||||
SPAWN_UNIT = "Unit",
|
||||
SPAWN_EFFECT = "Effect",
|
||||
SPAWN_EFFECT = "Effect"
|
||||
}
|
||||
|
||||
export enum OptionsSubstate {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
import { AudioSource } from "./audio/audiosource";
|
||||
import { OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { CommandModeOptions, OlympusConfig, ServerStatus } from "./interfaces";
|
||||
import { CommandModeOptions, OlympusConfig, ServerStatus, SpawnRequestTable } from "./interfaces";
|
||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||
import { Airbase } from "./mission/airbase";
|
||||
@@ -330,6 +330,19 @@ export class UnitContextMenuRequestEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class StarredSpawnContextMenuRequestEvent {
|
||||
static on(callback: (latlng: L.LatLng) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.latlng);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(latlng: L.LatLng) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {latlng}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class HotgroupsChangedEvent {
|
||||
static on(callback: (hotgroups: {[key: number]: number}) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
@@ -343,6 +356,19 @@ export class HotgroupsChangedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class StarredSpawnsChangedEvent {
|
||||
static on(callback: (starredSpawns: {[key: number]: SpawnRequestTable}) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.starredSpawns);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(starredSpawns: {[key: number]: SpawnRequestTable}) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {starredSpawns}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
/************** Command mode events ***************/
|
||||
export class CommandModeOptionsChangedEvent {
|
||||
static on(callback: (options: CommandModeOptions) => void) {
|
||||
|
||||
@@ -99,6 +99,7 @@ export interface SpawnRequestTable {
|
||||
category: string;
|
||||
coalition: string;
|
||||
unit: UnitSpawnTable;
|
||||
quickAccessName?: string
|
||||
}
|
||||
|
||||
export interface EffectRequestTable {
|
||||
|
||||
@@ -52,6 +52,8 @@ import {
|
||||
MapOptionsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
SelectionClearedEvent,
|
||||
StarredSpawnContextMenuRequestEvent,
|
||||
StarredSpawnsChangedEvent,
|
||||
UnitDeselectedEvent,
|
||||
UnitSelectedEvent,
|
||||
UnitUpdatedEvent,
|
||||
@@ -132,6 +134,7 @@ export class Map extends L.Map {
|
||||
|
||||
/* Unit spawning */
|
||||
#spawnRequestTable: SpawnRequestTable | null = null;
|
||||
#starredSpawnRequestTables: { [key: string]: SpawnRequestTable } = {};
|
||||
#effectRequestTable: EffectRequestTable | null = null;
|
||||
#temporaryMarkers: TemporaryUnitMarker[] = [];
|
||||
#currentSpawnMarker: TemporaryUnitMarker | null = null;
|
||||
@@ -369,31 +372,32 @@ export class Map extends L.Map {
|
||||
.getShortcutManager()
|
||||
.addShortcut(`panUp`, {
|
||||
label: "Pan map up",
|
||||
keyUpCallback: (ev: KeyboardEvent) => this.#panUp = false,
|
||||
keyDownCallback: (ev: KeyboardEvent) => this.#panUp = true,
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panUp = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panUp = true),
|
||||
code: "KeyW",
|
||||
})
|
||||
.addShortcut(`panDown`, {
|
||||
label: "Pan map down",
|
||||
keyUpCallback: (ev: KeyboardEvent) => this.#panDown = false,
|
||||
keyDownCallback: (ev: KeyboardEvent) => this.#panDown = true,
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panDown = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panDown = true),
|
||||
code: "KeyS",
|
||||
})
|
||||
.addShortcut(`panLeft`, {
|
||||
label: "Pan map left",
|
||||
keyUpCallback: (ev: KeyboardEvent) => this.#panLeft = false,
|
||||
keyDownCallback: (ev: KeyboardEvent) => this.#panLeft = true,
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panLeft = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panLeft = true),
|
||||
code: "KeyA",
|
||||
})
|
||||
.addShortcut(`panRight`, {
|
||||
label: "Pan map right",
|
||||
keyUpCallback: (ev: KeyboardEvent) => this.#panRight = false,
|
||||
keyDownCallback: (ev: KeyboardEvent) => this.#panRight = true,
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panRight = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panRight = true),
|
||||
code: "KeyD",
|
||||
}).addShortcut(`panFast`, {
|
||||
})
|
||||
.addShortcut(`panFast`, {
|
||||
label: "Pan map fast",
|
||||
keyUpCallback: (ev: KeyboardEvent) => this.#panFast = false,
|
||||
keyDownCallback: (ev: KeyboardEvent) => this.#panFast = true,
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panFast = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panFast = true),
|
||||
code: "ShiftLeft",
|
||||
});
|
||||
|
||||
@@ -481,6 +485,16 @@ export class Map extends L.Map {
|
||||
this.#spawnRequestTable = spawnRequestTable;
|
||||
}
|
||||
|
||||
addStarredSpawnRequestTable(key, spawnRequestTable: SpawnRequestTable) {
|
||||
this.#starredSpawnRequestTables[key] = spawnRequestTable;
|
||||
StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
|
||||
}
|
||||
|
||||
removeStarredSpawnRequestTable(key) {
|
||||
if (key in this.#starredSpawnRequestTables) delete this.#starredSpawnRequestTables[key];
|
||||
StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
|
||||
}
|
||||
|
||||
setEffectRequestTable(effectRequestTable: EffectRequestTable) {
|
||||
this.#effectRequestTable = effectRequestTable;
|
||||
if (getApp().getState() === OlympusState.SPAWN && getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
||||
@@ -1005,10 +1019,15 @@ export class Map extends L.Map {
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
if (getApp().getSubState() !== NO_SUBSTATE) {
|
||||
getApp().setState(getApp().getState(), NO_SUBSTATE);
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
StarredSpawnContextMenuRequestEvent.dispatch(e.latlng);
|
||||
getApp().setState(OlympusState.STARRED_SPAWN);
|
||||
} else {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
if (getApp().getSubState() !== NO_SUBSTATE) {
|
||||
getApp().setState(getApp().getState(), NO_SUBSTATE);
|
||||
} else {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,13 +197,11 @@ export class OlympusApp {
|
||||
}
|
||||
|
||||
setState(state: OlympusState, subState: OlympusSubState = NO_SUBSTATE) {
|
||||
if (state !== this.#state || subState !== this.#subState) {
|
||||
this.#state = state;
|
||||
this.#subState = subState;
|
||||
this.#state = state;
|
||||
this.#subState = subState;
|
||||
|
||||
console.log(`App state set to ${state}, substate ${subState}`);
|
||||
AppStateChangedEvent.dispatch(state, subState);
|
||||
}
|
||||
console.log(`App state set to ${state}, substate ${subState}`);
|
||||
AppStateChangedEvent.dispatch(state, subState);
|
||||
}
|
||||
|
||||
getState() {
|
||||
|
||||
@@ -315,6 +315,21 @@ export function makeID(length) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function hash(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||
for(let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return `${4294967296 * (2097151 & h2) + (h1 >>> 0)}`;
|
||||
};
|
||||
|
||||
export function byteArrayToInteger(array) {
|
||||
let res = 0;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
|
||||
@@ -8,7 +8,7 @@ export function OlStringInput(props: { value: string; className?: string; onChan
|
||||
min-w-32
|
||||
`}
|
||||
>
|
||||
<div className="relative flex max-w-[8rem] items-center">
|
||||
<div className="relative flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
onChange={props.onChange}
|
||||
|
||||
@@ -70,8 +70,8 @@ export function OlTooltip(props: { content: string; buttonRef: React.MutableRefO
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute whitespace-nowrap rounded-lg bg-gray-900 px-3 py-2 text-sm
|
||||
font-medium text-white shadow-sm
|
||||
absolute z-50 whitespace-nowrap rounded-lg bg-gray-900 px-3 py-2
|
||||
text-sm font-medium text-white shadow-sm
|
||||
dark:bg-gray-700
|
||||
`}
|
||||
>
|
||||
|
||||
108
frontend/react/src/ui/contextmenus/starredspawncontextmenu.tsx
Normal file
108
frontend/react/src/ui/contextmenus/starredspawncontextmenu.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { OlDropdownItem } from "../components/oldropdown";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { LatLng } from "leaflet";
|
||||
import { AppStateChangedEvent, StarredSpawnContextMenuRequestEvent, StarredSpawnsChangedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { SpawnRequestTable } from "../../interfaces";
|
||||
import { faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function StarredSpawnContextMenu(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [latlng, setLatLng] = useState(null as null | LatLng);
|
||||
const [starredSpawns, setStarredSpawns] = useState({} as { [key: string]: SpawnRequestTable });
|
||||
|
||||
var contentRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => setStarredSpawns({ ...starredSpawns }));
|
||||
StarredSpawnContextMenuRequestEvent.on((latlng) => {
|
||||
setLatLng(latlng);
|
||||
const containerPoint = getApp().getMap().latLngToContainerPoint(latlng);
|
||||
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
|
||||
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
|
||||
content.style.left = `${xPosition}px`;
|
||||
content.style.top = `${yPosition}px`;
|
||||
|
||||
let newXPosition = xPosition;
|
||||
let newYposition = yPosition;
|
||||
|
||||
let [cxr, cyb] = [content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight];
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxr > window.innerWidth) newXPosition -= cxr - window.innerWidth;
|
||||
if (cyb > window.innerHeight) newYposition -= cyb - window.innerHeight;
|
||||
|
||||
content.style.left = `${newXPosition}px`;
|
||||
content.style.top = `${newYposition}px`;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{appState === OlympusState.STARRED_SPAWN && (
|
||||
<>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute flex min-w-80 max-w-80 gap-2 rounded-md bg-olympus-600
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex w-full flex-col gap-2 overflow-y-auto no-scrollbar p-2
|
||||
`}
|
||||
>
|
||||
{Object.values(starredSpawns).length > 0? Object.values(starredSpawns).map((spawnRequestTable) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
className={`
|
||||
flex w-full content-center gap-2 text-sm text-white
|
||||
`}
|
||||
onClick={() => {
|
||||
if (latlng) {
|
||||
spawnRequestTable.unit.location = latlng;
|
||||
getApp().getUnitsManager().spawnUnits(spawnRequestTable.category, [spawnRequestTable.unit], spawnRequestTable.coalition, false);
|
||||
getApp().setState(OlympusState.IDLE)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
data-coalition={spawnRequestTable.coalition}
|
||||
className={`
|
||||
my-auto
|
||||
data-[coalition='blue']:text-blue-500
|
||||
data-[coalition='neutral']:text-gay-500
|
||||
data-[coalition='red']:text-red-500
|
||||
`}
|
||||
icon={faStar}
|
||||
/>
|
||||
<div>
|
||||
{getApp().getUnitsManager().getDatabase().getByName(spawnRequestTable.unit.unitType)?.label} ({spawnRequestTable.quickAccessName})
|
||||
</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
}):
|
||||
<div className="p-2 text-sm text-white">No starred spawns, use the spawn menu to create a quick access spawn</div>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -34,9 +34,9 @@ export function KeybindModal(props: { open: boolean }) {
|
||||
for (let id in shortcuts) {
|
||||
if (
|
||||
code === shortcuts[id].getOptions().code &&
|
||||
shiftKey == shortcuts[id].getOptions().shiftKey &&
|
||||
altKey == shortcuts[id].getOptions().altKey &&
|
||||
ctrlKey == shortcuts[id].getOptions().shiftKey
|
||||
shiftKey === (shortcuts[id].getOptions().shiftKey ?? false) &&
|
||||
altKey === (shortcuts[id].getOptions().altKey ?? false) &&
|
||||
ctrlKey === (shortcuts[id].getOptions().shiftKey ?? false)
|
||||
) {
|
||||
available = false;
|
||||
inUseShortcut = shortcuts[id];
|
||||
@@ -101,12 +101,12 @@ export function KeybindModal(props: { open: boolean }) {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (shortcut && code) {
|
||||
let options = shortcut.getOptions()
|
||||
let options = shortcut.getOptions();
|
||||
options.code = code;
|
||||
options.altKey = altKey;
|
||||
options.shiftKey = shiftKey;
|
||||
options.ctrlKey = ctrlKey;
|
||||
getApp().getShortcutManager().setShortcutOption(shortcut.getId(), options)
|
||||
getApp().getShortcutManager().setShortcutOption(shortcut.getId(), options);
|
||||
|
||||
getApp().setState(OlympusState.OPTIONS);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Menu } from "./components/menu";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
import { FaArrowLeft, FaCompass } from "react-icons/fa6";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { SpawnRequestTable, UnitBlueprint } from "../../interfaces";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
import { AirbaseSelectedEvent, CommandModeOptionsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { AirbaseSelectedEvent, CommandModeOptionsChangedEvent, StarredSpawnsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, RED_COMMANDER } from "../../constants/constants";
|
||||
|
||||
@@ -30,6 +30,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE);
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [showCost, setShowCost] = useState(false);
|
||||
const [starredSpawns, setStarredSpawns] = useState({} as { [key: string]: SpawnRequestTable });
|
||||
|
||||
useEffect(() => {
|
||||
AirbaseSelectedEvent.on((airbase) => {
|
||||
@@ -54,6 +55,8 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
setShowCost(!(commandModeOptions.commandMode === GAME_MASTER || !commandModeOptions.restrictSpawns));
|
||||
setOpenAccordion(CategoryAccordion.NONE);
|
||||
});
|
||||
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => setStarredSpawns({ ...starredSpawns }));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -114,10 +117,9 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<>
|
||||
{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}
|
||||
@@ -267,7 +269,13 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
)}
|
||||
<>
|
||||
{!(blueprint === null) && (
|
||||
<UnitSpawnMenu blueprint={blueprint} spawnAtLocation={false} airbase={airbase} coalition={(airbase?.getCoalition() ?? "blue") as Coalition} />
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
starredSpawns={starredSpawns}
|
||||
spawnAtLocation={false}
|
||||
airbase={airbase}
|
||||
coalition={(airbase?.getCoalition() ?? "blue") as Coalition}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
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";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
|
||||
export function Header() {
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import {
|
||||
faGamepad,
|
||||
faPencil,
|
||||
faEllipsisV,
|
||||
faCog,
|
||||
faQuestionCircle,
|
||||
faPlusSquare,
|
||||
faVolumeHigh,
|
||||
faJ,
|
||||
faCrown,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faGamepad, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faVolumeHigh, faJ, faCrown } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function SideBar() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -43,7 +37,7 @@ export function SideBar() {
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.SPAWN ? OlympusState.SPAWN : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.SPAWN}
|
||||
checked={appState === OlympusState.SPAWN && appSubState === SpawnSubState.NO_SUBSTATE}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
></OlStateButton>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { OlAccordion } from "../components/olaccordion";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { SpawnRequestTable, UnitBlueprint } from "../../interfaces";
|
||||
import {
|
||||
olButtonsVisibilityAircraft,
|
||||
olButtonsVisibilityGroundunit,
|
||||
@@ -17,7 +17,7 @@ import { faExplosion, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import { EffectSpawnMenu } from "./effectspawnmenu";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, CommandModeOptionsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { AppStateChangedEvent, CommandModeOptionsChangedEvent, StarredSpawnsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
@@ -42,6 +42,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
const [types, setTypes] = useState({ groundunit: [] as string[], navyunit: [] as string[] });
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [showCost, setShowCost] = useState(false);
|
||||
const [starredSpawns, setStarredSpawns] = useState({} as { [key: string]: SpawnRequestTable });
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
@@ -86,6 +87,8 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
setShowCost(!(commandModeOptions.commandMode == GAME_MASTER || !commandModeOptions.restrictSpawns));
|
||||
setOpenAccordion(CategoryAccordion.NONE);
|
||||
});
|
||||
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => setStarredSpawns({ ...starredSpawns }));
|
||||
}, []);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
@@ -434,6 +437,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
spawnAtLocation={true}
|
||||
starredSpawns={starredSpawns}
|
||||
coalition={commandModeOptions.commandMode !== GAME_MASTER ? (commandModeOptions.commandMode === BLUE_COMMANDER ? "blue" : "red") : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { OlUnitSummary } from "../components/olunitsummary";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
|
||||
import { LoadoutBlueprint, SpawnRequestTable, UnitBlueprint } from "../../interfaces";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ftToM } from "../../other/utils";
|
||||
import { ftToM, hash } from "../../other/utils";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
import { altitudeIncrements, groupUnitCount, maxAltitudeValues, minAltitudeValues, OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
|
||||
export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation: boolean; airbase?: Airbase | null; coalition?: Coalition }) {
|
||||
export function UnitSpawnMenu(props: { starredSpawns: { [key: string]: SpawnRequestTable }, blueprint: UnitBlueprint; spawnAtLocation: boolean; airbase?: Airbase | null; coalition?: Coalition }) {
|
||||
/* Compute the min and max values depending on the unit type */
|
||||
const minNumber = 1;
|
||||
const maxNumber = groupUnitCount[props.blueprint.category];
|
||||
@@ -28,61 +31,62 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
const [spawnLoadoutName, setSpawnLoadout] = useState("");
|
||||
const [spawnAltitude, setSpawnAltitude] = useState((maxAltitude - minAltitude) / 2);
|
||||
const [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
|
||||
|
||||
const [quickAccessName, setQuickAccessName] = useState("No name");
|
||||
const [key, setKey] = useState("");
|
||||
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (props.coalition && props.coalition !== spawnCoalition) {
|
||||
setSpawnCoalition(props.coalition);
|
||||
if (props.spawnAtLocation && spawnRequestTable) {
|
||||
/* Refresh the unique key identified */
|
||||
const newKey = hash(JSON.stringify(spawnRequestTable));
|
||||
setKey(newKey);
|
||||
|
||||
getApp()?.getMap()?.setSpawnRequestTable(spawnRequestTable);
|
||||
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_UNIT);
|
||||
}
|
||||
}, [spawnRequestTable]);
|
||||
|
||||
/* Callback and effect to update the quick access name of the starredSpawn */
|
||||
const updateStarredSpawnQuickAccessNameS = useCallback(() => {
|
||||
if (key in props.starredSpawns) props.starredSpawns[key].quickAccessName = quickAccessName;
|
||||
}, [props.starredSpawns, key, quickAccessName]);
|
||||
useEffect(updateStarredSpawnQuickAccessNameS, [quickAccessName]);
|
||||
|
||||
/* Callback and effect to update the quick access name in the input field */
|
||||
const updateQuickAccessName = useCallback(() => {
|
||||
if (props.spawnAtLocation) {
|
||||
if (props.blueprint !== null) {
|
||||
getApp()
|
||||
?.getMap()
|
||||
?.setSpawnRequestTable({
|
||||
category: props.blueprint.category,
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
skill: "High",
|
||||
liveryID: "",
|
||||
altitude: ftToM(spawnAltitude),
|
||||
loadout:
|
||||
props.blueprint.loadouts?.find((loadout) => {
|
||||
return loadout.name === spawnLoadoutName;
|
||||
})?.code ?? "",
|
||||
},
|
||||
coalition: spawnCoalition,
|
||||
});
|
||||
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_UNIT);
|
||||
} else {
|
||||
if (getApp().getState() === OlympusState.SPAWN) getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
/* If the spawn is starred, set the quick access name */
|
||||
if (key in props.starredSpawns && props.starredSpawns[key].quickAccessName) setQuickAccessName(props.starredSpawns[key].quickAccessName);
|
||||
else setQuickAccessName("No name");
|
||||
}
|
||||
}, [props.starredSpawns, key])
|
||||
useEffect(updateQuickAccessName, [key])
|
||||
|
||||
/* Callback and effect to update the spawn request table */
|
||||
const updateSpawnRequestTable = useCallback(() => {
|
||||
if (props.blueprint !== null) {
|
||||
setSpawnRequestTable({
|
||||
category: props.blueprint.category,
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
skill: "High",
|
||||
liveryID: "",
|
||||
altitude: ftToM(spawnAltitude),
|
||||
loadout: props.blueprint.loadouts?.find((loadout) => loadout.name === spawnLoadoutName)?.code ?? "",
|
||||
},
|
||||
coalition: spawnCoalition,
|
||||
});
|
||||
}
|
||||
}, [props.blueprint, spawnAltitude, spawnLoadoutName, spawnCoalition]);
|
||||
useEffect(updateSpawnRequestTable, [props.blueprint, spawnAltitude, spawnLoadoutName, spawnCoalition]);
|
||||
|
||||
function spawnAtAirbase() {
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
props.blueprint.category,
|
||||
[
|
||||
{
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // Not relevant spawning at airbase
|
||||
skill: "High",
|
||||
liveryID: "",
|
||||
altitude: 0,
|
||||
loadout:
|
||||
props.blueprint.loadouts?.find((loadout) => {
|
||||
return loadout.name === spawnLoadoutName;
|
||||
})?.code ?? "",
|
||||
},
|
||||
],
|
||||
props.coalition,
|
||||
false,
|
||||
props.airbase?.getName()
|
||||
);
|
||||
}
|
||||
/* Effect to update the coalition if it is force externally */
|
||||
useEffect(() => {
|
||||
if (props.coalition) setSpawnCoalition(props.coalition);
|
||||
}, [props.coalition]);
|
||||
|
||||
/* Get a list of all the roles */
|
||||
const roles: string[] = [];
|
||||
@@ -113,8 +117,8 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
<div className="flex h-fit flex-col gap-5 px-5 pb-8 pt-6">
|
||||
<div
|
||||
className={`
|
||||
inline-flex w-full flex-row content-center justify-between
|
||||
`}
|
||||
inline-flex w-full flex-row content-center justify-between gap-2
|
||||
`}
|
||||
>
|
||||
{!props.coalition && (
|
||||
<OlCoalitionToggle
|
||||
@@ -127,6 +131,7 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
/>
|
||||
)}
|
||||
<OlNumberInput
|
||||
className={"ml-auto"}
|
||||
value={spawnNumber}
|
||||
min={minNumber}
|
||||
max={maxNumber}
|
||||
@@ -141,6 +146,43 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
inline-flex w-full flex-row content-center justify-between gap-2
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-sm text-white">Quick access: </div>
|
||||
<OlStringInput
|
||||
onChange={(e) => {
|
||||
setQuickAccessName(e.target.value);
|
||||
}}
|
||||
value={quickAccessName}
|
||||
/>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
key in props.starredSpawns
|
||||
? getApp().getMap().removeStarredSpawnRequestTable(key)
|
||||
: getApp()
|
||||
.getMap()
|
||||
.addStarredSpawnRequestTable(key, {
|
||||
category: props.blueprint.category,
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
skill: "High",
|
||||
liveryID: "",
|
||||
altitude: ftToM(spawnAltitude),
|
||||
loadout: props.blueprint.loadouts?.find((loadout) => loadout.name === spawnLoadoutName)?.code ?? "",
|
||||
},
|
||||
coalition: spawnCoalition,
|
||||
quickAccessName: quickAccessName,
|
||||
});
|
||||
}}
|
||||
tooltip="Save this spawn for quick access"
|
||||
checked={key in props.starredSpawns}
|
||||
icon={faStar}
|
||||
></OlStateButton>
|
||||
</div>
|
||||
{["aircraft", "helicopter"].includes(props.blueprint.category) && (
|
||||
<>
|
||||
{!props.airbase && (
|
||||
@@ -283,7 +325,10 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
onClick={() => {
|
||||
spawnAtAirbase();
|
||||
if (spawnRequestTable)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(spawnRequestTable.category, [spawnRequestTable.unit], spawnRequestTable.coalition, false, props.airbase?.getName());
|
||||
}}
|
||||
>
|
||||
Spawn
|
||||
|
||||
@@ -8,13 +8,7 @@ import { MainMenu } from "./panels/mainmenu";
|
||||
import { SideBar } from "./panels/sidebar";
|
||||
import { OptionsMenu } from "./panels/optionsmenu";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import {
|
||||
NO_SUBSTATE,
|
||||
OlympusState,
|
||||
OlympusSubState,
|
||||
OptionsSubstate,
|
||||
UnitControlSubState,
|
||||
} from "../constants/constants";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants";
|
||||
import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/login";
|
||||
|
||||
@@ -34,6 +28,7 @@ import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
|
||||
import { GameMasterMenu } from "./panels/gamemastermenu";
|
||||
import { InfoBar } from "./panels/infobar";
|
||||
import { HotGroupBar } from "./panels/hotgroupsbar";
|
||||
import { StarredSpawnContextMenu } from "./contextmenus/starredspawncontextmenu";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@@ -58,8 +53,6 @@ export function UI() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
@@ -72,13 +65,12 @@ export function UI() {
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{appState === OlympusState.LOGIN && (
|
||||
<>
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}></div>
|
||||
<LoginModal
|
||||
|
||||
|
||||
/>
|
||||
`}
|
||||
></div>
|
||||
<LoginModal />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -87,7 +79,10 @@ export function UI() {
|
||||
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu
|
||||
open={appState === OlympusState.SPAWN && [SpawnSubState.NO_SUBSTATE, SpawnSubState.SPAWN_UNIT].includes(appSubState as SpawnSubState)}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
<UnitControlMenu
|
||||
@@ -117,6 +112,7 @@ export function UI() {
|
||||
<ControlsPanel />
|
||||
<UnitControlBar />
|
||||
<MapContextMenu />
|
||||
<StarredSpawnContextMenu />
|
||||
<SideBar />
|
||||
<InfoBar />
|
||||
<HotGroupBar />
|
||||
|
||||
Reference in New Issue
Block a user