feat: Created coalitionareamanager, fixed areas creation and management

This commit is contained in:
Davide Passoni
2024-12-18 17:50:11 +01:00
parent 416f0d3e36
commit ffeecfbf9e
9 changed files with 364 additions and 166 deletions

View File

@@ -229,6 +229,19 @@ export class CoalitionAreaSelectedEvent {
} }
} }
export class CoalitionAreasChangedEvent {
static on(callback: (coalitionAreas: (CoalitionCircle | CoalitionPolygon)[]) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.coalitionAreas);
});
}
static dispatch(coalitionAreas: (CoalitionCircle | CoalitionPolygon)[]) {
document.dispatchEvent(new CustomEvent(this.name, { detail: { coalitionAreas } }));
console.log(`Event ${this.name} dispatched`);
}
}
export class AirbaseSelectedEvent { export class AirbaseSelectedEvent {
static on(callback: (airbase: Airbase | null) => void) { static on(callback: (airbase: Airbase | null) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => { document.addEventListener(this.name, (ev: CustomEventInit) => {

View File

@@ -0,0 +1,121 @@
import { LatLng, LeafletMouseEvent } from "leaflet";
import { DrawSubState, OlympusState } from "../../constants/constants";
import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent } from "../../events";
import { getApp } from "../../olympusapp";
import { areaContains } from "../../other/utils";
import { CoalitionCircle } from "./coalitioncircle";
import { CoalitionPolygon } from "./coalitionpolygon";
export class CoalitionAreasManager {
/* Coalition areas drawing */
#areas: (CoalitionPolygon | CoalitionCircle)[] = [];
#selectedArea: CoalitionCircle | CoalitionPolygon | null = null;
constructor() {
AppStateChangedEvent.on((state, subState) => {
/* State changes can't happen inside a AppStateChanged handler. Use a timeout */
window.setTimeout(() => {
this.getSelectedArea()?.setCreating(false);
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState === DrawSubState.NO_SUBSTATE)) {
this.setSelectedArea(null);
} else {
/* If we are editing but no area is selected, revert to no substate */
if (subState === DrawSubState.EDIT && this.getSelectedArea() === null) getApp().setState(OlympusState.DRAW);
else {
/* Handle creation of new area */
let newArea: CoalitionCircle | CoalitionPolygon | null = null;
if (subState == DrawSubState.DRAW_POLYGON) newArea = new CoalitionPolygon([]);
else if (subState === DrawSubState.DRAW_CIRCLE) newArea = new CoalitionCircle(new LatLng(0, 0), { radius: 1000 });
if (newArea) {
this.setSelectedArea(newArea);
this.#areas.push(newArea);
CoalitionAreasChangedEvent.dispatch(this.#areas);
}
}
}
}, 200);
});
}
setSelectedArea(area: CoalitionCircle | CoalitionPolygon | null) {
this.#selectedArea?.setSelected(this.#selectedArea === area);
area?.setSelected(true);
this.#selectedArea = area;
CoalitionAreaSelectedEvent.dispatch(area);
}
deleteCoalitionArea(area: CoalitionPolygon | CoalitionCircle) {
if (!this.#areas.includes(area)) return;
if (area === this.getSelectedArea()) this.setSelectedArea(null);
this.#areas.splice(this.#areas.indexOf(area), 1);
if (getApp().getMap().hasLayer(area)) getApp().getMap().removeLayer(area);
CoalitionAreasChangedEvent.dispatch(this.#areas);
}
moveAreaUp(area: CoalitionPolygon | CoalitionCircle) {
if (!this.#areas.includes(area)) return;
const idx = this.#areas.indexOf(area);
if (idx === 0) return;
this.#areas.forEach((coalitionArea) => getApp().getMap().removeLayer(coalitionArea));
this.#areas.splice(this.#areas.indexOf(area), 1);
this.#areas = [...this.#areas.slice(0, idx - 1), area, ...this.#areas.slice(idx - 1)];
this.#areas.forEach((coalitionArea) => getApp().getMap().addLayer(coalitionArea));
CoalitionAreasChangedEvent.dispatch(this.#areas);
}
moveCoalitionAreaDown(area: CoalitionPolygon | CoalitionCircle) {
if (!this.#areas.includes(area)) return;
const idx = this.#areas.indexOf(area);
if (idx === this.#areas.length - 1) return;
this.#areas.forEach((coalitionArea) => getApp().getMap().removeLayer(coalitionArea));
this.#areas.splice(this.#areas.indexOf(area), 1);
this.#areas = [...this.#areas.slice(0, idx + 1), area, ...this.#areas.slice(idx + 1)];
this.#areas.forEach((coalitionArea) => getApp().getMap().addLayer(coalitionArea));
CoalitionAreasChangedEvent.dispatch(this.#areas);
}
getSelectedArea() {
return this.#areas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => coalitionArea.getSelected()) ?? null;
}
onLeftShortClick(e: LeafletMouseEvent) {
const selectedArea = this.getSelectedArea();
if (getApp().getSubState() === DrawSubState.DRAW_POLYGON) {
if (selectedArea && selectedArea instanceof CoalitionPolygon) selectedArea.addTemporaryLatLng(e.latlng);
} else if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE) {
if (selectedArea && selectedArea instanceof CoalitionCircle) {
if (selectedArea.getLatLng().lat == 0 && selectedArea.getLatLng().lng == 0) selectedArea.setLatLng(e.latlng);
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
}
} else {
let wasAreaSelected = this.getSelectedArea() !== null;
this.setSelectedArea(null);
for (let idx = 0; idx < this.#areas.length; idx++) {
if (areaContains(e.latlng, this.#areas[idx])) {
this.setSelectedArea(this.#areas[idx]);
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
break;
}
}
if (this.getSelectedArea() === null) getApp().setState(wasAreaSelected ? OlympusState.DRAW : OlympusState.IDLE);
}
}
onDoubleClick(e: LeafletMouseEvent) {
if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE || getApp().getSubState() === DrawSubState.DRAW_POLYGON)
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
else getApp().setState(OlympusState.DRAW);
}
}

View File

@@ -1,9 +1,20 @@
import { DivIcon, LatLng } from "leaflet"; import { DivIcon, DomEvent, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker"; import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker { export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) { constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: false }); super(latlng, { interactive: true, draggable: false });
this.on("mousedown", (e: any) => {
DomEvent.stop(e);
DomEvent.preventDefault(e);
e.originalEvent.stopImmediatePropagation();
});
this.on("mouseup", (e: any) => {
DomEvent.stop(e);
DomEvent.preventDefault(e);
e.originalEvent.stopImmediatePropagation();
});
} }
createIcon() { createIcon() {

View File

@@ -11,7 +11,7 @@ let totalCircles = 0;
export class CoalitionCircle extends Circle { export class CoalitionCircle extends Circle {
#coalition: Coalition = "blue"; #coalition: Coalition = "blue";
#selected: boolean = true; #selected: boolean = true;
#editing: boolean = true; #creating: boolean = true;
#radiusHandle: CoalitionAreaHandle; #radiusHandle: CoalitionAreaHandle;
#labelText: string; #labelText: string;
#label: Marker; #label: Marker;
@@ -39,9 +39,7 @@ export class CoalitionCircle extends Circle {
this.#drawLabel(); this.#drawLabel();
}); });
this.on("remove", () => { getApp().getMap().addLayer(this);
this.#label.removeFrom(this._map);
});
} }
setCoalition(coalition: Coalition) { setCoalition(coalition: Coalition) {
@@ -70,13 +68,16 @@ export class CoalitionCircle extends Circle {
return this.#selected; return this.#selected;
} }
setEditing(editing: boolean) { setCreating(creating: boolean) {
this.#editing = editing; this.#creating = creating;
this.#setRadiusHandle(); this.#setRadiusHandle();
/* Remove areas with less than 2 vertexes */
if (this.getLatLng().lat === 0 && this.getLatLng().lng === 0) getApp().getCoalitionAreasManager().deleteCoalitionArea(this);
} }
getEditing() { getCreating() {
return this.#editing; return this.#creating;
} }
setOpacity(opacity: number) { setOpacity(opacity: number) {
@@ -92,9 +93,16 @@ export class CoalitionCircle extends Circle {
this.#drawLabel(); this.#drawLabel();
} }
onAdd(map: Map): this {
super.onAdd(map);
this.#drawLabel();
return this;
}
onRemove(map: Map): this { onRemove(map: Map): this {
super.onRemove(map); super.onRemove(map);
this.#radiusHandle.removeFrom(getApp().getMap()); this.#label?.removeFrom(map);
this.#radiusHandle.removeFrom(map);
return this; return this;
} }
@@ -130,9 +138,8 @@ export class CoalitionCircle extends Circle {
} }
#drawLabel() { #drawLabel() {
if (this.#label) { this.#label?.removeFrom(this._map);
this.#label.removeFrom(this._map);
}
this.#label = new Marker(this.getLatLng(), { this.#label = new Marker(this.getLatLng(), {
icon: new DivIcon({ icon: new DivIcon({
className: "label", className: "label",

View File

@@ -12,7 +12,7 @@ let totalPolygons = 0;
export class CoalitionPolygon extends Polygon { export class CoalitionPolygon extends Polygon {
#coalition: Coalition = "blue"; #coalition: Coalition = "blue";
#selected: boolean = true; #selected: boolean = true;
#editing: boolean = true; #creating: boolean = true;
#handles: CoalitionAreaHandle[] = []; #handles: CoalitionAreaHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = []; #middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0; #activeIndex: number = 0;
@@ -43,9 +43,7 @@ export class CoalitionPolygon extends Polygon {
this.#drawLabel(); this.#drawLabel();
}); });
this.on("remove", () => { getApp().getMap().addLayer(this);
this.#label?.removeFrom(this._map);
});
} }
setCoalition(coalition: Coalition) { setCoalition(coalition: Coalition) {
@@ -63,12 +61,12 @@ export class CoalitionPolygon extends Polygon {
this.#setHandles(); this.#setHandles();
this.#drawLabel(); this.#drawLabel();
this.setOpacity(selected ? 1 : 0.5); this.setOpacity(selected ? 1 : 0.5);
if (!this.getSelected() && this.getEditing()) { if (!this.getSelected() && this.getCreating()) {
/* Remove the vertex we were working on */ /* Remove the vertex we were working on */
var latlngs = this.getLatLngs()[0] as LatLng[]; var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 1); latlngs.splice(this.#activeIndex, 1);
this.setLatLngs(latlngs); this.setLatLngs(latlngs);
this.setEditing(false); this.setCreating(false);
} }
if (selected) CoalitionAreaSelectedEvent.dispatch(this); if (selected) CoalitionAreaSelectedEvent.dispatch(this);
@@ -81,17 +79,17 @@ export class CoalitionPolygon extends Polygon {
return this.#selected; return this.#selected;
} }
setEditing(editing: boolean) { setCreating(creating: boolean) {
this.#editing = editing; this.#creating = creating;
this.#setHandles(); this.#setHandles();
var latlngs = this.getLatLngs()[0] as LatLng[]; var latlngs = this.getLatLngs()[0] as LatLng[];
/* Remove areas with less than 2 vertexes */ /* Remove areas with less than 2 vertexes */
if (latlngs.length <= 2) getApp().getMap().deleteCoalitionArea(this); if (latlngs.length <= 2) getApp().getCoalitionAreasManager().deleteCoalitionArea(this);
} }
getEditing() { getCreating() {
return this.#editing; return this.#creating;
} }
addTemporaryLatLng(latlng: LatLng) { addTemporaryLatLng(latlng: LatLng) {
@@ -115,9 +113,16 @@ export class CoalitionPolygon extends Polygon {
this.#drawLabel(); this.#drawLabel();
} }
onAdd(map: Map): this {
super.onAdd(map);
this.#drawLabel();
return this;
}
onRemove(map: Map): this { onRemove(map: Map): this {
super.onRemove(map); super.onRemove(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap())); this.#label?.removeFrom(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(map));
return this; return this;
} }
@@ -176,7 +181,6 @@ export class CoalitionPolygon extends Polygon {
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng); const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
middleHandle.addTo(getApp().getMap()); middleHandle.addTo(getApp().getMap());
middleHandle.on("click", (e: any) => { middleHandle.on("click", (e: any) => {
getApp().getMap().preventClicks();
this.#activeIndex = idx - 1; this.#activeIndex = idx - 1;
this.addTemporaryLatLng(middleLatLng); this.addTemporaryLatLng(middleLatLng);
}); });

View File

@@ -17,7 +17,6 @@ import {
OlympusSubState, OlympusSubState,
NO_SUBSTATE, NO_SUBSTATE,
SpawnSubState, SpawnSubState,
DrawSubState,
JTACSubState, JTACSubState,
UnitControlSubState, UnitControlSubState,
ContextActionTarget, ContextActionTarget,
@@ -26,7 +25,6 @@ import {
SHORT_PRESS_MILLISECONDS, SHORT_PRESS_MILLISECONDS,
DEBOUNCE_MILLISECONDS, DEBOUNCE_MILLISECONDS,
} from "../constants/constants"; } from "../constants/constants";
import { CoalitionPolygon } from "./coalitionarea/coalitionpolygon";
import { MapHiddenTypes, MapOptions } from "../types/types"; import { MapHiddenTypes, MapOptions } from "../types/types";
import { EffectRequestTable, OlympusConfig, SpawnRequestTable } from "../interfaces"; import { EffectRequestTable, OlympusConfig, SpawnRequestTable } from "../interfaces";
import { ContextAction } from "../unit/contextaction"; import { ContextAction } from "../unit/contextaction";
@@ -36,7 +34,6 @@ import "./markers/stylesheets/airbase.css";
import "./markers/stylesheets/bullseye.css"; import "./markers/stylesheets/bullseye.css";
import "./markers/stylesheets/units.css"; import "./markers/stylesheets/units.css";
import "./stylesheets/map.css"; import "./stylesheets/map.css";
import { CoalitionCircle } from "./coalitionarea/coalitioncircle";
import { initDraggablePath } from "./coalitionarea/draggablepath"; import { initDraggablePath } from "./coalitionarea/draggablepath";
import { ExplosionMarker } from "./markers/explosionmarker"; import { ExplosionMarker } from "./markers/explosionmarker";
@@ -44,7 +41,6 @@ import { TextMarker } from "./markers/textmarker";
import { TargetMarker } from "./markers/targetmarker"; import { TargetMarker } from "./markers/targetmarker";
import { import {
AppStateChangedEvent, AppStateChangedEvent,
CoalitionAreaSelectedEvent,
ConfigLoadedEvent, ConfigLoadedEvent,
ContextActionChangedEvent, ContextActionChangedEvent,
ContextActionSetChangedEvent, ContextActionSetChangedEvent,
@@ -128,9 +124,6 @@ export class Map extends L.Map {
#bradcastPositionXmlHttp: XMLHttpRequest | null = null; #bradcastPositionXmlHttp: XMLHttpRequest | null = null;
#cameraZoomRatio: number = 1.0; #cameraZoomRatio: number = 1.0;
/* Coalition areas drawing */
#coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = [];
/* Units movement */ /* Units movement */
#destinationPreviewMarkers: { [key: number]: TemporaryUnitMarker | TargetMarker } = {}; #destinationPreviewMarkers: { [key: number]: TemporaryUnitMarker | TargetMarker } = {};
#destinationRotation: number = 0; #destinationRotation: number = 0;
@@ -300,7 +293,7 @@ export class Map extends L.Map {
window.addEventListener("blur", () => { window.addEventListener("blur", () => {
this.setSelectionEnabled(false); this.setSelectionEnabled(false);
this.setPasteEnabled(false); this.setPasteEnabled(false);
}) });
/* Pan interval */ /* Pan interval */
this.#panInterval = window.setInterval(() => { this.#panInterval = window.setInterval(() => {
@@ -604,27 +597,6 @@ export class Map extends L.Map {
ContextActionChangedEvent.dispatch(this.#contextAction); ContextActionChangedEvent.dispatch(this.#contextAction);
} }
deselectAllCoalitionAreas() {
if (this.getSelectedCoalitionArea() !== null) {
CoalitionAreaSelectedEvent.dispatch(null);
this.#coalitionAreas.forEach((coalitionArea: CoalitionPolygon | CoalitionCircle) => coalitionArea.setSelected(false));
}
}
deleteCoalitionArea(coalitionArea: CoalitionPolygon | CoalitionCircle) {
if (this.#coalitionAreas.includes(coalitionArea)) this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1);
if (this.hasLayer(coalitionArea)) this.removeLayer(coalitionArea);
}
getSelectedCoalitionArea() {
const coalitionArea = this.#coalitionAreas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => {
return coalitionArea.getSelected();
});
return coalitionArea ?? null;
}
setHiddenType(key: string, value: boolean) { setHiddenType(key: string, value: boolean) {
this.#hiddenTypes[key] = value; this.#hiddenTypes[key] = value;
HiddenTypesChangedEvent.dispatch(this.#hiddenTypes); HiddenTypesChangedEvent.dispatch(this.#hiddenTypes);
@@ -765,7 +737,7 @@ export class Map extends L.Map {
setSelectionEnabled(selectionEnabled: boolean) { setSelectionEnabled(selectionEnabled: boolean) {
this.#selectionEnabled = selectionEnabled; this.#selectionEnabled = selectionEnabled;
SelectionEnabledChangedEvent.dispatch(selectionEnabled) SelectionEnabledChangedEvent.dispatch(selectionEnabled);
} }
getSelectionEnabled() { getSelectionEnabled() {
@@ -774,7 +746,7 @@ export class Map extends L.Map {
setPasteEnabled(pasteEnabled: boolean) { setPasteEnabled(pasteEnabled: boolean) {
this.#pasteEnabled = pasteEnabled; this.#pasteEnabled = pasteEnabled;
PasteEnabledChangedEvent.dispatch(pasteEnabled) PasteEnabledChangedEvent.dispatch(pasteEnabled);
} }
getPasteEnabled() { getPasteEnabled() {
@@ -822,18 +794,16 @@ export class Map extends L.Map {
/* Event handlers */ /* Event handlers */
#onStateChanged(state: OlympusState, subState: OlympusSubState) { #onStateChanged(state: OlympusState, subState: OlympusSubState) {
/* Operations to perform when leaving a state */ /* Operations to perform when leaving a state */
this.getSelectedCoalitionArea()?.setEditing(false);
this.#currentSpawnMarker?.removeFrom(this); this.#currentSpawnMarker?.removeFrom(this);
this.#currentSpawnMarker = null; this.#currentSpawnMarker = null;
this.#currentEffectMarker?.removeFrom(this); this.#currentEffectMarker?.removeFrom(this);
this.#currentEffectMarker = null; this.#currentEffectMarker = null;
if (state !== OlympusState.UNIT_CONTROL) { if (state !== OlympusState.UNIT_CONTROL) {
getApp().getUnitsManager().deselectAllUnits(); getApp().getUnitsManager().deselectAllUnits();
this.setContextAction(null); this.setContextAction(null);
this.setContextActionSet(null); this.setContextActionSet(null);
} }
if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas();
this.getContainer().classList.remove(`explosion-cursor`); this.getContainer().classList.remove(`explosion-cursor`);
["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`)); ["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`));
@@ -862,17 +832,7 @@ export class Map extends L.Map {
} else if (state === OlympusState.UNIT_CONTROL) { } else if (state === OlympusState.UNIT_CONTROL) {
console.log(`Context action:`); console.log(`Context action:`);
console.log(this.#contextAction); console.log(this.#contextAction);
} else if (state === OlympusState.DRAW) { }
if (subState == DrawSubState.DRAW_POLYGON) {
this.#coalitionAreas.push(new CoalitionPolygon([]));
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
} else if (subState === DrawSubState.DRAW_CIRCLE) {
this.#coalitionAreas.push(new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 }));
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
}
}
} }
#onDragStart(e: any) { #onDragStart(e: any) {
@@ -935,7 +895,7 @@ export class Map extends L.Map {
console.log(`Left short click at ${e.latlng}`); console.log(`Left short click at ${e.latlng}`);
if (this.#pasteEnabled) { if (this.#pasteEnabled) {
getApp().getUnitsManager().paste(e.latlng) getApp().getUnitsManager().paste(e.latlng);
} }
/* Execute the short click action */ /* Execute the short click action */
@@ -982,27 +942,7 @@ export class Map extends L.Map {
} }
} }
} else if (getApp().getState() === OlympusState.DRAW) { } else if (getApp().getState() === OlympusState.DRAW) {
if (getApp().getSubState() === DrawSubState.DRAW_POLYGON) { getApp().getCoalitionAreasManager().onLeftShortClick(e);
const selectedArea = this.getSelectedCoalitionArea();
if (selectedArea && selectedArea instanceof CoalitionPolygon) {
selectedArea.addTemporaryLatLng(e.latlng);
}
} else if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE) {
const selectedArea = this.getSelectedCoalitionArea();
if (selectedArea && selectedArea instanceof CoalitionCircle) {
if (selectedArea.getLatLng().lat == 0 && selectedArea.getLatLng().lng == 0) selectedArea.setLatLng(e.latlng);
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
}
} else if (getApp().getSubState() == DrawSubState.NO_SUBSTATE) {
this.deselectAllCoalitionAreas();
for (let idx = 0; idx < this.#coalitionAreas.length; idx++) {
if (areaContains(e.latlng, this.#coalitionAreas[idx])) {
this.#coalitionAreas[idx].setSelected(true);
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
break;
}
}
}
} else if (getApp().getState() === OlympusState.JTAC) { } else if (getApp().getState() === OlympusState.JTAC) {
// TODO less redundant way to do this // TODO less redundant way to do this
if (getApp().getSubState() === JTACSubState.SELECT_TARGET) { if (getApp().getSubState() === JTACSubState.SELECT_TARGET) {
@@ -1097,8 +1037,12 @@ export class Map extends L.Map {
this.setPasteEnabled(false); this.setPasteEnabled(false);
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE); if (getApp().getState() === OlympusState.DRAW) {
else getApp().setState(getApp().getState()); getApp().getCoalitionAreasManager().onDoubleClick(e);
} else {
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
else getApp().setState(getApp().getState());
}
} }
#onMouseMove(e: any) { #onMouseMove(e: any) {

View File

@@ -26,6 +26,7 @@ import { OlympusConfig } from "./interfaces";
import { SessionDataManager } from "./sessiondata"; import { SessionDataManager } from "./sessiondata";
import { ControllerManager } from "./controllers/controllermanager"; import { ControllerManager } from "./controllers/controllermanager";
import { AWACSController } from "./controllers/awacs"; import { AWACSController } from "./controllers/awacs";
import { CoalitionAreasManager } from "./map/coalitionarea/coalitionareamanager";
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}"; export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
export var IP = window.location.toString(); export var IP = window.location.toString();
@@ -50,6 +51,7 @@ export class OlympusApp {
#audioManager: AudioManager; #audioManager: AudioManager;
#sessionDataManager: SessionDataManager; #sessionDataManager: SessionDataManager;
#controllerManager: ControllerManager; #controllerManager: ControllerManager;
#coalitionAreasManager: CoalitionAreasManager;
//#pluginsManager: // TODO //#pluginsManager: // TODO
constructor() { constructor() {
@@ -98,6 +100,10 @@ export class OlympusApp {
return this.#controllerManager; return this.#controllerManager;
} }
getCoalitionAreasManager() {
return this.#coalitionAreasManager;
}
/* TODO /* TODO
getPluginsManager() { getPluginsManager() {
return null // this.#pluginsManager as PluginsManager; return null // this.#pluginsManager as PluginsManager;
@@ -117,6 +123,7 @@ export class OlympusApp {
this.#weaponsManager = new WeaponsManager(); this.#weaponsManager = new WeaponsManager();
this.#audioManager = new AudioManager(); this.#audioManager = new AudioManager();
this.#controllerManager = new ControllerManager(); this.#controllerManager = new ControllerManager();
this.#coalitionAreasManager = new CoalitionAreasManager();
/* Check if we are running the latest version */ /* Check if we are running the latest version */
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json"); const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");

View File

@@ -1,9 +1,8 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { faFighterJet, faHandPointer, faJetFighter, faMap, IconDefinition } from "@fortawesome/free-solid-svg-icons"; import { faFighterJet, faHandPointer, faJetFighter, faMap, IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants"; import { DrawSubState, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events"; import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events";
import { ContextAction } from "../../unit/contextaction";
import { ContextActionSet } from "../../unit/contextactionset"; import { ContextActionSet } from "../../unit/contextactionset";
import { MapToolBar } from "./maptoolbar"; import { MapToolBar } from "./maptoolbar";
@@ -23,6 +22,8 @@ export function ControlsPanel(props: {}) {
const [shortcuts, setShortcuts] = useState({}); const [shortcuts, setShortcuts] = useState({});
const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet); const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet);
// TODO change constant references of "Shift with actual keybind"
useEffect(() => { useEffect(() => {
AppStateChangedEvent.on((state, subState) => { AppStateChangedEvent.on((state, subState) => {
setAppState(state); setAppState(state);
@@ -100,7 +101,7 @@ export function ControlsPanel(props: {}) {
} else if (appState === OlympusState.SPAWN) { } else if (appState === OlympusState.SPAWN) {
controls = [ controls = [
{ {
actions: [touch ? faHandPointer : "LMB", 2], actions: [touch ? faHandPointer : "LMB"],
text: appSubState === SpawnSubState.NO_SUBSTATE ? "Close spawn menu" : "Return to spawn menu", text: appSubState === SpawnSubState.NO_SUBSTATE ? "Close spawn menu" : "Return to spawn menu",
}, },
{ {
@@ -123,6 +124,64 @@ export function ControlsPanel(props: {}) {
text: "Spawn effect", text: "Spawn effect",
}); });
} }
} else if (appState === OlympusState.DRAW) {
controls = [
{
actions: [touch ? faHandPointer : "LMB", "Drag"],
text: "Move map location",
},
];
if (appSubState === DrawSubState.NO_SUBSTATE) {
controls.unshift({
actions: [touch ? faHandPointer : "LMB"],
text: "Close draw menu",
});
controls.unshift({
actions: [touch ? faHandPointer : "LMB"],
text: "Select drawing",
});
}
if (appSubState === DrawSubState.DRAW_CIRCLE) {
controls.unshift({
actions: [touch ? faHandPointer : "LMB"],
text: "Add new circle",
});
controls.unshift({
actions: [touch ? faHandPointer : "LMB", "Drag"],
text: "Drag circle",
});
}
if (appSubState === DrawSubState.DRAW_POLYGON) {
controls.unshift({
actions: [touch ? faHandPointer : "LMB"],
text: "Add point to polygon",
});
controls.unshift({
actions: [touch ? faHandPointer : "LMB", "Drag"],
text: "Drag polygon",
});
}
if (appSubState === DrawSubState.EDIT) {
controls.unshift({
actions: [touch ? faHandPointer : "LMB"],
text: "Add/drag point",
});
controls.unshift({
actions: [touch ? faHandPointer : "LMB", "Drag"],
text: "Drag drawing",
});
}
if (appSubState !== DrawSubState.NO_SUBSTATE) {
controls.push({
actions: [touch ? faHandPointer : "LMB"],
text: "Deselect drawing",
});
}
} else { } else {
controls = baseControls; controls = baseControls;
controls.push({ controls.push({
@@ -167,14 +226,9 @@ export function ControlsPanel(props: {}) {
return ( return (
<div key={idx} className="flex gap-1"> <div key={idx} className="flex gap-1">
<div> <div>
{typeof action === "string" || typeof action === "number" ? ( {typeof action === "string" || typeof action === "number" ? action : <FontAwesomeIcon icon={action} className={`
action my-auto ml-auto
) : ( `} />}
<FontAwesomeIcon
icon={action}
className={`my-auto ml-auto`}
/>
)}
</div> </div>
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>} {idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>} {idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Menu } from "./components/menu"; import { Menu } from "./components/menu";
import { FaTrash } from "react-icons/fa"; import { FaArrowDown, FaArrowUp, FaTrash } from "react-icons/fa";
import { getApp } from "../../olympusapp"; import { getApp } from "../../olympusapp";
import { OlStateButton } from "../components/olstatebutton"; import { OlStateButton } from "../components/olstatebutton";
import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons"; import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
@@ -13,14 +13,14 @@ import { Coalition } from "../../types/types";
import { OlRangeSlider } from "../components/olrangeslider"; import { OlRangeSlider } from "../components/olrangeslider";
import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle"; import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle";
import { DrawSubState, ERAS_ORDER, IADSTypes, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants"; import { DrawSubState, ERAS_ORDER, IADSTypes, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
import { AppStateChangedEvent, CoalitionAreaSelectedEvent } from "../../events"; import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent } from "../../events";
import { UnitBlueprint } from "../../interfaces"; import { FaXmark } from "react-icons/fa6";
export function DrawingMenu(props: { open: boolean; onClose: () => void }) { export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState); const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle); const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle);
const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition); const [coalitionAreas, setCoalitionAreas] = useState([] as (CoalitionPolygon | CoalitionCircle)[]);
const [IADSDensity, setIADSDensity] = useState(50); const [IADSDensity, setIADSDensity] = useState(50);
const [IADSDistribution, setIADSDistribution] = useState(50); const [IADSDistribution, setIADSDistribution] = useState(50);
const [forceCoalitionAppropriateUnits, setForceCoalitionApproriateUnits] = useState(false); const [forceCoalitionAppropriateUnits, setForceCoalitionApproriateUnits] = useState(false);
@@ -38,27 +38,15 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
/* Get all the unique types and eras for groundunits */ /* Get all the unique types and eras for groundunits */
let types = IADSTypes; let types = IADSTypes;
let eras = getApp()?.getUnitsManager().getDatabase().getEras().sort((era1, era2) => ERAS_ORDER[era1] > ERAS_ORDER[era2] ? 1: -1 ); let eras = getApp()
?.getUnitsManager()
useEffect(() => { .getDatabase()
if (getApp()) { .getEras()
// TODO .sort((era1, era2) => (ERAS_ORDER[era1] > ERAS_ORDER[era2] ? 1 : -1));
///* If we are not in polygon drawing mode, force the draw polygon button off */
//if (drawingPolygon && getApp().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false);
//
///* If we are not in circle drawing mode, force the draw circle button off */
//if (drawingCircle && getApp().getState() !== COALITIONAREA_DRAW_CIRCLE) setDrawingCircle(false);
//
///* If we are not in any drawing mode, force the map in edit mode */
//if (props.open && !drawingPolygon && !drawingCircle) getApp().getMap().setState(COALITIONAREA_EDIT);
//
///* Align the state of the coalition toggle to the coalition of the area */
//if (activeCoalitionArea && activeCoalitionArea?.getCoalition() !== areaCoalition) setAreaCoalition(activeCoalitionArea?.getCoalition());
}
});
useEffect(() => { useEffect(() => {
CoalitionAreaSelectedEvent.on((coalitionArea) => setActiveCoalitionArea(coalitionArea)); CoalitionAreaSelectedEvent.on((coalitionArea) => setActiveCoalitionArea(coalitionArea));
CoalitionAreasChangedEvent.on((coalitionAreas) => setCoalitionAreas([...coalitionAreas]));
}, []); }, []);
return ( return (
@@ -67,43 +55,87 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
title="Draw" title="Draw"
onClose={props.onClose} onClose={props.onClose}
canBeHidden={true} canBeHidden={true}
showBackButton={activeCoalitionArea !== null} showBackButton={appSubState !== DrawSubState.NO_SUBSTATE}
onBack={() => { onBack={() => {
getApp().getCoalitionAreasManager().setSelectedArea(null);
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE); getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE);
}} }}
> >
<> <>
{appState === OlympusState.DRAW && appSubState !== DrawSubState.EDIT && ( {appState === OlympusState.DRAW && appSubState === DrawSubState.NO_SUBSTATE && (
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400"> <div className="flex flex-col gap-2 text-sm text-gray-400">
<OlStateButton <div className="flex flex-col bg-olympus-200/30">
className="!w-full" {coalitionAreas.map((coalitionArea) => {
icon={faDrawPolygon} return (
tooltip={"Add a new polygon"} <div
checked={appSubState === DrawSubState.DRAW_POLYGON} data-coalition={coalitionArea.getCoalition()}
onClick={() => { className={`
if (appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); flex cursor-pointer content-center border-l-4 px-4
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON); text-base text-white
}} data-[coalition="blue"]:border-blue-500
> data-[coalition="neutral"]:border-gray-500
<div className="text-sm">Add polygon</div> data-[coalition="red"]:border-red-500
</OlStateButton> hover:bg-white/10
<OlStateButton `}
className="!w-full" onClick={() => {
icon={faCircle} coalitionArea.setSelected(true);
tooltip={"Add a new circle"} getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
checked={appSubState === DrawSubState.DRAW_CIRCLE} }}
onClick={() => { >
if (appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW); <div className="py-3">{coalitionArea.getLabelText()}</div>
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE); <FaArrowUp
}} onClick={(ev) => {
> ev.stopPropagation();
<div className="text-sm">Add circle</div> getApp().getCoalitionAreasManager().moveAreaUp(coalitionArea);
</OlStateButton> }}
className={`
my-auto ml-auto rounded-md p-2 text-3xl
hover:bg-white/10
`}
/>
<FaArrowDown
onClick={(ev) => {
ev.stopPropagation();
getApp().getCoalitionAreasManager().moveCoalitionAreaDown(coalitionArea);
}}
className={`
my-auto rounded-md p-2 text-3xl
hover:bg-white/10
`}
/>
<FaXmark
onClick={(ev) => {
ev.stopPropagation();
getApp().getCoalitionAreasManager().deleteCoalitionArea(coalitionArea);
}}
className={`
my-auto rounded-md p-2 text-3xl
hover:bg-red-500/50
`}
/>
</div>
);
})}
</div>
<div className="flex flex-col gap-2 p-6">
<OlStateButton
className="!w-full"
icon={faDrawPolygon}
checked={false}
onClick={() => getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON)}
>
<div className="text-sm">Add polygon</div>
</OlStateButton>
<OlStateButton className="!w-full" icon={faCircle} checked={false} onClick={() => getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE)}>
<div className="text-sm">Add circle</div>
</OlStateButton>
</div>
</div> </div>
)} )}
</> </>
<div> <div>
{activeCoalitionArea !== null && appSubState === DrawSubState.EDIT && ( {activeCoalitionArea !== null && (
<div className={`flex flex-col gap-4 py-4`}> <div className={`flex flex-col gap-4 py-4`}>
<div <div
className={` className={`
@@ -114,9 +146,13 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
<div className="my-auto flex justify-between text-md"> <div className="my-auto flex justify-between text-md">
Area label Area label
<div <div
className="rounded-md bg-red-800 p-2" className={`
cursor-pointer rounded-md bg-red-600 p-2
hover:bg-red-400
`}
onClick={() => { onClick={() => {
getApp().getMap().deleteCoalitionArea(activeCoalitionArea); getApp().getCoalitionAreasManager().deleteCoalitionArea(activeCoalitionArea);
getApp().setState(OlympusState.DRAW);
setActiveCoalitionArea(null); setActiveCoalitionArea(null);
}} }}
> >
@@ -144,13 +180,12 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
> >
<div className="my-auto text-md">Coalition: </div> <div className="my-auto text-md">Coalition: </div>
<OlCoalitionToggle <OlCoalitionToggle
coalition={areaCoalition} coalition={activeCoalitionArea.getCoalition()}
onClick={() => { onClick={() => {
let newCoalition = ""; let newCoalition = "";
if (areaCoalition === "blue") newCoalition = "neutral"; if (activeCoalitionArea.getCoalition() === "blue") newCoalition = "neutral";
else if (areaCoalition === "neutral") newCoalition = "red"; else if (activeCoalitionArea.getCoalition() === "neutral") newCoalition = "red";
else if (areaCoalition === "red") newCoalition = "blue"; else if (activeCoalitionArea.getCoalition() === "red") newCoalition = "blue";
setAreaCoalition(newCoalition as Coalition);
activeCoalitionArea.setCoalition(newCoalition as Coalition); activeCoalitionArea.setCoalition(newCoalition as Coalition);
}} }}
></OlCoalitionToggle> ></OlCoalitionToggle>
@@ -161,9 +196,11 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
bg-olympus-600 p-5 bg-olympus-600 p-5
`} `}
> >
<div className={` <div
border-b-2 border-b-olympus-100 pb-4 text-gray-300 className={`border-b-2 border-b-olympus-100 pb-4 text-gray-300`}
`}>Automatic IADS generation</div> >
Automatic IADS generation
</div>
<OlDropdown className="" label="Units types"> <OlDropdown className="" label="Units types">
{types.map((type, idx) => { {types.map((type, idx) => {
if (!(type in typesSelection)) { if (!(type in typesSelection)) {