mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Readded command mode options
This commit is contained in:
parent
454c7ad2de
commit
df939f1ac3
59
frontend/react/public/images/markers/explosion.svg
Normal file
59
frontend/react/public/images/markers/explosion.svg
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="52"
|
||||
height="52"
|
||||
viewBox="0 0 13.758333 13.758333"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="explosion.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
width="52mm"
|
||||
units="px"
|
||||
inkscape:zoom="8.3856042"
|
||||
inkscape:cx="14.250613"
|
||||
inkscape:cy="36.550735"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient4717"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#0cffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4715" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="stroke-width:6.985;stroke-linejoin:round;paint-order:stroke fill markers;fill-opacity:1"
|
||||
d="M 6.5628344,12.967909 1.2936356,11.16944 4.7012613,11.421857 1.4829482,6.9099074 5.0483343,9.6864912 3.5022818,0.8519064 6.8468032,9.623387 8.6452724,3.0290005 8.0142306,9.9389079 11.737377,6.8783553 8.5506162,10.980127 12.904805,10.885471 Z"
|
||||
id="path1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@ -1,5 +1,6 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { MapOptions } from "../types/types";
|
||||
import { CommandModeOptions } from "../interfaces";
|
||||
|
||||
export const UNITS_URI = "units";
|
||||
export const WEAPONS_URI = "weapons";
|
||||
@ -136,7 +137,7 @@ export const groupUnitCount: { [key: string]: number } = {
|
||||
helicopter: 4,
|
||||
navyunit: 20,
|
||||
groundunit: 20,
|
||||
}
|
||||
};
|
||||
|
||||
export const minimapBoundaries = {
|
||||
Nevada: [
|
||||
@ -252,6 +253,7 @@ export enum OlympusState {
|
||||
OPTIONS = "Options",
|
||||
AUDIO = "Audio",
|
||||
AIRBASE = "Airbase",
|
||||
GAME_MASTER = "Game master",
|
||||
}
|
||||
|
||||
export const NO_SUBSTATE = "No substate";
|
||||
@ -262,7 +264,7 @@ export enum UnitControlSubState {
|
||||
PROTECTION = "Protection",
|
||||
MAP_CONTEXT_MENU = "Map context menu",
|
||||
UNIT_CONTEXT_MENU = "Unit context menu",
|
||||
UNIT_EXPLOSION_MENU = "Unit explosion menu"
|
||||
UNIT_EXPLOSION_MENU = "Unit explosion menu",
|
||||
}
|
||||
|
||||
export enum DrawSubState {
|
||||
@ -324,6 +326,15 @@ export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
neutral: false,
|
||||
};
|
||||
|
||||
export const COMMAND_MODE_OPTIONS_DEFAULTS: CommandModeOptions = {
|
||||
commandMode: GAME_MASTER,
|
||||
eras: [] as string[],
|
||||
restrictSpawns: false,
|
||||
restrictToCoalition: false,
|
||||
setupTime: 0,
|
||||
spawnPoints: { blue: 0, red: 0 },
|
||||
};
|
||||
|
||||
export enum DataIndexes {
|
||||
startOfData = 0,
|
||||
category,
|
||||
|
||||
@ -147,13 +147,13 @@ export class CoalitionAreaSelectedEvent {
|
||||
}
|
||||
|
||||
export class AirbaseSelectedEvent {
|
||||
static on(callback: (airbase: Airbase) => void) {
|
||||
static on(callback: (airbase: Airbase | null) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.airbase);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(airbase: Airbase) {
|
||||
static dispatch(airbase: Airbase | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { airbase } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
@ -168,7 +168,7 @@ export class ContactsUpdatedEvent {
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +198,12 @@ export class ContextActionChangedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class UnitUpdatedEvent extends BaseUnitEvent {};
|
||||
export class UnitUpdatedEvent extends BaseUnitEvent {
|
||||
static dispatch(unit: Unit) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { unit } }));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
};
|
||||
export class UnitSelectedEvent extends BaseUnitEvent {};
|
||||
export class UnitDeselectedEvent extends BaseUnitEvent {};
|
||||
export class UnitDeadEvent extends BaseUnitEvent {};
|
||||
|
||||
@ -92,6 +92,8 @@ export interface SpawnRequestTable {
|
||||
|
||||
export interface EffectRequestTable {
|
||||
type: string;
|
||||
explosionType?: string;
|
||||
smokeColor?: string;
|
||||
}
|
||||
|
||||
export interface UnitSpawnTable {
|
||||
|
||||
@ -38,6 +38,7 @@ import { ExplosionMarker } from "./markers/explosionmarker";
|
||||
import { TextMarker } from "./markers/textmarker";
|
||||
import { TargetMarker } from "./markers/targetmarker";
|
||||
import {
|
||||
AirbaseSelectedEvent,
|
||||
AppStateChangedEvent,
|
||||
CoalitionAreaSelectedEvent,
|
||||
ConfigLoadedEvent,
|
||||
@ -49,6 +50,7 @@ import {
|
||||
UnitUpdatedEvent,
|
||||
} from "../events";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
import { SmokeMarker } from "./markers/smokemarker";
|
||||
|
||||
/* Register the handler for the box selection */
|
||||
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
|
||||
@ -85,7 +87,6 @@ export class Map extends L.Map {
|
||||
#isShiftKeyDown: boolean = false;
|
||||
|
||||
/* Center on unit target */
|
||||
// TODO add back
|
||||
#centeredUnit: Unit | null = null;
|
||||
|
||||
/* Minimap */
|
||||
@ -124,6 +125,7 @@ export class Map extends L.Map {
|
||||
#effectRequestTable: EffectRequestTable | null = null;
|
||||
#temporaryMarkers: TemporaryUnitMarker[] = [];
|
||||
#currentSpawnMarker: TemporaryUnitMarker | null = null;
|
||||
#currentEffectMarker: ExplosionMarker | SmokeMarker | null = null;
|
||||
|
||||
/* JTAC tools */
|
||||
#ECHOPoint: TextMarker | null = null;
|
||||
@ -201,9 +203,8 @@ export class Map extends L.Map {
|
||||
});
|
||||
|
||||
UnitUpdatedEvent.on((unit) => {
|
||||
if (this.#centeredUnit != null && unit == this.#centeredUnit)
|
||||
this.#panToUnit(this.#centeredUnit);
|
||||
})
|
||||
if (this.#centeredUnit != null && unit == this.#centeredUnit) this.#panToUnit(this.#centeredUnit);
|
||||
});
|
||||
|
||||
MapOptionsChangedEvent.on((options) => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !options.showUnitLabels);
|
||||
@ -723,12 +724,6 @@ export class Map extends L.Map {
|
||||
return marker;
|
||||
}
|
||||
|
||||
addExplosionMarker(latlng: L.LatLng, timeout: number = 30) {
|
||||
var marker = new ExplosionMarker(latlng, timeout);
|
||||
marker.addTo(this);
|
||||
return marker;
|
||||
}
|
||||
|
||||
setOption(key, value) {
|
||||
this.#options[key] = value;
|
||||
MapOptionsChangedEvent.dispatch(this.#options);
|
||||
@ -814,8 +809,11 @@ export class Map extends L.Map {
|
||||
this.getSelectedCoalitionArea()?.setEditing(false);
|
||||
this.#currentSpawnMarker?.removeFrom(this);
|
||||
this.#currentSpawnMarker = null;
|
||||
this.#currentEffectMarker?.removeFrom(this);
|
||||
this.#currentEffectMarker = null;
|
||||
if (state !== OlympusState.UNIT_CONTROL) getApp().getUnitsManager().deselectAllUnits();
|
||||
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas();
|
||||
AirbaseSelectedEvent.dispatch(null);
|
||||
|
||||
/* Operations to perform when entering a state */
|
||||
if (state === OlympusState.IDLE) {
|
||||
@ -833,9 +831,11 @@ export class Map extends L.Map {
|
||||
} else if (subState === SpawnSubState.SPAWN_EFFECT) {
|
||||
console.log(`Effect request table:`);
|
||||
console.log(this.#effectRequestTable);
|
||||
// TODO add temporary effect marker
|
||||
//this.#currentEffectMarker = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "", this.#spawnRequestTable?.coalition ?? "neutral")
|
||||
//this.#currentEffectMarker.addTo(this);
|
||||
if (this.#effectRequestTable?.type === 'explosion')
|
||||
this.#currentEffectMarker = new ExplosionMarker(new L.LatLng(0, 0))
|
||||
else if (this.#effectRequestTable?.type === 'smoke')
|
||||
this.#currentEffectMarker = new SmokeMarker(new L.LatLng(0, 0), this.#effectRequestTable.smokeColor ?? "white")
|
||||
this.#currentEffectMarker?.addTo(this);
|
||||
}
|
||||
} else if (state === OlympusState.UNIT_CONTROL) {
|
||||
console.log(`Context action:`);
|
||||
@ -941,7 +941,21 @@ export class Map extends L.Map {
|
||||
}
|
||||
} else if (getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
||||
if (e.originalEvent.button != 2 && this.#effectRequestTable !== null) {
|
||||
getApp().getServerManager().spawnExplosion(50, "normal", pressLocation);
|
||||
if (this.#effectRequestTable.type === "explosion") {
|
||||
if (this.#effectRequestTable.explosionType === "High explosive") getApp().getServerManager().spawnExplosion(50, "normal", pressLocation);
|
||||
else if (this.#effectRequestTable.explosionType === "Napalm") getApp().getServerManager().spawnExplosion(50, "napalm", pressLocation);
|
||||
else if (this.#effectRequestTable.explosionType === "White phosphorous")
|
||||
getApp().getServerManager().spawnExplosion(50, "phosphorous", pressLocation);
|
||||
|
||||
const explosionMarker = new ExplosionMarker(pressLocation, 5);
|
||||
explosionMarker.addTo(this);
|
||||
} else if (this.#effectRequestTable.type === "smoke") {
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", pressLocation);
|
||||
const smokeMarker = new SmokeMarker(pressLocation, this.#effectRequestTable.smokeColor ?? "white");
|
||||
smokeMarker.addTo(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (getApp().getState() === OlympusState.DRAW) {
|
||||
@ -1056,9 +1070,10 @@ export class Map extends L.Map {
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
this.#lastMouseCoordinates = e.latlng;
|
||||
|
||||
if (this.#currentSpawnMarker) {
|
||||
if (this.#currentSpawnMarker)
|
||||
this.#currentSpawnMarker.setLatLng(e.latlng);
|
||||
}
|
||||
if (this.#currentEffectMarker)
|
||||
this.#currentEffectMarker.setLatLng(e.latlng);
|
||||
}
|
||||
|
||||
#onMapMove(e: any) {
|
||||
|
||||
@ -7,32 +7,33 @@ export class ExplosionMarker extends CustomMarker {
|
||||
#timer: number = 0;
|
||||
#timeout: number = 0;
|
||||
|
||||
constructor(latlng: LatLng, timeout: number) {
|
||||
constructor(latlng: LatLng, timeout?: number) {
|
||||
super(latlng, { interactive: false });
|
||||
|
||||
this.#timeout = timeout;
|
||||
if (timeout) {
|
||||
this.#timeout = timeout;
|
||||
|
||||
this.#timer = window.setTimeout(() => {
|
||||
this.removeFrom(getApp().getMap());
|
||||
}, timeout * 1000);
|
||||
this.#timer = window.setTimeout(() => {
|
||||
this.removeFrom(getApp().getMap());
|
||||
}, timeout * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
className: "leaflet-explosion-icon",
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
this.setIcon(icon);
|
||||
|
||||
this.setIcon(
|
||||
new DivIcon({
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 52],
|
||||
className: "leaflet-explosion-marker",
|
||||
})
|
||||
);
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-explosion-icon");
|
||||
var img = document.createElement("img");
|
||||
img.src = `/vite/images/markers/smoke.svg`;
|
||||
img.src = "/vite/images/markers/explosion.svg";
|
||||
img.onload = () => SVGInjector(img);
|
||||
el.append(img);
|
||||
|
||||
el.appendChild(img);
|
||||
this.getElement()?.appendChild(el);
|
||||
this.getElement()?.classList.add("ol-temporary-marker");
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,3 +24,7 @@
|
||||
.airbase-icon[data-coalition="neutral"] svg * {
|
||||
stroke: var(--unit-background-neutral);
|
||||
}
|
||||
|
||||
.airbase-icon[data-selected="true"] {
|
||||
filter: drop-shadow(0px 2px 0px white) drop-shadow(0px -2px 0px white) drop-shadow(2px 0px 0px white) drop-shadow(-2px 0px 0px white);
|
||||
}
|
||||
|
||||
@ -144,4 +144,36 @@
|
||||
font-weight: bold;
|
||||
border: 2px solid black;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ol-smoke-icon {
|
||||
opacity: 75%;
|
||||
}
|
||||
|
||||
[data-color="white"].ol-smoke-icon {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
[data-color="blue"].ol-smoke-icon {
|
||||
fill: blue;
|
||||
}
|
||||
|
||||
[data-color="red"].ol-smoke-icon {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
[data-color="green"].ol-smoke-icon {
|
||||
fill: green;
|
||||
}
|
||||
|
||||
[data-color="orange"].ol-smoke-icon {
|
||||
fill: orange;
|
||||
}
|
||||
|
||||
.ol-explosion-icon * {
|
||||
opacity: 75%;
|
||||
}
|
||||
|
||||
.ol-explosion-icon {
|
||||
fill: red;
|
||||
}
|
||||
@ -19,6 +19,7 @@ export class Airbase extends CustomMarker {
|
||||
#properties: string[] = [];
|
||||
#parkings: string[] = [];
|
||||
#img: HTMLImageElement;
|
||||
#selected: boolean = false;
|
||||
|
||||
constructor(options: AirbaseOptions) {
|
||||
super(options.position, { riseOnHover: true });
|
||||
@ -26,8 +27,14 @@ export class Airbase extends CustomMarker {
|
||||
this.#name = options.name;
|
||||
this.#img = document.createElement("img");
|
||||
|
||||
AirbaseSelectedEvent.on((airbase) => {
|
||||
this.#selected = airbase == this;
|
||||
if (this.getElement()?.querySelector(".airbase-icon"))
|
||||
(this.getElement()?.querySelector(".airbase-icon") as HTMLElement).dataset.selected = `${this.#selected}`;
|
||||
})
|
||||
|
||||
this.addEventListener("click", (ev) => {
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.AIRBASE) {
|
||||
getApp().setState(OlympusState.AIRBASE)
|
||||
AirbaseSelectedEvent.dispatch(this)
|
||||
}
|
||||
|
||||
@ -223,7 +223,8 @@ export class MissionManager {
|
||||
commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red ||
|
||||
commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue ||
|
||||
commandModeOptions.restrictSpawns !== this.getCommandModeOptions().restrictSpawns ||
|
||||
commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition;
|
||||
commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition ||
|
||||
commandModeOptions.setupTime !== this.getCommandModeOptions().setupTime;
|
||||
|
||||
this.#commandModeOptions = commandModeOptions;
|
||||
this.setSpentSpawnPoints(0);
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
emissionsCountermeasures,
|
||||
reactionsToThreat,
|
||||
} from "../constants/constants";
|
||||
import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from "../interfaces";
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from "../interfaces";
|
||||
import { ServerStatusUpdatedEvent } from "../events";
|
||||
|
||||
export class ServerManager {
|
||||
@ -489,22 +489,10 @@ export class ServerManager {
|
||||
}
|
||||
|
||||
setCommandModeOptions(
|
||||
restrictSpawns: boolean,
|
||||
restrictToCoalition: boolean,
|
||||
spawnPoints: { blue: number; red: number },
|
||||
eras: string[],
|
||||
setupTime: number,
|
||||
commandModeOptions: CommandModeOptions,
|
||||
callback: CallableFunction = () => {}
|
||||
) {
|
||||
var command = {
|
||||
restrictSpawns: restrictSpawns,
|
||||
restrictToCoalition: restrictToCoalition,
|
||||
spawnPoints: spawnPoints,
|
||||
eras: eras,
|
||||
setupTime: setupTime,
|
||||
};
|
||||
|
||||
var data = { setCommandModeOptions: command };
|
||||
var data = { setCommandModeOptions: commandModeOptions };
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { OlTooltip } from "./oltooltip";
|
||||
|
||||
export function OlStateButton(props: {
|
||||
className?: string;
|
||||
borderColor?: string | null;
|
||||
checked: boolean;
|
||||
icon: IconProp;
|
||||
tooltip: string;
|
||||
@ -35,6 +36,9 @@ export function OlStateButton(props: {
|
||||
data-checked={props.checked}
|
||||
type="button"
|
||||
className={className}
|
||||
style={{
|
||||
border: props.borderColor ? "2px solid " + props.borderColor : "0px solid transparent"
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setHover(true);
|
||||
}}
|
||||
|
||||
@ -4,8 +4,10 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
|
||||
|
||||
export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprint; onClick: () => void }) {
|
||||
const pillString = !["aircraft", "helicopter"].includes(props.blueprint.category) ? props.blueprint.type : props.blueprint.abilities;
|
||||
export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprint; showCost: boolean; cost: number; onClick: () => void }) {
|
||||
let pillString = "" as string | undefined
|
||||
if (props.showCost) pillString = `${props.cost} points`
|
||||
else pillString = !["aircraft", "helicopter"].includes(props.blueprint.category) ? props.blueprint.type : props.blueprint.abilities
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
@ -24,7 +26,7 @@ export function OlUnitListEntry(props: { icon: IconProp; blueprint: UnitBlueprin
|
||||
dark:text-olympus-50
|
||||
`}
|
||||
>
|
||||
{!["aircraft", "helicopter"].includes(props.blueprint.category) ? props.blueprint.type : props.blueprint.abilities}
|
||||
{pillString}
|
||||
</div>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
@ -9,15 +9,59 @@ import { OlAccordion } from "../components/olaccordion";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { olButtonsVisibilityAircraft, olButtonsVisibilityHelicopter } from "../components/olicons";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
import { AirbaseSelectedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
|
||||
export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase: Airbase | null; children?: JSX.Element | JSX.Element[] }) {
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
AIRCRAFT,
|
||||
HELICOPTER,
|
||||
}
|
||||
|
||||
export function AirbaseMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [airbase, setAirbase] = useState(null as null | Airbase);
|
||||
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const [selectedRole, setSelectedRole] = useState(null as null | string);
|
||||
const [runwaysAccordionOpen, setRunwaysAccordionOpen] = useState(false);
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE);
|
||||
|
||||
const [filteredAircraft, filteredHelicopters] = [{}, {}] // TODOgetUnitsByLabel(filterString);
|
||||
useEffect(() => {
|
||||
AirbaseSelectedEvent.on((airbase) => {
|
||||
setAirbase(airbase);
|
||||
});
|
||||
|
||||
UnitDatabaseLoadedEvent.on(() => {
|
||||
setRoles({
|
||||
aircraft: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getRoles((unit) => unit.category === "aircraft"),
|
||||
helicopter: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getRoles((unit) => unit.category === "helicopter"),
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
else setBlueprints(getApp()?.getUnitsManager().getDatabase().getBlueprints());
|
||||
}, [selectedRole, openAccordion]);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
const filteredBlueprints: UnitBlueprint[] = [];
|
||||
if (blueprints) {
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.enabled && (filterString === "" || blueprint.label.toLowerCase().includes(filterString.toLowerCase()))) filteredBlueprints.push(blueprint);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu title={props.airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
|
||||
<Menu title={airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-2 font-normal text-gray-800
|
||||
@ -25,7 +69,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
`}
|
||||
>
|
||||
<div
|
||||
data-coalition={props.airbase?.getCoalition()}
|
||||
data-coalition={airbase?.getCoalition()}
|
||||
className={`
|
||||
flex flex-col content-center justify-between gap-2 border-l-4
|
||||
bg-olympus-200/30 py-3 pl-4 pr-5
|
||||
@ -36,29 +80,35 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-gray-400">ICAO name</span>
|
||||
<span>{props.airbase?.getChartData().ICAO !== "" ? props.airbase?.getChartData().ICAO : "N/A"}</span>
|
||||
<span>{airbase?.getChartData().ICAO !== "" ? airbase?.getChartData().ICAO : "N/A"}</span>
|
||||
</div>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-gray-400">TACAN</span>
|
||||
<span>{props.airbase?.getChartData().TACAN !== "" ? props.airbase?.getChartData().TACAN : "None"}</span>
|
||||
<span>{airbase?.getChartData().TACAN !== "" ? airbase?.getChartData().TACAN : "None"}</span>
|
||||
</div>
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-gray-400">Elevation</span>
|
||||
<span>{props.airbase?.getChartData().elevation !== "" ? props.airbase?.getChartData().elevation : "N/A"}ft</span>
|
||||
<span>{airbase?.getChartData().elevation !== "" ? airbase?.getChartData().elevation : "N/A"}ft</span>
|
||||
</div>
|
||||
{
|
||||
// TODO
|
||||
// TODO I can't remember what tho
|
||||
}
|
||||
<OlAccordion title={`Runways`} className="!p-0 !text-gray-400">
|
||||
<OlAccordion
|
||||
title={`Runways`}
|
||||
className="!p-0 !text-gray-400"
|
||||
onClick={() => setRunwaysAccordionOpen(!runwaysAccordionOpen)}
|
||||
open={runwaysAccordionOpen}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
{props.airbase?.getChartData().runways.map((runway, idx) => {
|
||||
{airbase?.getChartData().runways.map((runway, idx) => {
|
||||
return (
|
||||
<>
|
||||
{Object.keys(runway.headings[0]).map((runwayName) => {
|
||||
return (
|
||||
<div key={`${idx}-${runwayName}`} className={`
|
||||
flex w-full justify-between
|
||||
`}>
|
||||
<div
|
||||
key={`${idx}-${runwayName}`}
|
||||
className={`flex w-full justify-between`}
|
||||
>
|
||||
<span>
|
||||
{" "}
|
||||
<span className="text-gray-400">RWY</span> {runwayName}
|
||||
@ -82,7 +132,6 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 flex gap-2 px-5 text-white bold">
|
||||
{blueprint && (
|
||||
<FaArrowLeft
|
||||
@ -95,42 +144,98 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
|
||||
)}
|
||||
<span className="my-auto">Spawn units at airbase</span>
|
||||
</div>
|
||||
{blueprint === null && (
|
||||
<div className="p-5">
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion
|
||||
title={`Aircraft`}
|
||||
open={openAccordion == CategoryAccordion.AIRCRAFT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.AIRCRAFT ? CategoryAccordion.NONE : CategoryAccordion.AIRCRAFT);
|
||||
setSelectedRole(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{roles.aircraft.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Helicopters`}
|
||||
open={openAccordion == CategoryAccordion.HELICOPTER}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.HELICOPTER ? CategoryAccordion.NONE : CategoryAccordion.HELICOPTER);
|
||||
setSelectedRole(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{roles.helicopter.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
<>
|
||||
{blueprint === null && (
|
||||
<div className="p-5">
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion title={`Aircraft`}>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAircraft).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Helicopters`}>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredHelicopters).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!(blueprint === null) && (
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
spawnAtLocation={false}
|
||||
airbase={props.airbase}
|
||||
coalition={(props.airbase?.getCoalition() ?? "blue") as Coalition}
|
||||
/>
|
||||
<UnitSpawnMenu blueprint={blueprint} spawnAtLocation={false} airbase={airbase} coalition={(airbase?.getCoalition() ?? "blue") as Coalition} />
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
|
||||
@ -1,45 +1,74 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function EffectSpawnMenu(props: { effect: string }) {
|
||||
const [explosionType, setExplosionType] = useState("High explosive");
|
||||
const [smokeColor, setSmokeColor] = useState("white");
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
// TODO
|
||||
//if (props.effect !== null) {
|
||||
// getApp()
|
||||
// ?.getMap()
|
||||
// ?.setState(SPAWN_EFFECT, {
|
||||
// effectRequestTable: {
|
||||
// type: props.effect,
|
||||
// }
|
||||
// });
|
||||
//} else {
|
||||
// if (getApp().getState() === SPAWN_EFFECT) getApp().setState(OlympusState.IDLE);
|
||||
//}
|
||||
|
||||
if (props.effect !== null) {
|
||||
if (props.effect === "explosion")
|
||||
getApp()?.getMap()?.setEffectRequestTable({
|
||||
type: props.effect,
|
||||
explosionType,
|
||||
});
|
||||
else if (props.effect === "smoke")
|
||||
getApp()?.getMap()?.setEffectRequestTable({
|
||||
type: props.effect,
|
||||
smokeColor,
|
||||
});
|
||||
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_EFFECT);
|
||||
} else {
|
||||
if (getApp().getState() === OlympusState.SPAWN && getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
<span className="text-white">Explosion type</span>
|
||||
|
||||
<OlDropdown label={explosionType} className="w-full">
|
||||
{["High explosive", "Napalm", "White phosphorous"].map((optionExplosionType) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={optionExplosionType}
|
||||
onClick={() => {
|
||||
setExplosionType(optionExplosionType);
|
||||
}}
|
||||
>
|
||||
{optionExplosionType}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
{props.effect === "explosion" && (
|
||||
<>
|
||||
<span className="text-white">Explosion type</span>
|
||||
<OlDropdown label={explosionType} className="w-full">
|
||||
{["High explosive", "Napalm", "White phosphorous"].map((optionExplosionType) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={optionExplosionType}
|
||||
onClick={() => {
|
||||
setExplosionType(optionExplosionType);
|
||||
}}
|
||||
>
|
||||
{optionExplosionType}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</>
|
||||
)}
|
||||
{props.effect === "smoke" && (
|
||||
<>
|
||||
<span className="text-white">Smoke color</span>
|
||||
<div className="flex w-full gap-2">
|
||||
{["white", "blue", "red", "green", "orange"].map((optionSmokeColor) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
checked={smokeColor === optionSmokeColor}
|
||||
icon={faSmog}
|
||||
onClick={() => {
|
||||
setSmokeColor(optionSmokeColor);
|
||||
}}
|
||||
tooltip=""
|
||||
borderColor={optionSmokeColor}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
296
frontend/react/src/ui/panels/gamemastermenu.tsx
Normal file
296
frontend/react/src/ui/panels/gamemastermenu.tsx
Normal file
@ -0,0 +1,296 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { MapOptions } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { CommandModeOptions, ServerStatus } from "../../interfaces";
|
||||
import { CommandModeOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ERAS, GAME_MASTER, RED_COMMANDER } from "../../constants/constants";
|
||||
|
||||
export function GameMasterMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [currentSetupTime, setCurrentSetupTime] = useState(300);
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
|
||||
useEffect(() => {
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
setCurrentSetupTime(commandModeOptions.setupTime);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Menu title="Game Master options" open={props.open} showBackButton={false} onClose={props.onClose}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-2 p-5 font-normal text-gray-800
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
You are operating as:
|
||||
{commandModeOptions.commandMode === GAME_MASTER && (
|
||||
<div
|
||||
className={`
|
||||
w-full rounded-md bg-olympus-400 p-2 text-center font-bold
|
||||
`}
|
||||
>
|
||||
GAME MASTER
|
||||
</div>
|
||||
)}
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && <div className={`
|
||||
w-full rounded-md bg-blue-600 p-2 text-center font-bold
|
||||
`}>BLUE COMMANDER</div>}
|
||||
{commandModeOptions.commandMode === RED_COMMANDER && <div className={`
|
||||
w-full rounded-md bg-red-700 p-2 text-center font-bold
|
||||
`}>RED COMMANDER</div>}
|
||||
{serverStatus.elapsedTime > currentSetupTime && (
|
||||
<div
|
||||
className={`
|
||||
w-full rounded-md bg-orange-600 p-2 text-center font-bold
|
||||
`}
|
||||
>
|
||||
Setup time has ended
|
||||
</div>
|
||||
)}
|
||||
{serverStatus.elapsedTime <= currentSetupTime && (
|
||||
<div
|
||||
className={`
|
||||
w-full rounded-md bg-green-700 p-2 text-center font-bold
|
||||
`}
|
||||
>
|
||||
SETUP ends in {(currentSetupTime - serverStatus.elapsedTime)?.toFixed()} seconds
|
||||
</div>
|
||||
)}
|
||||
<span className="mt-5">Options: </span>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
gap-4 p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
if (commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.restrictSpawns = !commandModeOptions.restrictSpawns;
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox checked={commandModeOptions.restrictSpawns} onChange={() => {}} disabled={commandModeOptions.commandMode !== GAME_MASTER} />
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`data-[disabled='true']:text-gray-400`}
|
||||
>
|
||||
Restrict unit spanws
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
gap-4 p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.restrictToCoalition = !commandModeOptions.restrictToCoalition;
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={commandModeOptions.restrictToCoalition}
|
||||
onChange={() => {}}
|
||||
disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
/>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`data-[disabled='true']:text-gray-400`}
|
||||
>
|
||||
Restrict spawns to coalition
|
||||
</span>
|
||||
</div>
|
||||
{ERAS.sort((a, b) => (a.chronologicalOrder > b.chronologicalOrder ? 1 : -1)).map((era) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
gap-4 p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
if (commandModeOptions.eras.includes(era.name)) newCommandModeOptions.eras.splice(newCommandModeOptions.eras.indexOf(era.name));
|
||||
else newCommandModeOptions.eras.push(era.name);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={commandModeOptions.eras.includes(era.name)}
|
||||
onChange={() => {}}
|
||||
disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
/>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`data-[disabled='true']:text-gray-400`}
|
||||
>
|
||||
Allow {era.name} units
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4
|
||||
bg-blue-600/40 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`
|
||||
my-auto mr-auto
|
||||
data-[disabled='true']:text-gray-400
|
||||
`}
|
||||
>
|
||||
Blue spawn points
|
||||
</span>
|
||||
<OlNumberInput
|
||||
min={0}
|
||||
max={1e6}
|
||||
value={commandModeOptions.spawnPoints.blue}
|
||||
onChange={(e) => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.blue = parseInt(e.target.value);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onIncrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.blue = Math.min(newCommandModeOptions.spawnPoints.blue + 10, 1000000);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onDecrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.blue = Math.max(newCommandModeOptions.spawnPoints.blue - 10, 0);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
></OlNumberInput>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4 bg-red-600/40
|
||||
px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`
|
||||
my-auto mr-auto
|
||||
data-[disabled='true']:text-gray-400
|
||||
`}
|
||||
>
|
||||
Red spawn points
|
||||
</span>
|
||||
<OlNumberInput
|
||||
min={0}
|
||||
max={1e6}
|
||||
value={commandModeOptions.spawnPoints.red}
|
||||
onChange={(e) => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.red = parseInt(e.target.value);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onIncrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.red = Math.min(newCommandModeOptions.spawnPoints.red + 15, 6000);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onDecrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.spawnPoints.red = Math.max(newCommandModeOptions.spawnPoints.red - 15, 0);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
></OlNumberInput>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-disabled={!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER}
|
||||
className={`
|
||||
my-auto mr-auto
|
||||
data-[disabled='true']:text-gray-400
|
||||
`}
|
||||
>
|
||||
Setup time (seconds)
|
||||
</span>
|
||||
<OlNumberInput
|
||||
min={0}
|
||||
max={6000}
|
||||
value={commandModeOptions.setupTime}
|
||||
onChange={(e) => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.setupTime = parseInt(e.target.value);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onIncrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.setupTime = Math.min(newCommandModeOptions.setupTime + 10, 6000);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
onDecrease={() => {
|
||||
if (!commandModeOptions.restrictSpawns || commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
const newCommandModeOptions = { ...commandModeOptions };
|
||||
newCommandModeOptions.setupTime = Math.max(newCommandModeOptions.setupTime - 10, 0);
|
||||
setCommandModeOptions(newCommandModeOptions);
|
||||
}}
|
||||
></OlNumberInput>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content gap-4 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span className="mr-auto">Elapsed time (seconds)</span>{" "}
|
||||
<span
|
||||
className={`w-32 text-center`}
|
||||
>
|
||||
{serverStatus.elapsedTime?.toFixed()}
|
||||
</span>
|
||||
</div>
|
||||
{commandModeOptions.commandMode === GAME_MASTER && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (commandModeOptions.commandMode !== GAME_MASTER) return;
|
||||
getApp().getServerManager().setCommandModeOptions(commandModeOptions);
|
||||
}}
|
||||
className={`
|
||||
w-full rounded-lg bg-blue-700 px-5 py-2.5 text-md font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
|
||||
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP } from "../../olympusapp";
|
||||
@ -17,9 +16,9 @@ import {
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { CommandModeOptions, OlympusConfig } from "../../interfaces";
|
||||
|
||||
export function Header() {
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
@ -29,6 +28,7 @@ export function Header() {
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
const [audioEnabled, setAudioEnabled] = useState(false);
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
|
||||
useEffect(() => {
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
@ -38,6 +38,9 @@ export function Header() {
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
});
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
@ -111,6 +114,9 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && <div className={`
|
||||
flex h-full rounded-md bg-blue-600 px-4 text-white
|
||||
`}><span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span></div>}
|
||||
<div
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
|
||||
132
frontend/react/src/ui/panels/infobar.tsx
Normal file
132
frontend/react/src/ui/panels/infobar.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent } from "../../events";
|
||||
|
||||
export function InfoBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) onScroll(scrollRef.current);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollLeft;
|
||||
const sr = el.scrollWidth - el.scrollLeft - el.clientWidth;
|
||||
|
||||
sl < 1 && !scrolledLeft && setScrolledLeft(true);
|
||||
sl > 1 && scrolledLeft && setScrolledLeft(false);
|
||||
|
||||
sr < 1 && !scrolledRight && setScrolledRight(true);
|
||||
sr > 1 && scrolledRight && setScrolledRight(false);
|
||||
}
|
||||
|
||||
let reorderedActions: ContextAction[] = [];
|
||||
CONTEXT_ACTION_COLORS.forEach((color) => {
|
||||
if (contextActionSet) {
|
||||
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
|
||||
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-16 flex max-w-[80%]
|
||||
translate-x-[calc(-50%+2rem)] gap-2 rounded-md bg-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
{!scrolledLeft && (
|
||||
<FaChevronLeft
|
||||
className={`
|
||||
absolute left-0 h-full w-6 rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
borderColor={contextActionIt.getOptions().buttonColor}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!scrolledRight && (
|
||||
<FaChevronRight
|
||||
className={`
|
||||
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-32 flex min-w-[300px]
|
||||
translate-x-[calc(-50%+2rem)] items-center gap-2 rounded-md
|
||||
bg-gray-200 p-4
|
||||
dark:bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<FaInfoCircle
|
||||
className={`
|
||||
mr-2 hidden min-w-8 text-sm text-blue-500
|
||||
md:block
|
||||
`}
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
px-2
|
||||
dark:text-gray-400
|
||||
md:border-l-[1px] md:px-5
|
||||
`}
|
||||
>
|
||||
{contextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -2,18 +2,17 @@ import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import {
|
||||
faGamepad,
|
||||
faRuler,
|
||||
faPencil,
|
||||
faEllipsisV,
|
||||
faCog,
|
||||
faQuestionCircle,
|
||||
faPlusSquare,
|
||||
faMagnifyingGlass,
|
||||
faVolumeHigh,
|
||||
faJ,
|
||||
faCrown,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function SideBar() {
|
||||
@ -80,6 +79,14 @@ export function SideBar() {
|
||||
icon={faJ}
|
||||
tooltip="Hide/show JTAC menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.GAME_MASTER ? OlympusState.GAME_MASTER : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.GAME_MASTER}
|
||||
icon={faCrown}
|
||||
tooltip="Hide/show Game Master menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
|
||||
@ -16,8 +16,8 @@ import {
|
||||
import { faExplosion, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import { EffectSpawnMenu } from "./effectspawnmenu";
|
||||
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, CommandModeOptionsChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
@ -40,6 +40,8 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [types, setTypes] = useState({ groundunit: [] as string[], navyunit: [] as string[] });
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [showCost, setShowCost] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
@ -71,6 +73,19 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
.getTypes((unit) => unit.category === "navyunit"),
|
||||
});
|
||||
});
|
||||
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
if (subState === NO_SUBSTATE) {
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
}
|
||||
});
|
||||
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
setShowCost(!(commandModeOptions.commandMode == GAME_MASTER || !commandModeOptions.restrictSpawns));
|
||||
setOpenAccordion(CategoryAccordion.NONE);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
@ -90,15 +105,6 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
if (subState === NO_SUBSTATE) {
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
{...props}
|
||||
@ -153,8 +159,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityAircraft}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@ -196,8 +211,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityHelicopter}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@ -218,8 +242,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit" && blueprint.type === "SAM Site")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunitSam}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@ -240,8 +273,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.canAAA)
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunitSam}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@ -286,8 +328,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit" && blueprint.type !== "SAM Site")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@ -329,8 +380,17 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
>
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "navyunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityNavyunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityNavyunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={blueprint.cost ?? 10}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
@ -370,7 +430,13 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} spawnAtLocation={true} />}
|
||||
{!(blueprint === null) && (
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
spawnAtLocation={true}
|
||||
coalition={commandModeOptions.commandMode !== GAME_MASTER ? (commandModeOptions.commandMode === BLUE_COMMANDER ? "blue" : "red") : undefined}
|
||||
/>
|
||||
)}
|
||||
{!(effect === null) && <EffectSpawnMenu effect={effect} />}
|
||||
</>
|
||||
</Menu>
|
||||
|
||||
@ -77,14 +77,7 @@ export function UnitControlBar(props: {}) {
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
className={
|
||||
contextActionIt.getOptions().buttonColor
|
||||
? `
|
||||
border-2
|
||||
border-${contextActionIt.getOptions().buttonColor}-500
|
||||
`
|
||||
: ""
|
||||
}
|
||||
borderColor={contextActionIt.getOptions().buttonColor}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
|
||||
@ -6,7 +6,7 @@ import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlButtonGroup, OlButtonGroupItem } from "../components/olbuttongroup";
|
||||
import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { ROEs, altitudeIncrements, emissionsCountermeasures, maxAltitudeValues, minAltitudeValues, reactionsToThreat, speedIncrements } from "../../constants/constants";
|
||||
import { ROEs, altitudeIncrements, emissionsCountermeasures, maxAltitudeValues, maxSpeedValues, minAltitudeValues, reactionsToThreat, speedIncrements } from "../../constants/constants";
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import {
|
||||
@ -207,22 +207,22 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const minSpeed = 0;
|
||||
|
||||
let maxAltitude = maxAltitudeValues.aircraft;
|
||||
let maxSpeed = minAltitudeValues.aircraft;
|
||||
let maxSpeed = maxSpeedValues.aircraft;
|
||||
let speedStep = speedIncrements.aircraft;
|
||||
let altitudeStep = altitudeIncrements.aircraft;
|
||||
|
||||
if (everyUnitIsHelicopter) {
|
||||
maxAltitude = maxAltitudeValues.helicopter;
|
||||
maxSpeed = minAltitudeValues.helicopter;
|
||||
maxSpeed = maxSpeedValues.helicopter;
|
||||
speedStep = speedIncrements.helicopter;
|
||||
altitudeStep = altitudeIncrements.helicopter;
|
||||
}
|
||||
else if (everyUnitIsGround) {
|
||||
maxSpeed = minAltitudeValues.groundunit;
|
||||
maxSpeed = maxSpeedValues.groundunit;
|
||||
speedStep = speedIncrements.groundunit;
|
||||
}
|
||||
else if (everyUnitIsNavy) {
|
||||
maxSpeed = minAltitudeValues.navyunit;
|
||||
maxSpeed = maxSpeedValues.navyunit;
|
||||
speedStep = speedIncrements.navyunit;
|
||||
}
|
||||
|
||||
|
||||
@ -143,38 +143,40 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
</div>
|
||||
{["aircraft", "helicopter"].includes(props.blueprint.category) && (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-row content-center items-center justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
{!props.airbase && (
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-row content-center items-center justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
</div>
|
||||
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
|
||||
</div>
|
||||
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
|
||||
value={spawnAltitude}
|
||||
min={minAltitude}
|
||||
max={maxAltitude}
|
||||
step={altitudeStep}
|
||||
/>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
|
||||
value={spawnAltitude}
|
||||
min={minAltitude}
|
||||
max={maxAltitude}
|
||||
step={altitudeStep}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="flex flex-row content-center justify-between">
|
||||
<span
|
||||
|
||||
@ -35,6 +35,7 @@ import { ProtectionPrompt } from "./modals/protectionprompt";
|
||||
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
|
||||
import { GameMasterMenu } from "./panels/gamemastermenu";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@ -57,8 +58,6 @@ export function UI() {
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
const [commandMode, setCommandMode] = useState(null as null | string);
|
||||
|
||||
const [airbase, setAirbase] = useState(null as null | Airbase);
|
||||
|
||||
const [formationLeader, setFormationLeader] = useState(null as null | Unit);
|
||||
const [formationWingmen, setFormationWingmen] = useState(null as null | Unit[]);
|
||||
|
||||
@ -157,8 +156,9 @@ export function UI() {
|
||||
/>
|
||||
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} /* TODO remove */ />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)}/>
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<GameMasterMenu open={appState === OlympusState.GAME_MASTER} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
@ -211,14 +211,8 @@ export class UnitDatabase {
|
||||
return null;
|
||||
}
|
||||
|
||||
getSpawnPointsByLabel(label: string) {
|
||||
var blueprint = this.getByLabel(label);
|
||||
if (blueprint) return this.getSpawnPointsByName(blueprint.name);
|
||||
else return Infinity;
|
||||
}
|
||||
|
||||
getSpawnPointsByName(name: string) {
|
||||
return Infinity;
|
||||
return this.getByLabel(name)?.cost ?? 10;
|
||||
}
|
||||
|
||||
getUnkownUnit(name: string): UnitBlueprint {
|
||||
|
||||
@ -69,7 +69,7 @@ import {
|
||||
faXmarksLines,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { Carrier } from "../mission/carrier";
|
||||
import { ContactsUpdatedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, UnitDeadEvent, UnitDeselectedEvent, UnitSelectedEvent } from "../events";
|
||||
import { ContactsUpdatedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, UnitDeadEvent, UnitDeselectedEvent, UnitSelectedEvent, UnitUpdatedEvent } from "../events";
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: "/vite/images/markers/marker-icon.png",
|
||||
@ -618,6 +618,8 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnitUpdatedEvent.dispatch(this);
|
||||
}
|
||||
|
||||
/** Get unit data collated into an object
|
||||
|
||||
@ -1422,8 +1422,7 @@ export class UnitsManager {
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return 0;
|
||||
// TODO return points + this.#unitIndexedDB.selectBlueprints({from:"Units", where: {name: unit.unitType}});
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType)
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "helicopter") {
|
||||
@ -1432,8 +1431,7 @@ export class UnitsManager {
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return 0;
|
||||
//TODO return points + helicopterDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType)
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "groundunit") {
|
||||
@ -1442,8 +1440,7 @@ export class UnitsManager {
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return 0;
|
||||
//TODOreturn points + groundUnitDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType)
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
} else if (category === "navyunit") {
|
||||
@ -1452,8 +1449,7 @@ export class UnitsManager {
|
||||
return false;
|
||||
}
|
||||
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {
|
||||
return 0;
|
||||
//TODOreturn points + navyUnitDatabase.getSpawnPointsByName(unit.unitType);
|
||||
return points + this.getDatabase().getSpawnPointsByName(unit.unitType)
|
||||
}, 0);
|
||||
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user