mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Started adding controls panel for shortcuts, more work on better polygon creation, added circles creation
This commit is contained in:
parent
1e72f15876
commit
86e0e6b3a3
@ -22,6 +22,7 @@
|
||||
"js-sha256": "^0.11.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-control-mini-map": "^0.4.0",
|
||||
"leaflet-path-drag": "^1.9.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.0.1",
|
||||
|
||||
@ -260,6 +260,7 @@ export const IDLE = "Idle";
|
||||
export const SPAWN_UNIT = "Spawn unit";
|
||||
export const CONTEXT_ACTION = "Context action";
|
||||
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area polygon";
|
||||
export const COALITIONAREA_DRAW_CIRCLE = "Draw Coalition Area circle";
|
||||
export const COALITIONAREA_EDIT = "Edit Coalition Area";
|
||||
|
||||
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
|
||||
|
||||
4
frontend/react/src/dom.d.ts
vendored
4
frontend/react/src/dom.d.ts
vendored
@ -1,4 +1,5 @@
|
||||
import { ServerStatus } from "./interfaces";
|
||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||
import { Unit } from "./unit/unit";
|
||||
|
||||
interface CustomEventMap {
|
||||
@ -20,7 +21,8 @@ interface CustomEventMap {
|
||||
contactsUpdated: CustomEvent<Unit>;
|
||||
activeCoalitionChanged: CustomEvent<any>;
|
||||
serverStatusUpdated: CustomEvent<ServerStatus>;
|
||||
mapForceBoxSelect: CustomEvent<any>
|
||||
mapForceBoxSelect: CustomEvent<any>;
|
||||
coalitionAreaSelected: CustomEvent<CoalitionPolygon>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
5
frontend/react/src/map/coalitionarea/coalitionarea.ts
Normal file
5
frontend/react/src/map/coalitionarea/coalitionarea.ts
Normal file
@ -0,0 +1,5 @@
|
||||
const CoalitionArea = Base => class extends Base {
|
||||
|
||||
}
|
||||
|
||||
export CoalitionArea;
|
||||
162
frontend/react/src/map/coalitionarea/coalitioncircle.ts
Normal file
162
frontend/react/src/map/coalitionarea/coalitioncircle.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import {
|
||||
LatLngExpression,
|
||||
Map,
|
||||
Circle,
|
||||
DivIcon,
|
||||
Marker,
|
||||
CircleOptions,
|
||||
LatLng,
|
||||
} from "leaflet";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
|
||||
import { Coalition } from "../../types/types";
|
||||
import * as turf from "@turf/turf";
|
||||
|
||||
let totalAreas = 0;
|
||||
|
||||
export class CoalitionCircle extends Circle {
|
||||
#coalition: Coalition = "blue";
|
||||
#selected: boolean = true;
|
||||
#editing: boolean = true;
|
||||
#radiusHandle: CoalitionAreaHandle;
|
||||
#labelText: string;
|
||||
#label: Marker;
|
||||
|
||||
constructor(latlng: LatLngExpression, options: CircleOptions) {
|
||||
if (options === undefined) options = { radius: 0 };
|
||||
|
||||
totalAreas++;
|
||||
|
||||
options.bubblingMouseEvents = false;
|
||||
options.interactive = false;
|
||||
//@ts-ignore draggable option added by leaflet-path-drag
|
||||
options.draggable = true;
|
||||
|
||||
super(latlng, options);
|
||||
this.#setColors();
|
||||
|
||||
this.#labelText = `Circle ${totalAreas}`;
|
||||
|
||||
if (
|
||||
[BLUE_COMMANDER, RED_COMMANDER].includes(
|
||||
getApp().getMissionManager().getCommandModeOptions().commandMode
|
||||
)
|
||||
)
|
||||
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
|
||||
|
||||
this.on("drag", () => {
|
||||
this.#setRadiusHandle();
|
||||
this.#drawLabel();
|
||||
});
|
||||
}
|
||||
|
||||
setCoalition(coalition: Coalition) {
|
||||
this.#coalition = coalition;
|
||||
this.#setColors();
|
||||
}
|
||||
|
||||
getCoalition() {
|
||||
return this.#coalition;
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this.#selected = selected;
|
||||
this.#setColors();
|
||||
this.#setRadiusHandle();
|
||||
this.#drawLabel();
|
||||
this.setOpacity(selected ? 1 : 0.5);
|
||||
|
||||
//@ts-ignore draggable option added by leaflet-path-drag
|
||||
selected ? this.dragging.enable() : this.dragging.disable();
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
setEditing(editing: boolean) {
|
||||
this.#editing = editing;
|
||||
this.#setRadiusHandle();
|
||||
}
|
||||
|
||||
getEditing() {
|
||||
return this.#editing;
|
||||
}
|
||||
|
||||
setOpacity(opacity: number) {
|
||||
this.setStyle({ opacity: opacity, fillOpacity: opacity * 0.25 });
|
||||
}
|
||||
|
||||
getLabelText() {
|
||||
return this.#labelText;
|
||||
}
|
||||
|
||||
setLabelText(labelText: string) {
|
||||
this.#labelText = labelText;
|
||||
this.#drawLabel();
|
||||
}
|
||||
|
||||
onRemove(map: Map): this {
|
||||
super.onRemove(map);
|
||||
this.#radiusHandle.removeFrom(getApp().getMap());
|
||||
return this;
|
||||
}
|
||||
|
||||
setLatLng(latlng: LatLngExpression): this {
|
||||
super.setLatLng(latlng);
|
||||
this.#setRadiusHandle();
|
||||
this.#drawLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
#setColors() {
|
||||
let coalitionColor = "#FFFFFF";
|
||||
if (this.getCoalition() === "blue") coalitionColor = "#247be2";
|
||||
else if (this.getCoalition() === "red") coalitionColor = "#ff5858";
|
||||
|
||||
this.setStyle({
|
||||
color: this.getSelected() ? "white" : coalitionColor,
|
||||
fillColor: coalitionColor,
|
||||
});
|
||||
}
|
||||
|
||||
#setRadiusHandle() {
|
||||
if (this.#radiusHandle) this.#radiusHandle.removeFrom(getApp().getMap());
|
||||
|
||||
if (this.#selected) {
|
||||
const dest = turf.destination(
|
||||
turf.point([this.getLatLng().lng, this.getLatLng().lat]),
|
||||
this.getRadius() / 1000,
|
||||
0
|
||||
);
|
||||
this.#radiusHandle = new CoalitionAreaHandle(
|
||||
new LatLng(dest.geometry.coordinates[1], dest.geometry.coordinates[0])
|
||||
);
|
||||
this.#radiusHandle.addTo(getApp().getMap());
|
||||
this.#radiusHandle.on("drag", (e: any) => {
|
||||
this.setRadius(this.getLatLng().distanceTo(e.latlng));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#drawLabel() {
|
||||
if (this.#label) {
|
||||
this.#label.removeFrom(this._map);
|
||||
}
|
||||
this.#label = new Marker(this.getLatLng(), {
|
||||
icon: new DivIcon({
|
||||
className: "label",
|
||||
html: this.#labelText,
|
||||
iconSize: [100, 40],
|
||||
}),
|
||||
interactive: false,
|
||||
}).addTo(this._map);
|
||||
this.#label
|
||||
.getElement()
|
||||
?.classList.add(
|
||||
`ol-coalitionarea-label`,
|
||||
`${this.#selected ? "selected" : `${this.#coalition}`}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ import {
|
||||
Polygon,
|
||||
PolylineOptions,
|
||||
DivIcon,
|
||||
Marker
|
||||
Marker,
|
||||
} from "leaflet";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
@ -37,11 +37,13 @@ export class CoalitionPolygon extends Polygon {
|
||||
|
||||
options.bubblingMouseEvents = false;
|
||||
options.interactive = false;
|
||||
//@ts-ignore draggable option added by leaflet-path-drag
|
||||
options.draggable = true;
|
||||
|
||||
super(latlngs, options);
|
||||
this.#setColors();
|
||||
|
||||
this.#labelText = `Polygon ${totalAreas}`
|
||||
this.#labelText = `Polygon ${totalAreas}`;
|
||||
|
||||
if (
|
||||
[BLUE_COMMANDER, RED_COMMANDER].includes(
|
||||
@ -49,6 +51,12 @@ export class CoalitionPolygon extends Polygon {
|
||||
)
|
||||
)
|
||||
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
|
||||
|
||||
this.on("drag", () => {
|
||||
this.#setHandles();
|
||||
this.#setMiddleHandles();
|
||||
this.#drawLabel();
|
||||
});
|
||||
}
|
||||
|
||||
setCoalition(coalition: Coalition) {
|
||||
@ -73,6 +81,9 @@ export class CoalitionPolygon extends Polygon {
|
||||
this.setLatLngs(latlngs);
|
||||
this.setEditing(false);
|
||||
}
|
||||
|
||||
//@ts-ignore draggable option added by leaflet-path-drag
|
||||
selected ? this.dragging.enable() : this.dragging.disable();
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
@ -103,7 +114,7 @@ export class CoalitionPolygon extends Polygon {
|
||||
setOpacity(opacity: number) {
|
||||
this.setStyle({ opacity: opacity, fillOpacity: opacity * 0.25 });
|
||||
}
|
||||
|
||||
|
||||
getLabelText() {
|
||||
return this.#labelText;
|
||||
}
|
||||
@ -123,15 +134,19 @@ export class CoalitionPolygon extends Polygon {
|
||||
return this;
|
||||
}
|
||||
|
||||
setLatLngs(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][]){
|
||||
setLatLngs(
|
||||
latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][]
|
||||
) {
|
||||
super.setLatLngs(latlngs);
|
||||
this.#drawLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
#setColors() {
|
||||
const coalitionColor =
|
||||
this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
|
||||
let coalitionColor = "#FFFFFF";
|
||||
if (this.getCoalition() === "blue") coalitionColor = "#247be2";
|
||||
else if (this.getCoalition() === "red") coalitionColor = "#ff5858";
|
||||
|
||||
this.setStyle({
|
||||
color: this.getSelected() ? "white" : coalitionColor,
|
||||
fillColor: coalitionColor,
|
||||
@ -196,18 +211,25 @@ export class CoalitionPolygon extends Polygon {
|
||||
}
|
||||
}
|
||||
|
||||
#drawLabel() {
|
||||
if (this.#label) {
|
||||
this.#label.removeFrom(this._map);
|
||||
#drawLabel() {
|
||||
if (this.#label) {
|
||||
this.#label.removeFrom(this._map);
|
||||
}
|
||||
if ((this.getLatLngs()[0] as LatLng[]).length > 2) {
|
||||
this.#label = new Marker(polyCenter(this), {
|
||||
icon: new DivIcon({
|
||||
className: "label",
|
||||
html: this.#labelText,
|
||||
iconSize: [100, 40],
|
||||
}),
|
||||
interactive: false,
|
||||
}).addTo(this._map);
|
||||
this.#label
|
||||
.getElement()
|
||||
?.classList.add(
|
||||
`ol-coalitionarea-label`,
|
||||
`${this.#selected ? "selected" : `${this.#coalition}`}`
|
||||
);
|
||||
}
|
||||
}
|
||||
this.#label = new Marker(polyCenter(this), {
|
||||
icon: new DivIcon({
|
||||
className: 'label',
|
||||
html: this.#labelText,
|
||||
iconSize: [100, 40]
|
||||
}),
|
||||
interactive: false
|
||||
}).addTo(this._map);
|
||||
this.#label.getElement()?.classList.add(`ol-coalitionarea-label`, `${this.#selected? "selected": `${this.#coalition}`}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class DrawingCursor extends CustomMarker {
|
||||
constructor() {
|
||||
super(new LatLng(0, 0), { interactive: false });
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(
|
||||
new DivIcon({
|
||||
iconSize: [24, 24],
|
||||
iconAnchor: [0, 24],
|
||||
className: "leaflet-draw-marker",
|
||||
})
|
||||
);
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-draw-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,13 @@ import { getApp } from "../olympusapp";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { deg2rad, getGroundElevation, polyContains } from "../other/utils";
|
||||
import {
|
||||
areaContains,
|
||||
circleContains,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
polyContains,
|
||||
} from "../other/utils";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import {
|
||||
@ -18,6 +24,7 @@ import {
|
||||
MAP_OPTIONS_DEFAULTS,
|
||||
MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
COALITIONAREA_EDIT,
|
||||
COALITIONAREA_DRAW_CIRCLE,
|
||||
} from "../constants/constants";
|
||||
import { CoalitionPolygon } from "./coalitionarea/coalitionpolygon";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
@ -29,6 +36,10 @@ import "./markers/stylesheets/airbase.css";
|
||||
import "./markers/stylesheets/bullseye.css";
|
||||
import "./markers/stylesheets/units.css";
|
||||
import "./map.css";
|
||||
import { CoalitionCircle } from "./coalitionarea/coalitioncircle";
|
||||
|
||||
import "leaflet-path-drag";
|
||||
import { faLeftLong } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
/* Register the handler for the box selection */
|
||||
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
|
||||
@ -92,7 +103,7 @@ export class Map extends L.Map {
|
||||
#cameraZoomRatio: number = 1.0;
|
||||
|
||||
/* Coalition areas drawing */
|
||||
#coalitionPolygons: CoalitionPolygon[] = [];
|
||||
#coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = [];
|
||||
|
||||
/* Unit context actions */
|
||||
#contextAction: null | ContextAction = null;
|
||||
@ -132,7 +143,7 @@ export class Map extends L.Map {
|
||||
this.#miniMapPolyline.addTo(this.#miniMapLayerGroup);
|
||||
|
||||
/* Init the state machine */
|
||||
this.#state = IDLE;
|
||||
this.setState(IDLE);
|
||||
|
||||
/* Register event handles */
|
||||
this.on("zoomstart", (e: any) => this.#onZoomStart(e));
|
||||
@ -174,18 +185,6 @@ export class Map extends L.Map {
|
||||
);
|
||||
});
|
||||
|
||||
document.addEventListener(
|
||||
"toggleCoalitionAreaDraw",
|
||||
(ev: CustomEventInit) => {
|
||||
this.deselectAllCoalitionAreas();
|
||||
if (ev.detail?.type == "polygon") {
|
||||
if (this.getState() !== COALITIONAREA_DRAW_POLYGON)
|
||||
this.setState(COALITIONAREA_DRAW_POLYGON);
|
||||
else this.setState(IDLE);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//document.addEventListener("unitUpdated", (ev: CustomEvent) => {
|
||||
// if (this.#centerUnit != null && ev.detail == this.#centerUnit)
|
||||
// this.#panToUnit(this.#centerUnit);
|
||||
@ -366,27 +365,38 @@ export class Map extends L.Map {
|
||||
console.log(`Switching from state ${this.#state} to ${state}`);
|
||||
|
||||
/* Operations to perform when leaving a state */
|
||||
if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
if (
|
||||
this.#state === COALITIONAREA_DRAW_POLYGON ||
|
||||
this.#state === COALITIONAREA_DRAW_CIRCLE
|
||||
)
|
||||
this.getSelectedCoalitionArea()?.setEditing(false);
|
||||
}
|
||||
|
||||
this.#state = state;
|
||||
|
||||
/* Operations to perform when entering a state */
|
||||
if (this.#state === IDLE) {
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
getApp().getUnitsManager()?.deselectAllUnits();
|
||||
this.deselectAllCoalitionAreas();
|
||||
} else if (this.#state === SPAWN_UNIT) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
this.#spawnRequestTable = options?.spawnRequestTable ?? null;
|
||||
console.log(`Spawn request table:`);
|
||||
console.log(this.#spawnRequestTable);
|
||||
} else if (this.#state === CONTEXT_ACTION) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
this.#contextAction = options?.contextAction ?? null;
|
||||
console.log(`Context action:`);
|
||||
console.log(this.#contextAction);
|
||||
} else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
this.#coalitionPolygons.push(new CoalitionPolygon([]));
|
||||
this.#coalitionPolygons[this.#coalitionPolygons.length - 1].addTo(this);
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
this.#coalitionAreas.push(new CoalitionPolygon([]));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
} else if (this.#state === COALITIONAREA_DRAW_CIRCLE) {
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
this.#coalitionAreas.push(
|
||||
new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 })
|
||||
);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
}
|
||||
|
||||
document.dispatchEvent(
|
||||
@ -398,18 +408,122 @@ export class Map extends L.Map {
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
getCurrentControls() {
|
||||
if (this.#state === IDLE) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: "Select unit",
|
||||
},
|
||||
{
|
||||
actions: ["Shift", "Drag"],
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
text: "Move map location",
|
||||
},
|
||||
{
|
||||
actions: ["Long tap", "Drag"],
|
||||
text: "Box selection",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === SPAWN_UNIT) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: "Spawn unit",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
text: "Exit spawn mode",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === CONTEXT_ACTION) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: this.#contextAction?.getLabel() ?? "",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
text: "Exit action mode",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === COALITIONAREA_EDIT) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: "Select shape",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
text: "Exit drawing mode",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: "Add vertex to polygon",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
text: "Finalize polygon",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === COALITIONAREA_DRAW_CIRCLE) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: "Add circle",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllCoalitionAreas() {
|
||||
this.#coalitionPolygons.forEach((coalitionPolygon: CoalitionPolygon) =>
|
||||
coalitionPolygon.setSelected(false)
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("coalitionAreaSelected", {
|
||||
detail: null,
|
||||
})
|
||||
);
|
||||
|
||||
this.#coalitionAreas.forEach(
|
||||
(coalitionArea: CoalitionPolygon | CoalitionCircle) =>
|
||||
coalitionArea.setSelected(false)
|
||||
);
|
||||
}
|
||||
|
||||
deleteCoalitionArea(coalitionArea: CoalitionPolygon) {
|
||||
if (this.#coalitionPolygons.includes(coalitionArea))
|
||||
this.#coalitionPolygons.splice(
|
||||
this.#coalitionPolygons.indexOf(coalitionArea),
|
||||
deleteCoalitionArea(coalitionArea: CoalitionPolygon | CoalitionCircle) {
|
||||
if (this.#coalitionAreas.includes(coalitionArea))
|
||||
this.#coalitionAreas.splice(
|
||||
this.#coalitionAreas.indexOf(coalitionArea),
|
||||
1
|
||||
);
|
||||
|
||||
if (this.hasLayer(coalitionArea)) this.removeLayer(coalitionArea);
|
||||
}
|
||||
|
||||
@ -550,15 +664,13 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
getSelectedCoalitionArea() {
|
||||
return this.#coalitionPolygons.find((coalitionPolygon: CoalitionPolygon) => {
|
||||
return coalitionPolygon.getSelected();
|
||||
});
|
||||
}
|
||||
const coalitionArea = this.#coalitionAreas.find(
|
||||
(coalitionArea: CoalitionPolygon | CoalitionCircle) => {
|
||||
return coalitionArea.getSelected();
|
||||
}
|
||||
);
|
||||
|
||||
bringCoalitionAreaToBack(coalitionArea: CoalitionPolygon) {
|
||||
coalitionArea.bringToBack();
|
||||
this.#coalitionPolygons.splice(this.#coalitionPolygons.indexOf(coalitionArea), 1);
|
||||
this.#coalitionPolygons.unshift(coalitionArea);
|
||||
return coalitionArea ?? null;
|
||||
}
|
||||
|
||||
setOption(key, value) {
|
||||
@ -644,7 +756,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
preventClicks() {
|
||||
console.log("Preventing clicks on map")
|
||||
console.log("Preventing clicks on map");
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
}
|
||||
@ -699,7 +811,10 @@ export class Map extends L.Map {
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
if (
|
||||
this.#state === COALITIONAREA_DRAW_POLYGON ||
|
||||
this.#state === COALITIONAREA_DRAW_CIRCLE
|
||||
) {
|
||||
this.setState(COALITIONAREA_EDIT);
|
||||
} else {
|
||||
this.setState(IDLE);
|
||||
@ -736,11 +851,33 @@ export class Map extends L.Map {
|
||||
);
|
||||
}
|
||||
} else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
this.getSelectedCoalitionArea()?.getEditing()
|
||||
? this.getSelectedCoalitionArea()?.addTemporaryLatLng(location)
|
||||
: this.deselectAllCoalitionAreas();
|
||||
} else if (this.#state === COALITIONAREA_EDIT) {
|
||||
const selectedArea = this.getSelectedCoalitionArea();
|
||||
if (selectedArea && selectedArea instanceof CoalitionPolygon) {
|
||||
selectedArea.addTemporaryLatLng(location);
|
||||
}
|
||||
} else if (this.#state === COALITIONAREA_DRAW_CIRCLE) {
|
||||
const selectedArea = this.getSelectedCoalitionArea();
|
||||
if (selectedArea && selectedArea instanceof CoalitionCircle) {
|
||||
if (
|
||||
selectedArea.getLatLng().lat == 0 &&
|
||||
selectedArea.getLatLng().lng == 0
|
||||
)
|
||||
selectedArea.setLatLng(location);
|
||||
this.setState(COALITIONAREA_EDIT);
|
||||
}
|
||||
} else if (this.#state == COALITIONAREA_EDIT) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
for (let idx = 0; idx < this.#coalitionAreas.length; idx++) {
|
||||
if (areaContains(e.latlng, this.#coalitionAreas[idx])) {
|
||||
this.#coalitionAreas[idx].setSelected(true);
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("coalitionAreaSelected", {
|
||||
detail: this.#coalitionAreas[idx],
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (this.#state === CONTEXT_ACTION) {
|
||||
this.executeContextAction(null, e.latlng);
|
||||
} else {
|
||||
@ -750,6 +887,8 @@ export class Map extends L.Map {
|
||||
#onLongPress(e: any) {
|
||||
console.log(`Long press at ${e.latlng}`);
|
||||
|
||||
this.deselectAllCoalitionAreas();
|
||||
|
||||
if (!this.#isDragging && !this.#isZooming) {
|
||||
if (this.#state == IDLE) {
|
||||
if (e.type === "touchstart")
|
||||
@ -760,13 +899,6 @@ export class Map extends L.Map {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("mapForceBoxSelect", { detail: e.originalEvent })
|
||||
);
|
||||
} else if (this.#state == COALITIONAREA_EDIT) {
|
||||
for (let idx = 0; idx < this.#coalitionPolygons.length; idx++) {
|
||||
if (polyContains(e.latlng, this.#coalitionPolygons[idx])) {
|
||||
this.#coalitionPolygons[idx].setSelected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { LatLng, Polygon } from "leaflet";
|
||||
import { Circle, LatLng, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import {
|
||||
@ -17,6 +17,7 @@ import { DateAndTime, UnitBlueprint } from "../interfaces";
|
||||
import { Converter } from "usng";
|
||||
import { MGRS } from "../types/types";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { featureCollection } from "turf";
|
||||
|
||||
export function bearing(
|
||||
lat1: number,
|
||||
@ -315,14 +316,30 @@ export function nmToFt(nm: number) {
|
||||
return nm * 6076.12;
|
||||
}
|
||||
|
||||
export function areaContains(latlng: LatLng, area: Polygon | Circle) {
|
||||
if (area instanceof Polygon)
|
||||
return polyContains(latlng, area);
|
||||
else
|
||||
return circleContains(latlng, area);
|
||||
}
|
||||
|
||||
export function polyContains(latlng: LatLng, polygon: Polygon) {
|
||||
const poly = polygon.toGeoJSON();
|
||||
let coordinates = [(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {return [latlng.lng, latlng.lat]} )];
|
||||
coordinates[0].push([polygon.getLatLngs()[0][0].lng, polygon.getLatLngs()[0][0].lat])
|
||||
const poly = turf.polygon(coordinates);
|
||||
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
|
||||
}
|
||||
|
||||
export function circleContains(latlng: LatLng, circle: Circle) {
|
||||
const poly = turf.circle(turf.point([circle.getLatLng().lng, circle.getLatLng().lat]), circle.getRadius() / 1000, 100, 'kilometers');
|
||||
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
|
||||
}
|
||||
|
||||
export function polyCenter(polygon: Polygon) {
|
||||
const poly = polygon.toGeoJSON();
|
||||
const center = turf.center(poly);
|
||||
let coordinates = [(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {return [latlng.lng, latlng.lat]} )];
|
||||
coordinates[0].push([polygon.getLatLngs()[0][0].lng, polygon.getLatLngs()[0][0].lat])
|
||||
const poly = turf.polygon(coordinates);
|
||||
const center = turf.center(featureCollection([poly]));
|
||||
return new LatLng(center.geometry.coordinates[1], center.geometry.coordinates[0]);
|
||||
}
|
||||
|
||||
@ -337,7 +354,9 @@ export function randomPointInPoly(polygon: Polygon): LatLng {
|
||||
var lat = y_min + Math.random() * (y_max - y_min);
|
||||
var lng = x_min + Math.random() * (x_max - x_min);
|
||||
|
||||
var poly = polygon.toGeoJSON();
|
||||
let coordinates = [(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {return [latlng.lng, latlng.lat]} )];
|
||||
coordinates[0].push([polygon.getLatLngs()[0][0].lng, polygon.getLatLngs()[0][0].lat])
|
||||
const poly = turf.polygon(coordinates);
|
||||
var inside = turf.inside(turf.point([lng, lat]), poly);
|
||||
|
||||
if (inside) {
|
||||
|
||||
@ -84,9 +84,8 @@ export function OlDropdown(props: {
|
||||
const target = event.target;
|
||||
if (
|
||||
target &&
|
||||
/*!content.contains(target as HTMLElement) &&*/ !button.contains(
|
||||
target as HTMLElement
|
||||
)
|
||||
!content.contains(target as HTMLElement) &&
|
||||
!button.contains(target as HTMLElement)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
50
frontend/react/src/ui/panels/controls.tsx
Normal file
50
frontend/react/src/ui/panels/controls.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import { getApp } from "../../olympusapp";
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
[] as { actions: string[]; text: string }[]
|
||||
);
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
setControls(getApp().getMap().getCurrentControls());
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
absolute bottom-[20px] right-[10px] w-[288px] z-ui-0 flex flex-col
|
||||
items-center justify-between gap-1 p-3 text-sm
|
||||
`}
|
||||
>
|
||||
{controls.map((control) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-full justify-between gap-2 rounded-full px-2 py-1
|
||||
backdrop-blur-lg
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto">{control.text}</div>
|
||||
<div className="flex gap-1">
|
||||
{control.actions.map((action, idx) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
rounded-full bg-olympus-500 px-1 py-0.5 text-sm
|
||||
font-bold text-white
|
||||
`}
|
||||
>
|
||||
{action}
|
||||
</div>
|
||||
{idx !== control.actions.length - 1 && <div>+</div>}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { Menu } from "./components/menu";
|
||||
import { FaQuestionCircle, FaRegCircle } from "react-icons/fa";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import {
|
||||
COALITIONAREA_DRAW_CIRCLE,
|
||||
COALITIONAREA_DRAW_POLYGON,
|
||||
COALITIONAREA_EDIT,
|
||||
IDLE,
|
||||
@ -16,28 +17,44 @@ import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle";
|
||||
|
||||
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [drawingPolygon, setDrawingPolygon] = useState(false);
|
||||
const [drawingCircle, setDrawingCircle] = useState(false);
|
||||
const [activeCoalitionArea, setActiveCoalitionArea] = useState(
|
||||
null as null | CoalitionPolygon
|
||||
null as null | CoalitionPolygon | CoalitionCircle
|
||||
);
|
||||
const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition);
|
||||
const [IADSDensity, setIADSDensity] = useState(50);
|
||||
const [IADSDistribution, setIADSDistribution] = useState(50);
|
||||
const [forceCoalitionAppropriateUnits, setForceCoalitionApproriateUnits] = useState(false);
|
||||
const [forceCoalitionAppropriateUnits, setForceCoalitionApproriateUnits] =
|
||||
useState(false);
|
||||
|
||||
const [typesSelection, setTypesSelection] = useState({});
|
||||
const [erasSelection, setErasSelection] = useState({});
|
||||
const [rangesSelection, setRangesSelection] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
/* If we are not in polygon drawing mode, force the draw polygon button off */
|
||||
if (
|
||||
drawingPolygon &&
|
||||
getApp().getMap().getState() !== COALITIONAREA_DRAW_POLYGON
|
||||
)
|
||||
setDrawingPolygon(false);
|
||||
|
||||
if (props.open && !drawingPolygon)
|
||||
/* If we are not in circle drawing mode, force the draw circle button off */
|
||||
if (
|
||||
drawingCircle &&
|
||||
getApp().getMap().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
|
||||
@ -52,42 +69,91 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)
|
||||
setDrawingPolygon(false);
|
||||
|
||||
if (
|
||||
[COALITIONAREA_DRAW_POLYGON, COALITIONAREA_EDIT].includes(
|
||||
getApp().getMap().getState()
|
||||
)
|
||||
) {
|
||||
if (getApp().getMap().getState() == COALITIONAREA_EDIT) {
|
||||
setActiveCoalitionArea(
|
||||
getApp().getMap().getSelectedCoalitionArea() ?? null
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("coalitionAreaSelected", (event: any) => {
|
||||
setActiveCoalitionArea(event.detail);
|
||||
});
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
setActiveCoalitionArea(null);
|
||||
getApp().getMap().deselectAllCoalitionAreas();
|
||||
}}
|
||||
>
|
||||
<div className="p-4 text-sm text-gray-400">
|
||||
The draw tool allows you to quickly draw areas on the map and use these
|
||||
areas to spawn units and activate triggers.
|
||||
</div>
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
<div>
|
||||
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-gray-100">
|
||||
Use the polygon or paint tool to draw areas on the map.
|
||||
<>
|
||||
{activeCoalitionArea === null && !drawingPolygon && !drawingCircle && (
|
||||
<>
|
||||
<div className="p-4 text-sm text-gray-400">
|
||||
The draw tool allows you to quickly draw areas on the map and use
|
||||
these areas to spawn units and activate triggers.
|
||||
</div>
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
<div>
|
||||
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-gray-100">
|
||||
Use the polygon or circle tool to draw areas on the map.
|
||||
</div>
|
||||
<div className="text-gray-400">
|
||||
After drawing a shape, select it to see the options for
|
||||
spawning units. Click on a shape to select it.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
<>
|
||||
{activeCoalitionArea === null && drawingPolygon && (
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
<div>
|
||||
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-gray-100">
|
||||
Click on the map to add vertices to the polygon.
|
||||
</div>
|
||||
<div className="text-gray-400">
|
||||
When you are done, double click on the map to finalize the
|
||||
polygon. Vertices can be dragged or added to adjust the shape.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-400">
|
||||
After drawing a shape, select it to see the options for spawning
|
||||
units.
|
||||
)}
|
||||
</>
|
||||
|
||||
<>
|
||||
{activeCoalitionArea === null && drawingCircle && (
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
<div>
|
||||
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-gray-100">
|
||||
Click on the map to add a new circle.
|
||||
</div>
|
||||
<div className="text-gray-400">
|
||||
You can drag the circle to move it and you can use the handle to set the radius.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
<>
|
||||
{activeCoalitionArea === null && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
@ -110,9 +176,14 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={drawingCircle}
|
||||
onClick={() => {}}
|
||||
onClick={() => {
|
||||
if (drawingCircle)
|
||||
getApp().getMap().setState(COALITIONAREA_EDIT);
|
||||
else getApp().getMap().setState(COALITIONAREA_DRAW_CIRCLE);
|
||||
setDrawingCircle(!drawingCircle);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle (WIP)</div>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
)}
|
||||
@ -137,7 +208,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
defaultValue={activeCoalitionArea.getLabelText()}
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) =>
|
||||
activeCoalitionArea.setLabelText(ev.currentTarget.value)
|
||||
}
|
||||
@ -174,11 +245,26 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getTypes()
|
||||
.map((era) => {
|
||||
.map((type) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(
|
||||
JSON.parse(JSON.stringify(typesSelection))
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox checked={true} onChange={() => {}} />
|
||||
{era}
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(
|
||||
JSON.parse(JSON.stringify(typesSelection))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{type}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
@ -188,20 +274,50 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
.getGroundUnitDatabase()
|
||||
.getEras()
|
||||
.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(
|
||||
JSON.parse(JSON.stringify(erasSelection))
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox checked={true} onChange={() => {}} />
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(
|
||||
JSON.parse(JSON.stringify(erasSelection))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{era}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((era) => {
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(
|
||||
JSON.parse(JSON.stringify(rangesSelection))
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox checked={true} onChange={() => {}} />
|
||||
{era}
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(
|
||||
JSON.parse(JSON.stringify(rangesSelection))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{range}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
@ -215,10 +331,15 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
50%
|
||||
{IADSDensity}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider value={50} onChange={() => {}}></OlRangeSlider>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
@ -229,24 +350,53 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
50%
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider value={IADSDistribution} onChange={(ev) => {setIADSDistribution(Number(ev.target.value))}}></OlRangeSlider>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox checked={forceCoalitionAppropriateUnits} onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}} />
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(
|
||||
!forceCoalitionAppropriateUnits
|
||||
);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button type="button" className={`
|
||||
mb-2 me-2 rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}>Generate IADS</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-2 rounded-lg bg-blue-700 px-5 py-2.5 text-sm
|
||||
font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -25,6 +25,7 @@ import { sha256 } from "js-sha256";
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitMouseControlBar } from "./panels/unitmousecontrolbar";
|
||||
import { DrawingMenu } from "./panels/drawingmenu";
|
||||
import { ControlsPanel } from "./panels/controls";
|
||||
|
||||
export type OlympusState = {
|
||||
mainMenuVisible: boolean;
|
||||
@ -235,6 +236,7 @@ export function UI() {
|
||||
options={mapOptions}
|
||||
/>
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitControlMenu
|
||||
open={unitControlMenuVisible}
|
||||
onClose={() => setUnitControlMenuVisible(false)}
|
||||
|
||||
@ -2,6 +2,7 @@ import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { Unit } from "./unit";
|
||||
import {
|
||||
areaContains,
|
||||
bearingAndDistanceToLatLng,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
@ -44,6 +45,7 @@ import {
|
||||
import { Group } from "./group";
|
||||
import { UnitDataFileExport } from "./importexport/unitdatafileexport";
|
||||
import { UnitDataFileImport } from "./importexport/unitdatafileimport";
|
||||
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
|
||||
|
||||
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
|
||||
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
|
||||
@ -1642,7 +1644,7 @@ export class UnitsManager {
|
||||
* @param distribution Value between 0 and 100, controls how "scattered" the units will be
|
||||
*/
|
||||
createIADS(
|
||||
coalitionArea: CoalitionPolygon,
|
||||
coalitionArea: CoalitionPolygon | CoalitionCircle,
|
||||
types: { [key: string]: boolean },
|
||||
eras: { [key: string]: boolean },
|
||||
ranges: { [key: string]: boolean },
|
||||
@ -1665,7 +1667,7 @@ export class UnitsManager {
|
||||
var airbase = airbases[airbaseName];
|
||||
/* Check if the city is inside the coalition area */
|
||||
if (
|
||||
polyContains(
|
||||
areaContains(
|
||||
new LatLng(airbase.getLatLng().lat, airbase.getLatLng().lng),
|
||||
coalitionArea
|
||||
)
|
||||
@ -1684,7 +1686,7 @@ export class UnitsManager {
|
||||
);
|
||||
|
||||
/* Make sure the unit is still inside the coalition area */
|
||||
if (polyContains(latlng, coalitionArea)) {
|
||||
if (areaContains(latlng, coalitionArea)) {
|
||||
const type =
|
||||
activeTypes[Math.floor(Math.random() * activeTypes.length)];
|
||||
if (Math.random() < IADSDensities[type]) {
|
||||
@ -1729,7 +1731,7 @@ export class UnitsManager {
|
||||
citiesDatabase.forEach(
|
||||
(city: { lat: number; lng: number; pop: number }) => {
|
||||
/* Check if the city is inside the coalition area */
|
||||
if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) {
|
||||
if (areaContains(new LatLng(city.lat, city.lng), coalitionArea)) {
|
||||
/* Arbitrary formula to obtain a number of units depending on the city population */
|
||||
var pointsNumber = 2 + (Math.pow(city.pop, 0.15) * density) / 100;
|
||||
for (let i = 0; i < pointsNumber; i++) {
|
||||
@ -1744,7 +1746,7 @@ export class UnitsManager {
|
||||
);
|
||||
|
||||
/* Make sure the unit is still inside the coalition area */
|
||||
if (polyContains(latlng, coalitionArea)) {
|
||||
if (areaContains(latlng, coalitionArea)) {
|
||||
const type =
|
||||
activeTypes[Math.floor(Math.random() * activeTypes.length)];
|
||||
if (Math.random() < IADSDensities[type]) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user