Added starred spawns

This commit is contained in:
Pax1601
2024-11-12 16:35:01 +01:00
parent 68980651dc
commit 9d225bfc1a
16 changed files with 339 additions and 124 deletions

View File

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

View File

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

View File

@@ -99,6 +99,7 @@ export interface SpawnRequestTable {
category: string;
coalition: string;
unit: UnitSpawnTable;
quickAccessName?: string
}
export interface EffectRequestTable {

View File

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

View File

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

View File

@@ -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++) {

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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